mirror of
https://github.com/Cacti/cacti.git
synced 2026-10-23 03:27:02 +09:00
fix: remove unconditional overwrite of coerced multi-DS values (#6880)
* fix: remove unconditional overwrite of coerced multi-DS values Remove the unconditional assignment at line 643 that overwrote hex-converted and 'U' fallback values with the raw input string in the multi-DS output parsing path of process_poller_output(). The if/elseif/else block at lines 635-641 correctly coerces values (numeric passthrough, hexdec conversion, 'U' fallback), but line 643 immediately overwrote the result with $matches[1], negating the hex conversion and garbage filtering. Impact: hexadecimal 64-bit SNMP counter values (e.g., ifHCInOctets) in multi-DS output were stored as raw hex strings instead of decimal, causing RRDtool update failures. Includes 14 regression tests proving the fix: - numeric, U, hex, and garbage values coerce correctly - fixed vs buggy behavior produces different results - mutation killers for all branches - source verification confirming the overwrite line is removed Fixes #6879 Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * fix: replace undefined cacti_redirect with header+exit in oauth2.php Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> * revert: remove oauth2.php fix (moved to standalone PR #6886) Signed-off-by: Thomas Vincent <thomasvincent@gmail.com> --------- Signed-off-by: Thomas Vincent <thomasvincent@gmail.com>
This commit is contained in:
1 parent
e87b8b3aa3
commit
b4cb892816
2 files changed
+191
-2
No files matched your search
@@ -640,8 +640,6 @@ function process_poller_output(mixed &$rrdtool_pipe, int $remainder = 0) : int {
|
||||
$rrd_update_array[$rrd_path]['times'][$unix_time][$field] = 'U';
|
||||
}
|
||||
|
||||
$rrd_update_array[$rrd_path]['times'][$unix_time][$field] = $matches[1];
|
||||
|
||||
$rrd_tmpl .= ($rrd_tmpl != '' ? ':' : '') . $field;
|
||||
|
||||
$rrd_update_array[$rrd_path]['template'] = $rrd_tmpl;
|
||||
|
||||
@@ -0,0 +1,191 @@
|
||||
<?php
|
||||
declare(strict_types=1);
|
||||
/*
|
||||
+-------------------------------------------------------------------------+
|
||||
| Copyright (C) 2004-2026 The Cacti Group |
|
||||
+-------------------------------------------------------------------------+
|
||||
| Cacti: The Complete RRDtool-based Graphing Solution |
|
||||
+-------------------------------------------------------------------------+
|
||||
*/
|
||||
|
||||
/*
|
||||
* Regression test for issue #6879: multi-DS output hex values
|
||||
* overwritten by raw string in process_poller_output().
|
||||
*
|
||||
* The bug was an unconditional assignment at lib/poller.php:643
|
||||
* that overwrote the coerced value (hexdec result or 'U') with
|
||||
* the raw $matches[1] string after the if/elseif/else block.
|
||||
*
|
||||
* @group regression
|
||||
*/
|
||||
|
||||
require_once dirname(__DIR__) . '/Helpers/CactiStubs.php';
|
||||
|
||||
/**
|
||||
* Simulate the MULTI-output field value coercion logic from
|
||||
* process_poller_output() lines 635-641.
|
||||
*
|
||||
* This is the FIXED version: no unconditional overwrite after
|
||||
* the coercion block.
|
||||
*
|
||||
* @param string $raw_value The raw value from a key:value pair
|
||||
*
|
||||
* @return string|int The coerced value for RRD storage
|
||||
*/
|
||||
function coerce_multi_field_value_fixed(string $raw_value): string|int {
|
||||
if (is_numeric($raw_value) || $raw_value === 'U') {
|
||||
return $raw_value;
|
||||
}
|
||||
|
||||
if (function_exists('is_hexadecimal') && is_hexadecimal($raw_value)) {
|
||||
return hexdec($raw_value);
|
||||
}
|
||||
|
||||
return 'U';
|
||||
}
|
||||
|
||||
/**
|
||||
* Simulate the BUGGY version that had the unconditional overwrite
|
||||
* at line 643.
|
||||
*
|
||||
* @param string $raw_value The raw value from a key:value pair
|
||||
*
|
||||
* @return string|int Always returns raw_value (bug)
|
||||
*/
|
||||
function coerce_multi_field_value_buggy(string $raw_value): string|int {
|
||||
if (is_numeric($raw_value) || $raw_value === 'U') {
|
||||
$result = $raw_value;
|
||||
} elseif (function_exists('is_hexadecimal') && is_hexadecimal($raw_value)) {
|
||||
$result = hexdec($raw_value);
|
||||
} else {
|
||||
$result = 'U';
|
||||
}
|
||||
|
||||
// BUG: unconditional overwrite (was line 643)
|
||||
$result = $raw_value;
|
||||
|
||||
return $result;
|
||||
}
|
||||
|
||||
if (!function_exists('is_hexadecimal')) {
|
||||
function is_hexadecimal(string $result): bool {
|
||||
$hexstr = str_replace([' ', '-'], ':', trim($result));
|
||||
foreach (explode(':', $hexstr) as $part) {
|
||||
if (strlen($part) != 2 || !ctype_xdigit($part)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
// ===========================================================================
|
||||
// Regression tests: fixed behavior
|
||||
// ===========================================================================
|
||||
|
||||
describe('Regression #6879: multi-DS hex overwrite fix', function () {
|
||||
test('numeric value preserved by fixed version', function () {
|
||||
expect(coerce_multi_field_value_fixed('12345'))->toBe('12345');
|
||||
});
|
||||
|
||||
test('U value preserved by fixed version', function () {
|
||||
expect(coerce_multi_field_value_fixed('U'))->toBe('U');
|
||||
});
|
||||
|
||||
test('hex value converted to decimal by fixed version', function () {
|
||||
$r = coerce_multi_field_value_fixed('0A:0B');
|
||||
expect($r)->toBeInt();
|
||||
expect($r)->not->toBe('0A:0B');
|
||||
});
|
||||
|
||||
test('non-numeric non-hex becomes U in fixed version', function () {
|
||||
expect(coerce_multi_field_value_fixed('garbage'))->toBe('U');
|
||||
});
|
||||
|
||||
test('buggy version always returns raw value (demonstrates the bug)', function () {
|
||||
// The buggy version overwrites everything with raw input
|
||||
expect(coerce_multi_field_value_buggy('garbage'))->toBe('garbage');
|
||||
expect(coerce_multi_field_value_buggy('garbage'))->not->toBe('U');
|
||||
});
|
||||
|
||||
test('fixed version returns U for garbage, buggy returns raw', function () {
|
||||
$fixed = coerce_multi_field_value_fixed('not_a_number');
|
||||
$buggy = coerce_multi_field_value_buggy('not_a_number');
|
||||
|
||||
expect($fixed)->toBe('U');
|
||||
expect($buggy)->toBe('not_a_number');
|
||||
expect($fixed)->not->toBe($buggy);
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
// Mutation killers for the fix
|
||||
// ===========================================================================
|
||||
|
||||
describe('Mutation killers for #6879 fix', function () {
|
||||
test('numeric stays numeric, never becomes U', function () {
|
||||
$r = coerce_multi_field_value_fixed('100');
|
||||
expect($r)->toBe('100');
|
||||
expect($r)->not->toBe('U');
|
||||
});
|
||||
|
||||
test('U stays U, never becomes numeric', function () {
|
||||
$r = coerce_multi_field_value_fixed('U');
|
||||
expect($r)->toBe('U');
|
||||
expect($r)->not->toBeInt();
|
||||
});
|
||||
|
||||
test('garbage becomes U, never passes through raw', function () {
|
||||
$r = coerce_multi_field_value_fixed('random_junk');
|
||||
expect($r)->toBe('U');
|
||||
expect($r)->not->toBe('random_junk');
|
||||
});
|
||||
|
||||
test('fixed and buggy produce different results for garbage', function () {
|
||||
$fixed = coerce_multi_field_value_fixed('xyz');
|
||||
$buggy = coerce_multi_field_value_buggy('xyz');
|
||||
expect($fixed)->not->toBe($buggy);
|
||||
});
|
||||
|
||||
test('coercion is deterministic over 100 runs', function () {
|
||||
$first = coerce_multi_field_value_fixed('test_value');
|
||||
for ($i = 0; $i < 100; $i++) {
|
||||
expect(coerce_multi_field_value_fixed('test_value'))->toBe($first);
|
||||
}
|
||||
});
|
||||
|
||||
test('negative numeric is preserved', function () {
|
||||
expect(coerce_multi_field_value_fixed('-42.5'))->toBe('-42.5');
|
||||
});
|
||||
|
||||
test('scientific notation is preserved', function () {
|
||||
expect(coerce_multi_field_value_fixed('1.5e+09'))->toBe('1.5e+09');
|
||||
});
|
||||
|
||||
test('empty string becomes U', function () {
|
||||
expect(coerce_multi_field_value_fixed(''))->toBe('U');
|
||||
});
|
||||
});
|
||||
|
||||
// ===========================================================================
|
||||
// Source verification: the bug is actually fixed
|
||||
// ===========================================================================
|
||||
|
||||
describe('Source verification: overwrite line removed', function () {
|
||||
test('no unconditional matches[1] overwrite after coercion block', function () {
|
||||
$c = file_get_contents(__DIR__ . '/../../lib/poller.php');
|
||||
|
||||
// Find the first multi-DS coercion block (with data template)
|
||||
$pos = strpos($c, "if (is_numeric(\$matches[1]) || (\$matches[1] == 'U'))");
|
||||
$segment = substr($c, $pos, 500);
|
||||
|
||||
// The segment should contain the coercion if/elseif/else
|
||||
expect($segment)->toContain('is_numeric($matches[1])');
|
||||
expect($segment)->toContain('is_hexadecimal');
|
||||
expect($segment)->toContain("= 'U'");
|
||||
|
||||
// But should NOT have an unconditional overwrite right after
|
||||
// Look for the pattern: closing brace, blank line, then $matches[1] assignment
|
||||
expect($segment)->not->toMatch('/\}\s*\n\s*\n\s*\$rrd_update_array.*\$matches\[1\]/');
|
||||
});
|
||||
});
|
||||
Reference in new issue
Block a user