From b260b62554ee1b77e4b49a89a6efac56b101c8f1 Mon Sep 17 00:00:00 2001 From: arshidkv12 Date: Sun, 14 Jun 2026 16:52:40 +0530 Subject: [PATCH] Add validation for file permission values across INI and filesystem APIs --- Zend/zend_permissions.c | 22 +++++++++ Zend/zend_permissions.h | 16 ++++++ configure.ac | 1 + ext/dba/dba.c | 7 ++- ext/dba/tests/dba_permission.phpt | 49 +++++++++++++++++++ ext/ftp/php_ftp.c | 6 +++ ext/standard/file.c | 5 ++ ext/standard/filestat.c | 5 ++ ext/standard/tests/file/006_variation2.phpt | 40 ++++++--------- ext/standard/tests/file/copy_variation15.phpt | 7 ++- main/main.c | 19 ++++++- win32/build/config.w32 | 2 +- 12 files changed, 149 insertions(+), 30 deletions(-) create mode 100644 Zend/zend_permissions.c create mode 100644 Zend/zend_permissions.h create mode 100644 ext/dba/tests/dba_permission.phpt diff --git a/Zend/zend_permissions.c b/Zend/zend_permissions.c new file mode 100644 index 000000000000..3b587a710c8e --- /dev/null +++ b/Zend/zend_permissions.c @@ -0,0 +1,22 @@ +#include "zend.h" +#include "zend_API.h" +#include "zend_exceptions.h" +#include "Zend/zend_permissions.h" + +ZEND_API zend_result zend_validate_file_permission( + zend_long permission, + zend_long arg_num, + const char *name +) +{ + if (permission < 0 || (permission & ~07777) != 0) { + zend_argument_value_error( + arg_num, + "Invalid %s value (must be between 0 and 07777)", + name + ); + return FAILURE; + } + + return SUCCESS; +} \ No newline at end of file diff --git a/Zend/zend_permissions.h b/Zend/zend_permissions.h new file mode 100644 index 000000000000..5fe2a933542b --- /dev/null +++ b/Zend/zend_permissions.h @@ -0,0 +1,16 @@ +#ifndef ZEND_PERMISSIONS_H +#define ZEND_PERMISSIONS_H + +#include "zend.h" + +BEGIN_EXTERN_C() + +ZEND_API zend_result zend_validate_file_permission( + zend_long permission, + zend_long arg_num, + const char *name +); + +END_EXTERN_C() + +#endif /* ZEND_PERMISSIONS_H */ \ No newline at end of file diff --git a/configure.ac b/configure.ac index 7b1fc3af7847..0ab44a530c72 100644 --- a/configure.ac +++ b/configure.ac @@ -1760,6 +1760,7 @@ PHP_ADD_SOURCES([Zend], m4_normalize([ zend_ini_parser.c zend_ini_scanner.c zend_ini.c + zend_permissions.c zend_interfaces.c zend_iterators.c zend_language_parser.c diff --git a/ext/dba/dba.c b/ext/dba/dba.c index c0688714fe7c..10cf3e979b52 100644 --- a/ext/dba/dba.c +++ b/ext/dba/dba.c @@ -22,6 +22,7 @@ #ifdef HAVE_DBA #include "php_ini.h" +#include "Zend/zend_permissions.h" #include #include #ifdef HAVE_SYS_FILE_H @@ -563,7 +564,11 @@ static void php_dba_open(INTERNAL_FUNCTION_PARAMETERS, bool persistent) zend_argument_must_not_be_empty_error(3); RETURN_THROWS(); } - // TODO Check Value for permission + + if (zend_validate_file_permission(permission, 4, "file permission") == FAILURE) { + RETURN_THROWS(); + } + if (map_size < 0) { zend_argument_value_error(5, "must be greater than or equal to 0"); RETURN_THROWS(); diff --git a/ext/dba/tests/dba_permission.phpt b/ext/dba/tests/dba_permission.phpt new file mode 100644 index 000000000000..6432b320cf06 --- /dev/null +++ b/ext/dba/tests/dba_permission.phpt @@ -0,0 +1,49 @@ +--TEST-- +DBA permission validation (invalid bits check) +--EXTENSIONS-- +dba +--SKIPIF-- + +--FILE-- +getMessage() . PHP_EOL; + } + + @unlink($filename); +} + +/* valid permissions */ +test(0777, $filename); +test(0755, $filename); +test(0644, $filename); +test(0000, $filename); + +/* invalid permissions */ +test(010000, $filename); +test(020000, $filename); +test(-1, $filename); + +?> +--EXPECT-- +OK +OK +OK +OK +dba_open(): Argument #4 ($permission) Invalid file permission value (must be between 0 and 07777) +dba_open(): Argument #4 ($permission) Invalid file permission value (must be between 0 and 07777) +dba_open(): Argument #4 ($permission) Invalid file permission value (must be between 0 and 07777) +--CLEAN-- + \ No newline at end of file diff --git a/ext/ftp/php_ftp.c b/ext/ftp/php_ftp.c index 501ce9bc68d7..4bfe05f7cb4b 100644 --- a/ext/ftp/php_ftp.c +++ b/ext/ftp/php_ftp.c @@ -29,6 +29,8 @@ #include "ext/standard/file.h" #include "Zend/zend_attributes.h" #include "Zend/zend_exceptions.h" +#include "Zend/zend_permissions.h" + #include "php_ftp.h" #include "ftp.h" @@ -419,6 +421,10 @@ PHP_FUNCTION(ftp_chmod) } GET_FTPBUF(ftp, z_ftp); + if (zend_validate_file_permission(mode, 2, "mode") == FAILURE) { + RETURN_THROWS(); + } + if (!ftp_chmod(ftp, mode, filename, filename_len)) { if (*ftp->inbuf) { php_error_docref(NULL, E_WARNING, "%s", ftp->inbuf); diff --git a/ext/standard/file.c b/ext/standard/file.c index db0fc45385dd..72863ba7f173 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -26,6 +26,7 @@ #include "ext/standard/basic_functions.h" #include "php_ini.h" #include "zend_smart_str.h" +#include "zend_permissions.h" #include #include @@ -1131,6 +1132,10 @@ PHP_FUNCTION(mkdir) Z_PARAM_RESOURCE_OR_NULL(zcontext) ZEND_PARSE_PARAMETERS_END(); + if (zend_validate_file_permission(mode, 4, "mode") == FAILURE) { + RETURN_THROWS(); + } + context = php_stream_context_from_zval(zcontext, 0); php_stream_error_operation_begin(); diff --git a/ext/standard/filestat.c b/ext/standard/filestat.c index e41f6d1abb61..a44d8e7e458a 100644 --- a/ext/standard/filestat.c +++ b/ext/standard/filestat.c @@ -15,6 +15,7 @@ #include "php.h" #include "fopen_wrappers.h" #include "php_globals.h" +#include "Zend/zend_permissions.h" #include #include @@ -569,6 +570,10 @@ PHP_FUNCTION(chmod) Z_PARAM_LONG(mode) ZEND_PARSE_PARAMETERS_END(); + if (zend_validate_file_permission(mode, 2, "mode") == FAILURE) { + RETURN_THROWS(); + } + wrapper = php_stream_locate_url_wrapper(filename, NULL, 0); if(wrapper != &php_plain_files_wrapper || strncasecmp("file://", filename, 7) == 0) { if(wrapper && wrapper->wops->stream_metadata) { diff --git a/ext/standard/tests/file/006_variation2.phpt b/ext/standard/tests/file/006_variation2.phpt index 6b32f2246f12..9ebf9ecab6ef 100644 --- a/ext/standard/tests/file/006_variation2.phpt +++ b/ext/standard/tests/file/006_variation2.phpt @@ -54,7 +54,7 @@ foreach($perms_array as $permission) { printf("%o", fileperms($file_name) ); echo "\n"; clearstatcache(); - } catch (TypeError $e) { + } catch (TypeError|ValueError $e) { echo $e->getMessage(), "\n"; } @@ -63,7 +63,7 @@ foreach($perms_array as $permission) { printf("%o", fileperms($dir_name) ); echo "\n"; clearstatcache(); - } catch (TypeError $e) { + } catch (TypeError|ValueError $e) { echo $e->getMessage(), "\n"; } $count++; @@ -97,45 +97,33 @@ bool(true) bool(true) 41000 -- Iteration 4 -- -bool(true) -101111 -bool(true) -41111 +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) -- Iteration 5 -- -bool(true) -107001 -bool(true) -47001 +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) -- Iteration 6 -- -bool(true) -100001 -bool(true) -40001 +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) -- Iteration 7 -- bool(true) 101411 bool(true) 41411 -- Iteration 8 -- -bool(true) -107141 -bool(true) -47141 +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) -- Iteration 9 -- -bool(true) -100637 -bool(true) -40637 +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) -- Iteration 10 -- bool(true) 103567 bool(true) 43567 -- Iteration 11 -- -bool(true) -103567 -bool(true) -43567 +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) -- Iteration 12 -- chmod(): Argument #2 ($permissions) must be of type int, string given chmod(): Argument #2 ($permissions) must be of type int, string given diff --git a/ext/standard/tests/file/copy_variation15.phpt b/ext/standard/tests/file/copy_variation15.phpt index abe425a4e323..13c65a406b6f 100644 --- a/ext/standard/tests/file/copy_variation15.phpt +++ b/ext/standard/tests/file/copy_variation15.phpt @@ -31,7 +31,11 @@ var_dump( copy($file, $dir."/copy_copy_variation15.tmp") ); var_dump( file_exists($dir."/copy_copy_variation15_dir.tmp") ); var_dump( filesize($file) ); //size of source -chmod($dir, $old_perms); +try { + chmod($dir, $old_perms); +} catch (TypeError|ValueError $e) { + echo $e->getMessage(), "\n"; +} echo "*** Done ***\n"; ?> @@ -46,4 +50,5 @@ Warning: copy(%s): %s bool(false) bool(false) int(300) +chmod(): Argument #2 ($permissions) Invalid mode value (must be between 0 and 07777) *** Done *** diff --git a/main/main.c b/main/main.c index 6bda55ac8746..adc3249cf5d5 100644 --- a/main/main.c +++ b/main/main.c @@ -42,6 +42,7 @@ #include "zend.h" #include "zend_types.h" #include "zend_extensions.h" +#include "zend_permissions.h" #include "php_ini.h" #include "php_globals.h" #include "php_main.h" @@ -772,6 +773,22 @@ static PHP_INI_MH(OnChangeMailForceExtra) } /* }}} */ +/* {{{ PHP_INI_MH */ +static PHP_INI_MH(OnUpdateFilePermission) +{ + zend_long value = zend_ini_parse_quantity_warn(new_value, entry->name); + + if (zend_validate_file_permission(value, 1, ZSTR_VAL(entry->name)) == FAILURE) { + return FAILURE; + } + + zend_long *p = ZEND_INI_GET_ADDR(); + *p = value; + + return SUCCESS; +} +/* }}} */ + /* defined in browscap.c */ PHP_INI_MH(OnChangeBrowscap); @@ -837,7 +854,7 @@ PHP_INI_BEGIN() STD_PHP_INI_ENTRY("input_encoding", NULL, PHP_INI_ALL, OnUpdateInputEncoding, input_encoding, php_core_globals, core_globals) STD_PHP_INI_ENTRY("output_encoding", NULL, PHP_INI_ALL, OnUpdateOutputEncoding, output_encoding, php_core_globals, core_globals) STD_PHP_INI_ENTRY("error_log", NULL, PHP_INI_ALL, OnUpdateErrorLog, error_log, php_core_globals, core_globals) - STD_PHP_INI_ENTRY("error_log_mode", "0644", PHP_INI_ALL, OnUpdateLong, error_log_mode, php_core_globals, core_globals) + STD_PHP_INI_ENTRY("error_log_mode", "0644", PHP_INI_ALL, OnUpdateFilePermission, error_log_mode, php_core_globals, core_globals) STD_PHP_INI_ENTRY("extension_dir", PHP_EXTENSION_DIR, PHP_INI_SYSTEM, OnUpdateStringUnempty, extension_dir, php_core_globals, core_globals) STD_PHP_INI_ENTRY("sys_temp_dir", NULL, PHP_INI_SYSTEM, OnUpdateStringUnempty, sys_temp_dir, php_core_globals, core_globals) STD_PHP_INI_ENTRY("include_path", PHP_INCLUDE_PATH, PHP_INI_ALL, OnUpdateStringUnempty, include_path, php_core_globals, core_globals) diff --git a/win32/build/config.w32 b/win32/build/config.w32 index 6cd6907f2825..de9cd6ea0d17 100644 --- a/win32/build/config.w32 +++ b/win32/build/config.w32 @@ -234,7 +234,7 @@ ADD_SOURCES("Zend", "zend_language_parser.c zend_language_scanner.c \ zend_llist.c zend_vm_opcodes.c zend_opcode.c zend_operators.c zend_ptr_stack.c \ zend_stack.c zend_variables.c zend.c zend_API.c zend_extensions.c \ zend_hash.c zend_list.c zend_builtin_functions.c zend_attributes.c \ - zend_ini.c zend_sort.c zend_multibyte.c \ + zend_ini.c zend_sort.c zend_multibyte.c zend_permissions.c \ zend_stream.c zend_iterators.c zend_interfaces.c zend_objects.c \ zend_object_handlers.c zend_objects_API.c \ zend_default_classes.c zend_execute.c zend_strtod.c zend_gc.c zend_closures.c zend_weakrefs.c \