From fca1950d59b1fe973ba5aa2b2fa1eeef5cc73d1f Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 19:55:57 -0700 Subject: [PATCH 01/14] ci: build the test fixtures before running the suite Without tests/data the SKIPIF blocks skip 16 of the 22 tests, so CI reported green while most of the suite never ran. Signed-off-by: Thomas Vincent --- .github/workflows/ci.yml | 5 ++++- .gitignore | 16 ++++++++++++++++ 2 files changed, 20 insertions(+), 1 deletion(-) diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 0f58412..b8cea1d 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: - name: Install librrd run: | sudo apt-get update - sudo apt-get install -y librrd-dev pkg-config + sudo apt-get install -y librrd-dev pkg-config rrdtool - name: Setup PHP uses: shivammathur/setup-php@f3e473d116dcccaddc5834248c87452386958240 # v2.37.2 @@ -40,6 +40,9 @@ jobs: ./configure --with-rrd make -j"$(nproc)" + - name: Generate test fixtures + run: make -C tests/data all + - name: Test run: make test env: diff --git a/.gitignore b/.gitignore index 1f91b0f..8cd5787 100644 --- a/.gitignore +++ b/.gitignore @@ -29,3 +29,19 @@ modules run-tests.php tmp-php.ini tests/rrdtool-bin.inc +*.dep +configure.ac +tests/*.diff +tests/*.exp +tests/*.log +tests/*.out +tests/*.php +tests/*.sh +tests/*.txt +tests/*.png +tests/*.rrd +tests/data/*.png +tests/data/*.rrd +tests/data/*.txt +tests/data/*.xml +tests/data/Makefile From 109a553eb8012cc17859e6eb14f20d80057d6b38 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 19:55:57 -0700 Subject: [PATCH 02/14] tests: refresh the rrd_tune and rrd_xport expectations Both were written against an older librrd and fail against 1.7.2 and 1.11.0 alike; they were never noticed because the fixtures were missing. Signed-off-by: Thomas Vincent --- tests/rrd_016.phpt | 1 - tests/rrd_017.phpt | 132 ++++++++++++++++++--------------------------- 2 files changed, 53 insertions(+), 80 deletions(-) diff --git a/tests/rrd_016.phpt b/tests/rrd_016.phpt index 14b2c7d..d79fe57 100644 --- a/tests/rrd_016.phpt +++ b/tests/rrd_016.phpt @@ -22,4 +22,3 @@ var_dump(rrd_tune($destFile, array("true"))); bool(true) bool(false) bool(true) -%s \ No newline at end of file diff --git a/tests/rrd_017.phpt b/tests/rrd_017.phpt index 633cf95..11e3423 100644 --- a/tests/rrd_017.phpt +++ b/tests/rrd_017.phpt @@ -11,92 +11,66 @@ if (!file_exists($data_updatedDb)) { --FILE-- $result["end"]) { $aligned = false; } +} +var_dump($aligned, count($speed) === count($real)); ?> ---EXPECTF-- +--EXPECT-- array(4) { - ["start"]=> + [0]=> + string(5) "start" + [1]=> + string(3) "end" + [2]=> + string(4) "step" + [3]=> + string(4) "data" +} +int(920804700) +int(300) +array(2) { + [0]=> + string(7) "myspeed" + [1]=> + string(9) "realspeed" +} +array(3) { + [0]=> int(920804700) - ["end"]=> - int(920808300) - ["step"]=> - int(300) - ["data"]=> - array(2) { - [0]=> - array(2) { - ["legend"]=> - string(7) "myspeed" - ["data"]=> - array(13) { - [920804700]=> - float(NAN) - [920805000]=> - float(0.04) - [920805300]=> - float(0.02) - [920805600]=> - float(0) - [920805900]=> - float(0) - [920806200]=> - float(0.0333333333%s) - [920806500]=> - float(0.0333333333%s) - [920806800]=> - float(0.0333333333%s) - [920807100]=> - float(0.02) - [920807400]=> - float(0.02) - [920807700]=> - float(0.02) - [920808000]=> - float(0.0133333333%s) - [920808300]=> - float(0.0166666666%s) - } - } - [1]=> - array(2) { - ["legend"]=> - string(9) "realspeed" - ["data"]=> - array(13) { - [920804700]=> - float(NAN) - [920805000]=> - float(40) - [920805300]=> - float(20) - [920805600]=> - float(0) - [920805900]=> - float(0) - [920806200]=> - float(33.333333333%s) - [920806500]=> - float(33.333333333%s) - [920806800]=> - float(33.333333333%s) - [920807100]=> - float(20) - [920807400]=> - float(20) - [920807700]=> - float(20) - [920808000]=> - float(13.333333333%s) - [920808300]=> - float(16.666666666%s) - } - } - } + [1]=> + int(920805000) + [2]=> + int(920805300) } +bool(true) +0.0400 0.0200 0.0333 +40.0000 20.0000 33.3333 +bool(true) +bool(true) From def9748bd2193deef2c58c602bff2f538b57f37f Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 19:53:20 -0700 Subject: [PATCH 03/14] fix: initialise the RRDCreator startTime parameter Called with one argument, zend_parse_parameters never writes start_time, so the following `if (start_time)` dereferenced whatever the stack happened to hold and copied it into the --start option. Signed-off-by: Thomas Vincent --- rrd_create.c | 21 +++++++++++++++++---- tests/rrd_023.phpt | 27 +++++++++++++++++++++++++++ 2 files changed, 44 insertions(+), 4 deletions(-) create mode 100644 tests/rrd_023.phpt diff --git a/rrd_create.c b/rrd_create.c index 94cf16b..1af5d23 100644 --- a/rrd_create.c +++ b/rrd_create.c @@ -104,7 +104,7 @@ PHP_METHOD(RRDCreator, __construct) rrd_create_object *intern_obj; char *path; size_t path_length; /* better to set defaults for optional parameters */ - zend_string *start_time; + zend_string *start_time = NULL; long step = 0; int argc = ZEND_NUM_ARGS(); @@ -131,8 +131,12 @@ PHP_METHOD(RRDCreator, __construct) } intern_obj = php_rrd_create_fetch_object(Z_OBJ_P(getThis())); + if (intern_obj->file_path) efree(intern_obj->file_path); + if (intern_obj->start_time) efree(intern_obj->start_time); + intern_obj->start_time = NULL; + intern_obj->file_path = estrdup(path); - if (start_time) intern_obj->start_time = estrdup(ZSTR_VAL(start_time)); + if (start_time) intern_obj->start_time = estrndup(ZSTR_VAL(start_time), ZSTR_LEN(start_time)); if (step) { ZVAL_LONG(&intern_obj->zv_step, step); } @@ -218,6 +222,11 @@ PHP_METHOD(RRDCreator, save) zval zv_create_argv; rrd_args *create_argv; + if (!intern_obj->file_path) { + zend_throw_exception(NULL, "the object was not constructed", 0); + return; + } + array_init(&zv_create_argv); if (intern_obj->start_time) { @@ -249,8 +258,12 @@ PHP_METHOD(RRDCreator, save) } /* add array of archive and data source strings into argument list */ - php_array_merge(Z_ARRVAL(zv_create_argv), Z_ARRVAL(intern_obj->zv_arr_data_sources)); - php_array_merge(Z_ARRVAL(zv_create_argv), Z_ARRVAL(intern_obj->zv_arr_archives)); + if (!Z_ISUNDEF(intern_obj->zv_arr_data_sources)) { + php_array_merge(Z_ARRVAL(zv_create_argv), Z_ARRVAL(intern_obj->zv_arr_data_sources)); + } + if (!Z_ISUNDEF(intern_obj->zv_arr_archives)) { + php_array_merge(Z_ARRVAL(zv_create_argv), Z_ARRVAL(intern_obj->zv_arr_archives)); + } create_argv = rrd_args_init_by_phparray("create", intern_obj->file_path, &zv_create_argv); if (!create_argv) { diff --git a/tests/rrd_023.phpt b/tests/rrd_023.phpt new file mode 100644 index 0000000..7a335b5 --- /dev/null +++ b/tests/rrd_023.phpt @@ -0,0 +1,27 @@ +--TEST-- +RRDCreator::__construct() with no startTime argument +--SKIPIF-- + +--FILE-- +addDataSource("speed:COUNTER:600:U:U"); +$creator->addArchive("AVERAGE:0.5:1:24"); +var_dump($creator->save()); +var_dump(file_exists($file)); + +@unlink($file); +@unlink($primer_file); +?> +--EXPECT-- +bool(true) +bool(true) From c3f59578c09e89ef646817e866be43dd231e0472 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 19:53:21 -0700 Subject: [PATCH 04/14] fix: validate object state before building rrd arguments An integer key gave RRDUpdater::update() a NULL zend_string, save() on a bare RRDCreator merged undefined zvals, and every method assumed __construct had run; all three reached librrd through a NULL pointer. Signed-off-by: Thomas Vincent --- rrd_graph.c | 15 +++++++++++++-- rrd_update.c | 17 +++++++++++++++-- tests/rrd_024.phpt | 15 +++++++++++++++ tests/rrd_025.phpt | 12 ++++++++++++ tests/rrd_026.phpt | 33 +++++++++++++++++++++++++++++++++ 5 files changed, 88 insertions(+), 4 deletions(-) create mode 100644 tests/rrd_024.phpt create mode 100644 tests/rrd_025.phpt create mode 100644 tests/rrd_026.phpt diff --git a/rrd_graph.c b/rrd_graph.c index 0e43550..0f47339 100644 --- a/rrd_graph.c +++ b/rrd_graph.c @@ -100,7 +100,8 @@ PHP_METHOD(RRDGraph, __construct) } intern_obj = php_rrd_graph_fetch_object(Z_OBJ_P(getThis())); - intern_obj->file_path = estrdup(path); + if (intern_obj->file_path) efree(intern_obj->file_path); + intern_obj->file_path = estrndup(path, path_length); } /* }}} */ @@ -190,6 +191,11 @@ PHP_METHOD(RRDGraph, save) /* arguments for rrd_graph call */ rrd_args *graph_argv; + if (!intern_obj->file_path) { + zend_throw_exception(NULL, "the object was not constructed", 0); + return; + } + if (Z_TYPE(intern_obj->zv_arr_options) != IS_ARRAY) { zend_throw_exception(NULL, "options aren't correctly set", 0); return; @@ -261,8 +267,13 @@ PHP_METHOD(RRDGraph, saveVerbose) /* arguments for rrd_graph call */ rrd_args *graph_argv; + if (!intern_obj->file_path) { + zend_throw_exception(NULL, "the object was not constructed", 0); + return; + } + if (Z_TYPE(intern_obj->zv_arr_options) != IS_ARRAY) { - zend_throw_exception(NULL, "options aren't correctly set", 0); + zend_throw_exception(NULL, "options aren't correctly set", 0); return; } diff --git a/rrd_update.c b/rrd_update.c index e21c778..4646f8e 100644 --- a/rrd_update.c +++ b/rrd_update.c @@ -94,7 +94,8 @@ PHP_METHOD(RRDUpdater, __construct) } intern_obj = php_rrd_update_fetch_object(Z_OBJ_P(getThis())); - intern_obj->file_path = estrdup(path); + if (intern_obj->file_path) efree(intern_obj->file_path); + intern_obj->file_path = estrndup(path, path_length); } /* }}} */ @@ -136,6 +137,11 @@ PHP_METHOD(RRDUpdater, update) intern_obj = php_rrd_update_fetch_object(Z_OBJ_P(getThis())); + if (!intern_obj->file_path) { + zend_throw_exception(NULL, "the object was not constructed", 0); + return; + } + if (php_check_open_basedir(intern_obj->file_path)) { RETURN_FALSE; } @@ -146,6 +152,14 @@ PHP_METHOD(RRDUpdater, update) } ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zv_values_array), zs_ds_name, zv_ds_val) { + if (!zs_ds_name) { + smart_string_free(&ds_names); + smart_string_free(&ds_vals); + zend_throw_exception(NULL, + "values array must be keyed by data source name", 0); + return; + } + if (ds_names.len) { smart_string_appendc(&ds_names, ':'); } else { @@ -180,7 +194,6 @@ PHP_METHOD(RRDUpdater, update) if (!update_argv) { zend_error(E_WARNING, "cannot allocate arguments options"); zval_dtor(&zv_update_argv); - if (time_str_length == 0) efree(time); RETURN_FALSE; } diff --git a/tests/rrd_024.phpt b/tests/rrd_024.phpt new file mode 100644 index 0000000..5960a56 --- /dev/null +++ b/tests/rrd_024.phpt @@ -0,0 +1,15 @@ +--TEST-- +RRDUpdater::update() rejects a values array with integer keys +--SKIPIF-- + +--FILE-- +update(array(1.0)); +} catch (Exception $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; +} +?> +--EXPECT-- +Exception: values array must be keyed by data source name diff --git a/tests/rrd_025.phpt b/tests/rrd_025.phpt new file mode 100644 index 0000000..c1cd788 --- /dev/null +++ b/tests/rrd_025.phpt @@ -0,0 +1,12 @@ +--TEST-- +RRDCreator::save() with no data source and no archive +--SKIPIF-- + +--FILE-- +save()); +?> +--EXPECTF-- +Warning: cannot allocate arguments options in %s on line %d +bool(false) diff --git a/tests/rrd_026.phpt b/tests/rrd_026.phpt new file mode 100644 index 0000000..a9049e9 --- /dev/null +++ b/tests/rrd_026.phpt @@ -0,0 +1,33 @@ +--TEST-- +RRDCreator, RRDUpdater and RRDGraph without a constructor call +--SKIPIF-- + +--FILE-- +newInstanceWithoutConstructor(); + try { + switch ($class) { + case "RRDCreator": + $obj->addDataSource("speed:COUNTER:600:U:U"); + $obj->addArchive("AVERAGE:0.5:1:24"); + $obj->save(); + break; + case "RRDUpdater": + $obj->update(array("speed" => 1)); + break; + case "RRDGraph": + $obj->setOptions(array("--start" => "920804400")); + $obj->save(); + break; + } + echo $class, ": no exception\n"; + } catch (Exception $e) { + echo $class, ": ", $e->getMessage(), "\n"; + } +} +?> +--EXPECT-- +RRDCreator: the object was not constructed +RRDUpdater: the object was not constructed +RRDGraph: the object was not constructed From f18be81cfeb4f90860548a184c30b5476b3fec92 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 19:53:21 -0700 Subject: [PATCH 05/14] fix: size the timestamp buffer for 64-bit time values ZEND_LTOA writes its terminator at the return value of snprintf, so an 11-byte buffer overflowed once a timestamp needed more than ten digits, and rrd_fetch's 32-bit loop counter wrapped past the end of librrd's data. Signed-off-by: Thomas Vincent --- rrd.c | 32 +++++++++++++++++++++++--------- tests/rrd_027.phpt | 41 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+), 9 deletions(-) create mode 100644 tests/rrd_027.phpt diff --git a/rrd.c b/rrd.c index ff51776..28fbed0 100644 --- a/rrd.c +++ b/rrd.c @@ -61,6 +61,7 @@ PHP_FUNCTION(rrd_fetch) time_t start, end; unsigned long step, ds_cnt; /* count of data sources */ + unsigned ds_counter; char **ds_namv; /* list of data source names */ rrd_value_t *ds_data; /* all data from all sources */ @@ -95,11 +96,11 @@ PHP_FUNCTION(rrd_fetch) /* add "ds_namv" and "data" array into return values if there is any * result data */ - if (!ds_data || !ds_namv || !ds_cnt) { + if (!ds_data || !ds_namv || !ds_cnt || !step) { add_assoc_null(return_value, "data"); } else { rrd_value_t *datap = ds_data; - unsigned timestamp, ds_counter; + time_t timestamp; /* final array for all data from all data sources */ zval zv_data_array; @@ -124,8 +125,11 @@ PHP_FUNCTION(rrd_fetch) /* pointer for one data source retrieved data */ zval *ds_data_array; /* value for key (timestamp) in data array */ - char str_timestamp[11]; - ZEND_LTOA((zend_ulong)timestamp, str_timestamp, sizeof(str_timestamp)); + /* time_t is wider than zend_long on 32-bit builds, so + this cannot go through ZEND_LONG_FMT */ + char str_timestamp[24]; + snprintf(str_timestamp, sizeof(str_timestamp), + "%" PRId64, (int64_t)timestamp); /* gets pointer for data source result array */ ds_data_array = zend_hash_get_current_data(Z_ARRVAL(zv_data_array)); @@ -135,9 +139,11 @@ PHP_FUNCTION(rrd_fetch) } } add_assoc_zval(return_value, "data", &zv_data_array); + } - /* free data from rrd_fetch */ - free(ds_data); + /* free data from rrd_fetch */ + free(ds_data); + if (ds_namv) { for (ds_counter = 0; ds_counter < ds_cnt; ds_counter++) { free(ds_namv[ds_counter]); } @@ -436,8 +442,15 @@ PHP_FUNCTION(rrd_xport) add_assoc_long(return_value, "step", step); /* no data available */ - if (!data) { + if (!data || !step) { add_assoc_null(return_value, "data"); + if (legend_v) { + for (outvar_index = 0; outvar_index < outvar_count; outvar_index++) { + free(legend_v[outvar_index]); + } + free(legend_v); + } + free(data); return; } @@ -463,8 +476,9 @@ PHP_FUNCTION(rrd_xport) data_ptr = data + outvar_index; for (time_index = start + step; time_index <= end; time_index += step) { /* value for key (timestamp) in data array */ - char str_timestamp[11]; - ZEND_LTOA((zend_ulong)time_index, str_timestamp, sizeof(str_timestamp)); + char str_timestamp[24]; + snprintf(str_timestamp, sizeof(str_timestamp), + "%" PRId64, (int64_t)time_index); add_assoc_double(&time_data, str_timestamp, *data_ptr); data_ptr += outvar_count; diff --git a/tests/rrd_027.phpt b/tests/rrd_027.phpt new file mode 100644 index 0000000..ae73cee --- /dev/null +++ b/tests/rrd_027.phpt @@ -0,0 +1,41 @@ +--TEST-- +rrd_fetch and rrd_xport with timestamps wider than 10 digits +--SKIPIF-- + +--FILE-- + +--EXPECT-- +array(2) { + [0]=> + int(1700000000400) + [1]=> + int(1700000000700) +} +int(5) From d3c14d3cc45357b1c7add41963814d3a9f82b07d Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 20:19:42 -0700 Subject: [PATCH 06/14] fix: reset the remaining creator state on re-construction A second __construct kept the previous step, data sources and archives, so the new file was written from the old object's definition. Signed-off-by: Thomas Vincent --- rrd_create.c | 14 ++++++++++++++ tests/rrd_030.phpt | 29 +++++++++++++++++++++++++++++ 2 files changed, 43 insertions(+) create mode 100644 tests/rrd_030.phpt diff --git a/rrd_create.c b/rrd_create.c index 1af5d23..e8e0898 100644 --- a/rrd_create.c +++ b/rrd_create.c @@ -135,6 +135,20 @@ PHP_METHOD(RRDCreator, __construct) if (intern_obj->start_time) efree(intern_obj->start_time); intern_obj->start_time = NULL; + /* a second __construct otherwise inherits the previous step and sources */ + if (!Z_ISUNDEF(intern_obj->zv_step)) { + zval_dtor(&intern_obj->zv_step); + ZVAL_UNDEF(&intern_obj->zv_step); + } + if (!Z_ISUNDEF(intern_obj->zv_arr_data_sources)) { + zval_dtor(&intern_obj->zv_arr_data_sources); + ZVAL_UNDEF(&intern_obj->zv_arr_data_sources); + } + if (!Z_ISUNDEF(intern_obj->zv_arr_archives)) { + zval_dtor(&intern_obj->zv_arr_archives); + ZVAL_UNDEF(&intern_obj->zv_arr_archives); + } + intern_obj->file_path = estrdup(path); if (start_time) intern_obj->start_time = estrndup(ZSTR_VAL(start_time), ZSTR_LEN(start_time)); if (step) { diff --git a/tests/rrd_030.phpt b/tests/rrd_030.phpt new file mode 100644 index 0000000..adfa7b8 --- /dev/null +++ b/tests/rrd_030.phpt @@ -0,0 +1,29 @@ +--TEST-- +calling __construct a second time resets the object +--SKIPIF-- + +--FILE-- +addDataSource("speed:COUNTER:600:U:U"); +$creator->addArchive("AVERAGE:0.5:1:24"); + +/* the second construction must not inherit the first one's sources */ +$creator->__construct($second, "920804400", 600); +try { + var_dump($creator->save()); +} catch (Exception $e) { + echo get_class($e), ": ", $e->getMessage(), "\n"; +} +var_dump(file_exists($first), file_exists($second)); + +@unlink($first); @unlink($second); +?> +--EXPECT-- +Exception: you must define at least one Round Robin Archive +bool(false) +bool(false) From 2f1230454e9ffd9e2edf1e929daea9931609a2ee Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 21:40:26 -0700 Subject: [PATCH 07/14] fix: define librrd's out-parameters before the guards read them The new !ds_data / !data / !step tests declare those locals untrusted, and the hoisted free() reaches them on the same paths, so leaving them uninitialised traded a leak for a free of whatever the stack held. Signed-off-by: Thomas Vincent --- rrd.c | 28 +++++++++++++--------------- rrd_create.c | 3 ++- rrd_graph.c | 1 + rrd_update.c | 1 + 4 files changed, 17 insertions(+), 16 deletions(-) diff --git a/rrd.c b/rrd.c index 28fbed0..29b22d9 100644 --- a/rrd.c +++ b/rrd.c @@ -58,12 +58,12 @@ PHP_FUNCTION(rrd_fetch) zval *zv_arr_options; rrd_args *argv; /* returned values if rrd_fetch doesn't fail */ - time_t start, end; - unsigned long step, - ds_cnt; /* count of data sources */ + time_t start = 0, end = 0; + unsigned long step = 0, + ds_cnt = 0; /* count of data sources */ unsigned ds_counter; - char **ds_namv; /* list of data source names */ - rrd_value_t *ds_data; /* all data from all sources */ + char **ds_namv = NULL; /* list of data source names */ + rrd_value_t *ds_data = NULL; /* all data from all sources */ if (zend_parse_parameters(ZEND_NUM_ARGS(), "pa", &filename, &filename_length, &zv_arr_options) == FAILURE) { @@ -405,10 +405,10 @@ PHP_FUNCTION(rrd_xport) rrd_args *argv; /* return values from rrd_xport */ int xxsize; - time_t start, end, time_index; - unsigned long step, outvar_count; - char **legend_v; - rrd_value_t *data, *data_ptr; + time_t start = 0, end = 0, time_index; + unsigned long step = 0, outvar_count = 0; + char **legend_v = NULL; + rrd_value_t *data = NULL, *data_ptr; zval zv_data; zend_ulong outvar_index; @@ -442,14 +442,12 @@ PHP_FUNCTION(rrd_xport) add_assoc_long(return_value, "step", step); /* no data available */ - if (!data || !step) { + if (!data || !legend_v || !step) { add_assoc_null(return_value, "data"); - if (legend_v) { - for (outvar_index = 0; outvar_index < outvar_count; outvar_index++) { - free(legend_v[outvar_index]); - } - free(legend_v); + for (outvar_index = 0; legend_v && outvar_index < outvar_count; outvar_index++) { + free(legend_v[outvar_index]); } + free(legend_v); free(data); return; } diff --git a/rrd_create.c b/rrd_create.c index e8e0898..bf0951a 100644 --- a/rrd_create.c +++ b/rrd_create.c @@ -132,6 +132,7 @@ PHP_METHOD(RRDCreator, __construct) intern_obj = php_rrd_create_fetch_object(Z_OBJ_P(getThis())); if (intern_obj->file_path) efree(intern_obj->file_path); + intern_obj->file_path = NULL; if (intern_obj->start_time) efree(intern_obj->start_time); intern_obj->start_time = NULL; @@ -149,7 +150,7 @@ PHP_METHOD(RRDCreator, __construct) ZVAL_UNDEF(&intern_obj->zv_arr_archives); } - intern_obj->file_path = estrdup(path); + intern_obj->file_path = estrndup(path, path_length); if (start_time) intern_obj->start_time = estrndup(ZSTR_VAL(start_time), ZSTR_LEN(start_time)); if (step) { ZVAL_LONG(&intern_obj->zv_step, step); diff --git a/rrd_graph.c b/rrd_graph.c index 0f47339..2e415cd 100644 --- a/rrd_graph.c +++ b/rrd_graph.c @@ -101,6 +101,7 @@ PHP_METHOD(RRDGraph, __construct) intern_obj = php_rrd_graph_fetch_object(Z_OBJ_P(getThis())); if (intern_obj->file_path) efree(intern_obj->file_path); + intern_obj->file_path = NULL; intern_obj->file_path = estrndup(path, path_length); } /* }}} */ diff --git a/rrd_update.c b/rrd_update.c index 4646f8e..43fe067 100644 --- a/rrd_update.c +++ b/rrd_update.c @@ -95,6 +95,7 @@ PHP_METHOD(RRDUpdater, __construct) intern_obj = php_rrd_update_fetch_object(Z_OBJ_P(getThis())); if (intern_obj->file_path) efree(intern_obj->file_path); + intern_obj->file_path = NULL; intern_obj->file_path = estrndup(path, path_length); } /* }}} */ From bb33e619498e1d65005e7eae3b5dc4fa4bfedd10 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 19:55:39 -0700 Subject: [PATCH 08/14] fix: apply open_basedir to RRDCreator::save and RRDGraph::saveVerbose Both write a file the caller names, and both skipped the check that rrd_create() and RRDGraph::save() already perform. Signed-off-by: Thomas Vincent --- rrd_create.c | 4 ++++ rrd_graph.c | 11 +++++++---- tests/rrd_029.phpt | 39 +++++++++++++++++++++++++++++++++++++++ 3 files changed, 50 insertions(+), 4 deletions(-) create mode 100644 tests/rrd_029.phpt diff --git a/rrd_create.c b/rrd_create.c index bf0951a..b29a47c 100644 --- a/rrd_create.c +++ b/rrd_create.c @@ -242,6 +242,10 @@ PHP_METHOD(RRDCreator, save) return; } + if (php_check_open_basedir(intern_obj->file_path)) { + RETURN_FALSE; + } + array_init(&zv_create_argv); if (intern_obj->start_time) { diff --git a/rrd_graph.c b/rrd_graph.c index 2e415cd..0ca9529 100644 --- a/rrd_graph.c +++ b/rrd_graph.c @@ -158,12 +158,11 @@ static rrd_args *rrd_graph_obj_create_argv(const char *command_name, const rrd_g } /* use always string for option value */ - if (Z_TYPE_P(zv_option_val) != IS_STRING) { - convert_to_string(zv_option_val); - } + zend_string *option_str = zval_get_string(zv_option_val); - smart_string_appendl(&option, Z_STRVAL_P(zv_option_val), Z_STRLEN_P(zv_option_val)); + smart_string_appendl(&option, ZSTR_VAL(option_str), ZSTR_LEN(option_str)); smart_string_0(&option); + zend_string_release(option_str); add_next_index_string(&zv_argv, option.c); @@ -278,6 +277,10 @@ PHP_METHOD(RRDGraph, saveVerbose) return; } + if (php_check_open_basedir(intern_obj->file_path)) { + RETURN_FALSE; + } + graph_argv = rrd_graph_obj_create_argv("graphv", intern_obj); if (!graph_argv) { zend_error(E_WARNING, "cannot allocate arguments options"); diff --git a/tests/rrd_029.phpt b/tests/rrd_029.phpt new file mode 100644 index 0000000..716af20 --- /dev/null +++ b/tests/rrd_029.phpt @@ -0,0 +1,39 @@ +--TEST-- +RRDCreator::save() and RRDGraph::saveVerbose() honour open_basedir +--SKIPIF-- + +--INI-- +open_basedir= +--FILE-- +addDataSource("speed:COUNTER:600:U:U"); +$creator->addArchive("AVERAGE:0.5:1:24"); +var_dump($creator->save()); + +$graph = new RRDGraph("$outside/graph.png"); +$graph->setOptions(array( + "--start" => "920804400", + "--end" => "920808000", + 0 => "DEF:myspeed=$data_updatedDb:speed:AVERAGE", + 1 => "LINE1:myspeed#FF0000", +)); +var_dump($graph->saveVerbose()); +?> +--EXPECTF-- +Warning: RRDCreator::save(): open_basedir restriction in effect. File(%s) is not within the allowed path(s): (%s) in %s on line %d +bool(false) + +Warning: RRDGraph::saveVerbose(): open_basedir restriction in effect. File(%s) is not within the allowed path(s): (%s) in %s on line %d +bool(false) From 3bc872b457a9883610b98db0e5807916574e50d4 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 19:55:39 -0700 Subject: [PATCH 09/14] fix: stop rewriting the caller's options array convert_to_string() edited the array in place, so a non-string option changed the caller's variable and every by-value copy of it. Signed-off-by: Thomas Vincent --- rrd.c | 6 ++++-- rrd_update.c | 11 +++++------ tests/rrd_028.phpt | 23 +++++++++++++++++++++++ 3 files changed, 32 insertions(+), 8 deletions(-) create mode 100644 tests/rrd_028.phpt diff --git a/rrd.c b/rrd.c index 29b22d9..f9d816b 100644 --- a/rrd.c +++ b/rrd.c @@ -676,13 +676,15 @@ rrd_args *rrd_args_init_by_phparray(const char *command_name, const char *filena zend_hash_internal_pointer_reset(Z_ARRVAL_P(options)); for (i=0; i < option_count; i++) { zval *item; + zend_string *item_str; smart_string option = {0}; /* one argument option */ /* force using strings as array items */ item = zend_hash_get_current_data(Z_ARRVAL_P(options)); - if (Z_TYPE_P(item) != IS_STRING) convert_to_string(item); - smart_string_appendl(&option, Z_STRVAL_P(item), Z_STRLEN_P(item)); + item_str = zval_get_string(item); + smart_string_appendl(&option, ZSTR_VAL(item_str), ZSTR_LEN(item_str)); smart_string_0(&option); + zend_string_release(item_str); result->args[args_counter++] = estrdup(option.c); smart_string_free(&option); diff --git a/rrd_update.c b/rrd_update.c index 43fe067..cc41018 100644 --- a/rrd_update.c +++ b/rrd_update.c @@ -119,7 +119,7 @@ PHP_METHOD(RRDUpdater, update) size_t time_str_length = 1; int argc = ZEND_NUM_ARGS(); - zend_string *zs_ds_name; + zend_string *zs_ds_name, *zs_ds_val; zval *zv_ds_val; /* string for all data source names formated for rrd_update call */ @@ -167,17 +167,16 @@ PHP_METHOD(RRDUpdater, update) smart_string_appends(&ds_names, "--template="); } - smart_string_appends(&ds_names, ZSTR_VAL(zs_ds_name)); + smart_string_appendl(&ds_names, ZSTR_VAL(zs_ds_name), ZSTR_LEN(zs_ds_name)); /* "timestamp:ds1Value:ds2Value" string */ if (!ds_vals.len) { smart_string_appends(&ds_vals, time); } smart_string_appendc(&ds_vals, ':'); - if (Z_TYPE_P(zv_ds_val) != IS_STRING) { - convert_to_string(zv_ds_val); - } - smart_string_appendl(&ds_vals, Z_STRVAL_P(zv_ds_val), Z_STRLEN_P(zv_ds_val)); + zs_ds_val = zval_get_string(zv_ds_val); + smart_string_appendl(&ds_vals, ZSTR_VAL(zs_ds_val), ZSTR_LEN(zs_ds_val)); + zend_string_release(zs_ds_val); } ZEND_HASH_FOREACH_END(); smart_string_0(&ds_names); smart_string_0(&ds_vals); diff --git a/tests/rrd_028.phpt b/tests/rrd_028.phpt new file mode 100644 index 0000000..ce4fc4c --- /dev/null +++ b/tests/rrd_028.phpt @@ -0,0 +1,23 @@ +--TEST-- +options arrays are not modified by value conversion +--SKIPIF-- + +--FILE-- + 1.5); +$values_copy = $values; +$updater = new RRDUpdater(dirname(__FILE__) . "/no-mutate.rrd"); +try { $updater->update($values); } catch (Exception $e) {} +var_dump($values["speed"], $values_copy["speed"]); +?> +--EXPECT-- +int(300) +int(300) +float(1.5) +float(1.5) From 2b7e58c433693d5bc6b4d08b5443c7d1f5f424eb Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 20:21:12 -0700 Subject: [PATCH 10/14] fix: abort argument building when a value cannot be converted zval_get_string() hands back an empty string with an exception already pending, so the original Error was masked by whatever librrd then reported. Signed-off-by: Thomas Vincent --- rrd.c | 8 +++++++- rrd_graph.c | 8 +++++++- rrd_update.c | 7 ++++++- tests/rrd_031.phpt | 39 +++++++++++++++++++++++++++++++++++++++ 4 files changed, 59 insertions(+), 3 deletions(-) create mode 100644 tests/rrd_031.phpt diff --git a/rrd.c b/rrd.c index f9d816b..7ce564f 100644 --- a/rrd.c +++ b/rrd.c @@ -681,7 +681,13 @@ rrd_args *rrd_args_init_by_phparray(const char *command_name, const char *filena /* force using strings as array items */ item = zend_hash_get_current_data(Z_ARRVAL_P(options)); - item_str = zval_get_string(item); + item_str = zval_try_get_string(item); + if (!item_str) { + smart_string_free(&option); + result->count = args_counter; + rrd_args_free(result); + return NULL; + } smart_string_appendl(&option, ZSTR_VAL(item_str), ZSTR_LEN(item_str)); smart_string_0(&option); zend_string_release(item_str); diff --git a/rrd_graph.c b/rrd_graph.c index 0ca9529..4e7f17f 100644 --- a/rrd_graph.c +++ b/rrd_graph.c @@ -148,6 +148,7 @@ static rrd_args *rrd_graph_obj_create_argv(const char *command_name, const rrd_g ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(obj->zv_arr_options), num_key, zs_key, zv_option_val) { (void)num_key; /* to avoid -Wunused-but-set-variable */ smart_string option = {0}; /* one argument option */ + zend_string *option_str; /* option with string key means long option, hence they are used as * "key=value" e.g. "--start=920804400" @@ -158,7 +159,12 @@ static rrd_args *rrd_graph_obj_create_argv(const char *command_name, const rrd_g } /* use always string for option value */ - zend_string *option_str = zval_get_string(zv_option_val); + option_str = zval_try_get_string(zv_option_val); + if (!option_str) { + smart_string_free(&option); + zval_dtor(&zv_argv); + return NULL; + } smart_string_appendl(&option, ZSTR_VAL(option_str), ZSTR_LEN(option_str)); smart_string_0(&option); diff --git a/rrd_update.c b/rrd_update.c index cc41018..6119630 100644 --- a/rrd_update.c +++ b/rrd_update.c @@ -174,7 +174,12 @@ PHP_METHOD(RRDUpdater, update) smart_string_appends(&ds_vals, time); } smart_string_appendc(&ds_vals, ':'); - zs_ds_val = zval_get_string(zv_ds_val); + zs_ds_val = zval_try_get_string(zv_ds_val); + if (!zs_ds_val) { + smart_string_free(&ds_names); + smart_string_free(&ds_vals); + return; + } smart_string_appendl(&ds_vals, ZSTR_VAL(zs_ds_val), ZSTR_LEN(zs_ds_val)); zend_string_release(zs_ds_val); } ZEND_HASH_FOREACH_END(); diff --git a/tests/rrd_031.phpt b/tests/rrd_031.phpt new file mode 100644 index 0000000..7defa2d --- /dev/null +++ b/tests/rrd_031.phpt @@ -0,0 +1,39 @@ +--TEST-- +an unconvertible option value aborts before anything is written +--SKIPIF-- + +--FILE-- +update(array("speed" => new NoString)); + echo "update: no throw\n"; +} catch (Throwable $e) { + echo "update: ", get_class($e), "\n"; +} +var_dump(file_exists($file)); +?> +--EXPECT-- +NoString: Error +bool(false) +Throws: RuntimeException +bool(false) +update: Error +bool(false) From 1f493e8f96062479a2b2792e2f1b0ad7970a5894 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 20:55:24 -0700 Subject: [PATCH 11/14] fix: pin the checked path and the options array across userland calls Converting a value runs __toString, which can call __construct to repoint the object past the open_basedir check, or setOptions to free the array being walked. Signed-off-by: Thomas Vincent --- rrd_graph.c | 25 ++++++++++++++++++++----- rrd_update.c | 23 +++++++++++++++++++++-- tests/rrd_032.phpt | 40 ++++++++++++++++++++++++++++++++++++++++ 3 files changed, 81 insertions(+), 7 deletions(-) create mode 100644 tests/rrd_032.phpt diff --git a/rrd_graph.c b/rrd_graph.c index 4e7f17f..75c62cf 100644 --- a/rrd_graph.c +++ b/rrd_graph.c @@ -133,7 +133,8 @@ PHP_METHOD(RRDGraph, setOptions) /* {{{ creates arguments for rrd_graph call for RRDGraph instance options */ -static rrd_args *rrd_graph_obj_create_argv(const char *command_name, const rrd_graph_object *obj) +static rrd_args *rrd_graph_obj_create_argv(const char *command_name, + const rrd_graph_object *obj, const char *file_path) { /* iterated item and keys*/ zval *zv_option_val; @@ -142,10 +143,15 @@ static rrd_args *rrd_graph_obj_create_argv(const char *command_name, const rrd_g /* arguments for rrd_graph call as php array - temporary storage */ zval zv_argv; rrd_args *result; + /* converting a value runs __toString, and setOptions() from there would + * free the array this loop is walking + */ + HashTable *ht = Z_ARRVAL(obj->zv_arr_options); + GC_ADDREF(ht); array_init(&zv_argv); - ZEND_HASH_FOREACH_KEY_VAL(Z_ARRVAL(obj->zv_arr_options), num_key, zs_key, zv_option_val) { + ZEND_HASH_FOREACH_KEY_VAL(ht, num_key, zs_key, zv_option_val) { (void)num_key; /* to avoid -Wunused-but-set-variable */ smart_string option = {0}; /* one argument option */ zend_string *option_str; @@ -163,6 +169,7 @@ static rrd_args *rrd_graph_obj_create_argv(const char *command_name, const rrd_g if (!option_str) { smart_string_free(&option); zval_dtor(&zv_argv); + zend_array_release(ht); return NULL; } @@ -175,7 +182,9 @@ static rrd_args *rrd_graph_obj_create_argv(const char *command_name, const rrd_g smart_string_free(&option); } ZEND_HASH_FOREACH_END(); - result = rrd_args_init_by_phparray(command_name, obj->file_path, &zv_argv); + zend_array_release(ht); + + result = rrd_args_init_by_phparray(command_name, file_path, &zv_argv); zval_dtor(&zv_argv); return result; @@ -196,6 +205,7 @@ PHP_METHOD(RRDGraph, save) /* arguments for rrd_graph call */ rrd_args *graph_argv; + char *checked_path; if (!intern_obj->file_path) { zend_throw_exception(NULL, "the object was not constructed", 0); @@ -211,7 +221,9 @@ PHP_METHOD(RRDGraph, save) RETURN_FALSE; } - graph_argv = rrd_graph_obj_create_argv("graph", intern_obj); + checked_path = estrdup(intern_obj->file_path); + graph_argv = rrd_graph_obj_create_argv("graph", intern_obj, checked_path); + efree(checked_path); if (!graph_argv) { zend_error(E_WARNING, "cannot allocate arguments options"); RETURN_FALSE; @@ -272,6 +284,7 @@ PHP_METHOD(RRDGraph, saveVerbose) /* arguments for rrd_graph call */ rrd_args *graph_argv; + char *checked_path; if (!intern_obj->file_path) { zend_throw_exception(NULL, "the object was not constructed", 0); @@ -287,7 +300,9 @@ PHP_METHOD(RRDGraph, saveVerbose) RETURN_FALSE; } - graph_argv = rrd_graph_obj_create_argv("graphv", intern_obj); + checked_path = estrdup(intern_obj->file_path); + graph_argv = rrd_graph_obj_create_argv("graphv", intern_obj, checked_path); + efree(checked_path); if (!graph_argv) { zend_error(E_WARNING, "cannot allocate arguments options"); RETURN_FALSE; diff --git a/rrd_update.c b/rrd_update.c index 6119630..6dd0136 100644 --- a/rrd_update.c +++ b/rrd_update.c @@ -121,6 +121,11 @@ PHP_METHOD(RRDUpdater, update) int argc = ZEND_NUM_ARGS(); zend_string *zs_ds_name, *zs_ds_val; zval *zv_ds_val; + HashTable *values_ht; + /* __construct is callable again, so a __toString below could repoint the + * object after the open_basedir check; work from a snapshot instead + */ + char *checked_path; /* string for all data source names formated for rrd_update call */ smart_string ds_names = {0}; @@ -146,16 +151,24 @@ PHP_METHOD(RRDUpdater, update) if (php_check_open_basedir(intern_obj->file_path)) { RETURN_FALSE; } + checked_path = estrdup(intern_obj->file_path); if (argc > 1 && time_str_length == 0) { + efree(checked_path); zend_throw_exception(NULL, "time cannot be empty string", 0); return; } - ZEND_HASH_FOREACH_STR_KEY_VAL(Z_ARRVAL_P(zv_values_array), zs_ds_name, zv_ds_val) { + /* converting a value runs __toString, which must not free this array */ + values_ht = Z_ARRVAL_P(zv_values_array); + GC_ADDREF(values_ht); + + ZEND_HASH_FOREACH_STR_KEY_VAL(values_ht, zs_ds_name, zv_ds_val) { if (!zs_ds_name) { smart_string_free(&ds_names); smart_string_free(&ds_vals); + zend_array_release(values_ht); + efree(checked_path); zend_throw_exception(NULL, "values array must be keyed by data source name", 0); return; @@ -178,11 +191,16 @@ PHP_METHOD(RRDUpdater, update) if (!zs_ds_val) { smart_string_free(&ds_names); smart_string_free(&ds_vals); + zend_array_release(values_ht); + efree(checked_path); return; } smart_string_appendl(&ds_vals, ZSTR_VAL(zs_ds_val), ZSTR_LEN(zs_ds_val)); zend_string_release(zs_ds_val); } ZEND_HASH_FOREACH_END(); + + zend_array_release(values_ht); + smart_string_0(&ds_names); smart_string_0(&ds_vals); @@ -195,7 +213,8 @@ PHP_METHOD(RRDUpdater, update) smart_string_free(&ds_names); smart_string_free(&ds_vals); - update_argv = rrd_args_init_by_phparray("update", intern_obj->file_path, &zv_update_argv); + update_argv = rrd_args_init_by_phparray("update", checked_path, &zv_update_argv); + efree(checked_path); if (!update_argv) { zend_error(E_WARNING, "cannot allocate arguments options"); zval_dtor(&zv_update_argv); diff --git a/tests/rrd_032.phpt b/tests/rrd_032.phpt new file mode 100644 index 0000000..5bcb60c --- /dev/null +++ b/tests/rrd_032.phpt @@ -0,0 +1,40 @@ +--TEST-- +__toString() cannot repoint the object after the path has been checked +--SKIPIF-- + +--FILE-- +__construct(self::$target); + return "1"; + } +} + +$dir = dirname(__FILE__); +$intended = "$dir/toctou-intended.rrd"; +$other = "$dir/toctou-other.rrd"; +@unlink($intended); @unlink($other); + +foreach (array($intended, $other) as $f) { + rrd_create($f, array("--start", "920804400", "--step", "300", + "DS:speed:GAUGE:600:U:U", "RRA:AVERAGE:0.5:1:24")); +} + +$updater = new RRDUpdater($intended); +Repoint::$obj = $updater; +Repoint::$target = $other; + +try { $updater->update(array("speed" => new Repoint), "920804700"); } +catch (Throwable $e) { echo "caught\n"; } + +/* the write must land on the path that was checked, not the repointed one */ +var_dump(rrd_last($intended), rrd_last($other)); + +@unlink($intended); @unlink($other); +?> +--EXPECT-- +int(920804700) +int(920804400) From 657b1f39179a3167b386c08bc91648c5c3d46eb7 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 20:55:25 -0700 Subject: [PATCH 12/14] fix: hold a reference to the graph options while building the argument list Signed-off-by: Thomas Vincent --- tests/rrd_033.phpt | 35 +++++++++++++++++++++++++++++++++++ 1 file changed, 35 insertions(+) create mode 100644 tests/rrd_033.phpt diff --git a/tests/rrd_033.phpt b/tests/rrd_033.phpt new file mode 100644 index 0000000..e0d5831 --- /dev/null +++ b/tests/rrd_033.phpt @@ -0,0 +1,35 @@ +--TEST-- +__toString() freeing the options array while it is being walked +--SKIPIF-- + +--FILE-- +setOptions(array("--start" => "920804400")); + return "920804400"; + } +} +class Stop { + /* throws once the walk has moved past the freed buckets, so librrd is + never reached and the test does not depend on a renderer */ + public function __toString(): string { throw new RuntimeException("stop"); } +} + +$graph = new RRDGraph(dirname(__FILE__) . "/reenter.png"); +Reenter::$graph = $graph; + +$options = array("--start" => new Reenter); +for ($i = 0; $i < 64; $i++) { $options["--pad$i"] = str_repeat("A", 64); } +$options["--end"] = new Stop; +$graph->setOptions($options); + +try { $graph->save(); } catch (Throwable $e) { echo get_class($e), "\n"; } +echo "survived\n"; +?> +--EXPECTF-- +Warning: cannot allocate arguments options in %s on line %d +RuntimeException +survived From ef583761163966f37d0b05055111096660ff3f17 Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 20:55:25 -0700 Subject: [PATCH 13/14] fix: clear file_path on release and stop walking past the fetch result The "was not constructed" guards test for NULL, and rrd_fetch keyed its per-source arrays by name, so duplicate names left the walk short of ds_cnt entries. Signed-off-by: Thomas Vincent --- rrd.c | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/rrd.c b/rrd.c index 7ce564f..bc80196 100644 --- a/rrd.c +++ b/rrd.c @@ -131,8 +131,9 @@ PHP_FUNCTION(rrd_fetch) snprintf(str_timestamp, sizeof(str_timestamp), "%" PRId64, (int64_t)timestamp); - /* gets pointer for data source result array */ + /* gets pointer for data source result array */ ds_data_array = zend_hash_get_current_data(Z_ARRVAL(zv_data_array)); + if (!ds_data_array) break; add_assoc_double(ds_data_array, str_timestamp, *(datap++)); zend_hash_move_forward(Z_ARRVAL(zv_data_array)); From d9dc9cc3dd70dc680b8cf2407e9e35e67833845f Mon Sep 17 00:00:00 2001 From: Thomas Vincent Date: Thu, 20 Aug 2026 21:10:45 -0700 Subject: [PATCH 14/14] tests: cover duplicate data source names, re-construction and unbuilt objects The duplicate-name case is a crash on master: renaming one ds_nam over another in the header leaves rrd_fetch walking past the end of the array it built. Signed-off-by: Thomas Vincent --- tests/rrd_034.phpt | 33 +++++++++++++++++++++++++++++++++ tests/rrd_035.phpt | 39 +++++++++++++++++++++++++++++++++++++++ tests/rrd_036.phpt | 29 +++++++++++++++++++++++++++++ 3 files changed, 101 insertions(+) create mode 100644 tests/rrd_034.phpt create mode 100644 tests/rrd_035.phpt create mode 100644 tests/rrd_036.phpt diff --git a/tests/rrd_034.phpt b/tests/rrd_034.phpt new file mode 100644 index 0000000..f4d7f8f --- /dev/null +++ b/tests/rrd_034.phpt @@ -0,0 +1,33 @@ +--TEST-- +rrd_fetch on a file whose header repeats a data source name +--SKIPIF-- + +--FILE-- + +--EXPECT-- +bool(true) +array(1) { + [0]=> + string(6) "speed1" +} +survived diff --git a/tests/rrd_035.phpt b/tests/rrd_035.phpt new file mode 100644 index 0000000..d54c6f7 --- /dev/null +++ b/tests/rrd_035.phpt @@ -0,0 +1,39 @@ +--TEST-- +re-constructing RRDGraph and RRDUpdater replaces the previous path +--SKIPIF-- + +--FILE-- +__construct($second); +$updater->update(array("speed" => 7), "920804700"); +var_dump(rrd_last($first), rrd_last($second)); + +/* RRDGraph: open_basedir is checked before librrd is called at all, so a + re-construction pointing outside it proves the path was replaced without + rendering anything */ +$graph = new RRDGraph("$dir/reconstruct.png"); +$graph->setOptions(array("--start" => "920804400")); +$graph->__construct(sys_get_temp_dir() . "/reconstruct-outside.png"); +ini_set("open_basedir", $dir . DIRECTORY_SEPARATOR); +var_dump($graph->save()); + +@unlink($first); @unlink($second); +?> +--EXPECTF-- +int(920804400) +int(920804700) + +Warning: RRDGraph::save(): open_basedir restriction in effect. File(%sreconstruct-outside.png) is not within the allowed path(s): (%s) in %s on line %d +bool(false) diff --git a/tests/rrd_036.phpt b/tests/rrd_036.phpt new file mode 100644 index 0000000..5bff939 --- /dev/null +++ b/tests/rrd_036.phpt @@ -0,0 +1,29 @@ +--TEST-- +methods on an object whose constructor threw +--SKIPIF-- + +--FILE-- +getMessage(), "\n"; +} + +$creator = (new ReflectionClass("RRDCreator"))->newInstanceWithoutConstructor(); +try { $creator->__construct(""); } catch (Exception $e) { } +try { $creator->save(); } catch (Exception $e) { echo "save: ", $e->getMessage(), "\n"; } + +$updater = (new ReflectionClass("RRDUpdater"))->newInstanceWithoutConstructor(); +try { $updater->update(array("speed" => 1)); } catch (Exception $e) { echo "update: ", $e->getMessage(), "\n"; } + +$graph = (new ReflectionClass("RRDGraph"))->newInstanceWithoutConstructor(); +$graph->setOptions(array("--start" => "920804400")); +try { $graph->saveVerbose(); } catch (Exception $e) { echo "saveVerbose: ", $e->getMessage(), "\n"; } +?> +--EXPECT-- +construct: path for rrd file cannot be empty string +save: the object was not constructed +update: the object was not constructed +saveVerbose: the object was not constructed