diff --git a/ext/standard/dir.c b/ext/standard/dir.c index ae6acdce5db..927bf958d70 100644 --- a/ext/standard/dir.c +++ b/ext/standard/dir.c @@ -282,7 +282,7 @@ PHP_FUNCTION(chdir) } convert_to_string_ex(arg); - if (PG(safe_mode) && !php_checkuid((*arg)->value.str.val, NULL, CHECKUID_ALLOW_ONLY_DIR)) { + if (PG(safe_mode) && !php_checkuid((*arg)->value.str.val, NULL, CHECKUID_ALLOW_ONLY_FILE)) { RETURN_FALSE; } ret = VCWD_CHDIR((*arg)->value.str.val); diff --git a/main/fopen_wrappers.c b/main/fopen_wrappers.c index 721ac0b554f..e45e52b97e5 100644 --- a/main/fopen_wrappers.c +++ b/main/fopen_wrappers.c @@ -389,20 +389,64 @@ PHPAPI FILE *php_fopen_primary_script(void) PHPAPI FILE *php_fopen_with_path(char *filename, char *mode, char *path, char **opened_path) { char *pathbuf, *ptr, *end; + char *exec_fname; char trypath[MAXPATHLEN]; + char trydir[MAXPATHLEN]; + char safe_mode_include_dir[MAXPATHLEN]; struct stat sb; FILE *fp; + int path_length; int filename_length; + int safe_mode_include_dir_length; + int exec_fname_length; PLS_FETCH(); + ELS_FETCH(); if (opened_path) { *opened_path = NULL; } + + if(!filename) { + return NULL; + } filename_length = strlen(filename); - - /* Absolute & relative path open */ - if ((*filename == '.') || (IS_ABSOLUTE_PATH(filename, filename_length))) { + + /* Relative path open */ + if (*filename == '.') { + if (PG(safe_mode) && (!php_checkuid(filename, mode, CHECKUID_CHECK_MODE_PARAM))) { + return NULL; + } + return php_fopen_and_set_opened_path(filename, mode, opened_path); + } + + /* + * files in safe_mode_include_dir (or subdir) are excluded from + * safe mode GID/UID checks + */ + *safe_mode_include_dir = 0; + safe_mode_include_dir_length = 0; + if(PG(safe_mode_include_dir) && VCWD_REALPATH(PG(safe_mode_include_dir), safe_mode_include_dir)) { + safe_mode_include_dir_length = strlen(safe_mode_include_dir); + } + + /* Absolute path open */ + if (IS_ABSOLUTE_PATH(filename, filename_length)) { + /* Check to see if file is in safe_mode_include_dir (or subdir) */ + if (PG(safe_mode) && *safe_mode_include_dir && VCWD_REALPATH(filename, trypath)) { +#ifdef PHP_WIN32 + if (strncasecmp(safe_mode_include_dir, trypath, safe_mode_include_dir_length) == 0) +#else + if (strncmp(safe_mode_include_dir, trypath, safe_mode_include_dir_length) == 0) +#endif + { + /* absolute path matches safe_mode_include_dir */ + fp = php_fopen_and_set_opened_path(trypath, mode, opened_path); + if (fp) { + return fp; + } + } + } if (PG(safe_mode) && (!php_checkuid(filename, mode, CHECKUID_CHECK_MODE_PARAM))) { return NULL; } @@ -415,7 +459,31 @@ PHPAPI FILE *php_fopen_with_path(char *filename, char *mode, char *path, char ** } return php_fopen_and_set_opened_path(filename, mode, opened_path); } - pathbuf = estrdup(path); + + /* check in provided path */ + /* append the calling scripts' current working directory + * as a fall back case + */ + exec_fname = zend_get_executed_filename(ELS_C); + exec_fname_length = strlen(exec_fname); + path_length = strlen(path); + + while ((--exec_fname_length >= 0) && !IS_SLASH(exec_fname[exec_fname_length])) { + } + if (exec_fname && exec_fname[0] == '[') { + /* [no active file] */ + exec_fname_length = 0; + } + + pathbuf = (char *) emalloc(exec_fname_length + path_length +1 +1); + memcpy(pathbuf, path, path_length); +#ifdef PHP_WIN32 + pathbuf[path_length] = ';'; +#else + pathbuf[path_length] = ':'; +#endif + memcpy(pathbuf+path_length+1, exec_fname, exec_fname_length); + pathbuf[path_length + exec_fname_length +1] = '\0'; ptr = pathbuf; @@ -430,6 +498,22 @@ PHPAPI FILE *php_fopen_with_path(char *filename, char *mode, char *path, char ** end++; } snprintf(trypath, MAXPATHLEN, "%s/%s", ptr, filename); + /* Check to see trypath is in safe_mode_include_dir (or subdir) */ + if (PG(safe_mode) && *safe_mode_include_dir && VCWD_REALPATH(trypath, trydir)) { +#ifdef PHP_WIN32 + if (strncasecmp(safe_mode_include_dir, trydir, safe_mode_include_dir_length) == 0) +#else + if (strncmp(safe_mode_include_dir, trydir, safe_mode_include_dir_length) == 0) +#endif + { + /* trypath is in safe_mode_include_dir */ + fp = php_fopen_and_set_opened_path(trydir, mode, opened_path); + if (fp) { + efree(pathbuf); + return fp; + } + } + } if (PG(safe_mode)) { if (VCWD_STAT(trypath, &sb) == 0 && (!php_checkuid(trypath, mode, CHECKUID_CHECK_MODE_PARAM))) { efree(pathbuf); @@ -442,37 +526,10 @@ PHPAPI FILE *php_fopen_with_path(char *filename, char *mode, char *path, char ** return fp; } ptr = end; - } + } /* end provided path */ efree(pathbuf); - - { - char *exec_fname; - int exec_fname_len; - ELS_FETCH(); - - exec_fname = zend_get_executed_filename(ELS_C); - exec_fname_len = strlen(exec_fname); - - pathbuf = (char *) emalloc(exec_fname_len+filename_length+1+1); /* Over allocate to save time */ - memcpy(pathbuf, exec_fname, exec_fname_len+1); - - while ((--exec_fname_len >= 0) && !IS_SLASH(pathbuf[exec_fname_len])) { - } - pathbuf[exec_fname_len] = DEFAULT_SLASH; - memcpy(&pathbuf[exec_fname_len+1], filename, filename_length+1); - - if (PG(safe_mode)) { - if (VCWD_STAT(pathbuf, &sb) == 0 && (!php_checkuid(pathbuf, mode, CHECKUID_CHECK_MODE_PARAM))) { - efree(pathbuf); - return NULL; - } - } - fp = php_fopen_and_set_opened_path(pathbuf, mode, opened_path); - efree(pathbuf); - return fp; - } - return NULL; /* Not really needed anymore */ + return NULL; } /* }}} */ diff --git a/main/main.c b/main/main.c index 57dabfa8525..2c3810cc313 100644 --- a/main/main.c +++ b/main/main.c @@ -215,6 +215,7 @@ PHP_INI_BEGIN() STD_PHP_INI_BOOLEAN("register_argc_argv", "1", PHP_INI_ALL, OnUpdateBool, register_argc_argv, php_core_globals, core_globals) STD_PHP_INI_BOOLEAN("register_globals", "1", PHP_INI_ALL, OnUpdateBool, register_globals, php_core_globals, core_globals) STD_PHP_INI_BOOLEAN("safe_mode", "0", PHP_INI_SYSTEM, OnUpdateBool, safe_mode, php_core_globals, core_globals) + STD_PHP_INI_ENTRY("safe_mode_include_dir", NULL, PHP_INI_SYSTEM, OnUpdateString, safe_mode_include_dir, php_core_globals, core_globals) STD_PHP_INI_BOOLEAN("safe_mode_gid", "0", PHP_INI_SYSTEM, OnUpdateBool, safe_mode_gid, php_core_globals, core_globals) STD_PHP_INI_BOOLEAN("short_open_tag",DEFAULT_SHORT_OPEN_TAG, PHP_INI_SYSTEM|PHP_INI_PERDIR, OnUpdateBool, short_tags, zend_compiler_globals, compiler_globals) STD_PHP_INI_BOOLEAN("sql.safe_mode", "0", PHP_INI_SYSTEM, OnUpdateBool, sql_safe_mode, php_core_globals, core_globals) diff --git a/main/php_globals.h b/main/php_globals.h index f426b9ed89f..56de68d7e1a 100644 --- a/main/php_globals.h +++ b/main/php_globals.h @@ -68,6 +68,7 @@ struct _php_core_globals { zend_bool implicit_flush; zend_bool safe_mode; + char *safe_mode_include_dir; zend_bool safe_mode_gid; zend_bool sql_safe_mode; zend_bool enable_dl; diff --git a/main/safe_mode.c b/main/safe_mode.c index e251d37b260..f20fe516c4b 100644 --- a/main/safe_mode.c +++ b/main/safe_mode.c @@ -48,6 +48,7 @@ PHPAPI int php_checkuid(const char *filename, char *fopen_mode, int mode) struct stat sb; int ret; long uid=0L, gid=0L, duid=0L, dgid=0L; + char path[MAXPATHLEN]; char *s; PLS_FETCH(); @@ -71,8 +72,12 @@ PHPAPI int php_checkuid(const char *filename, char *fopen_mode, int mode) return 1; } + /* First we see if the file is owned by the same user... + * If that fails, passthrough and check directory... + */ if (mode != CHECKUID_ALLOW_ONLY_DIR) { - ret = VCWD_STAT(filename, &sb); + VCWD_REALPATH(filename, path); + ret = VCWD_STAT(path, &sb); if (ret < 0) { if (mode == CHECKUID_DISALLOW_FILE_NOT_EXISTS) { php_error(E_WARNING, "Unable to access %s", filename); @@ -83,63 +88,67 @@ PHPAPI int php_checkuid(const char *filename, char *fopen_mode, int mode) } } else { uid = sb.st_uid; + gid = sb.st_gid; if (uid == php_getuid()) { return 1; + } else if (PG(safe_mode_gid) && gid == php_getgid()) { + return 1; } } - } - s = strrchr(filename,'/'); - /* This loop gets rid of trailing slashes which could otherwise be - * used to confuse the function. - */ - while(s && *(s+1)=='\0' && s>filename) { - *s='\0'; - s = strrchr(filename,'/'); - } + /* Trim off filename */ + if (s = strrchr(path,DEFAULT_SLASH)) { + *s = '\0'; + } + } else { /* CHECKUID_ALLOW_ONLY_DIR */ + s = strrchr(filename,DEFAULT_SLASH); - if (s) { - *s='\0'; - ret = VCWD_STAT(filename, &sb); - *s='/'; + if (s) { + *s = '\0'; + VCWD_REALPATH(filename, path); + *s = DEFAULT_SLASH; + } else { + VCWD_GETCWD(path, MAXPATHLEN); + } + } /* end CHECKUID_ALLOW_ONLY_DIR */ + + if (mode != CHECKUID_ALLOW_ONLY_FILE) { + /* check directory */ + ret = VCWD_STAT(path, &sb); if (ret < 0) { php_error(E_WARNING, "Unable to access %s", filename); return 0; } duid = sb.st_uid; - } else { - char cwd[MAXPATHLEN]; - if (!VCWD_GETCWD(cwd, MAXPATHLEN)) { - php_error(E_WARNING, "Unable to access current working directory"); - return 0; - } - ret = VCWD_STAT(cwd, &sb); - if (ret < 0) { - php_error(E_WARNING, "Unable to access %s", cwd); - return 0; - } - duid = sb.st_uid; - } - if (duid == (uid=php_getuid())) { - return 1; - } else if (PG(safe_mode_gid) && dgid == (gid=php_getgid())) { - return 1; - } else { - SLS_FETCH(); + dgid = sb.st_gid; + if (duid == php_getuid()) { + return 1; + } else if (PG(safe_mode_gid) && dgid == php_getgid()) { + return 1; + } else { + SLS_FETCH(); - if (SG(rfc1867_uploaded_files)) { - if (zend_hash_exists(SG(rfc1867_uploaded_files), (char *) filename, strlen(filename)+1)) { - return 1; + if (SG(rfc1867_uploaded_files)) { + if (zend_hash_exists(SG(rfc1867_uploaded_files), (char *) filename, strlen(filename)+1)) { + return 1; + } } } - - if (PG(safe_mode_gid)) { - php_error(E_WARNING, "SAFE MODE Restriction in effect. The script whose uid/gid is %ld/%ld is not allowed to access %s owned by uid/gid %ld/%ld", uid, gid, filename, duid, dgid); - } else { - php_error(E_WARNING, "SAFE MODE Restriction in effect. The script whose uid is %ld is not allowed to access %s owned by uid %ld", uid, filename, duid); - } - return 0; } + + if (mode == CHECKUID_ALLOW_ONLY_DIR) { + uid = duid; + gid = dgid; + if (s) { + *s = 0; + } + } + if (PG(safe_mode_gid)) { + php_error(E_WARNING, "SAFE MODE Restriction in effect. The script whose uid/gid is %ld/%ld is not allowed to access %s owned by uid/gid %ld/%ld", php_getuid(), php_getgid(), filename, uid, gid); + } else { + php_error(E_WARNING, "SAFE MODE Restriction in effect. The script whose uid is %ld is not allowed to access %s owned by uid %ld", php_getuid(), filename, uid); + } + return 0; } diff --git a/main/safe_mode.h b/main/safe_mode.h index 9f012689f4b..307c5570784 100644 --- a/main/safe_mode.h +++ b/main/safe_mode.h @@ -7,6 +7,7 @@ #define CHECKUID_CHECK_FILE_AND_DIR 2 #define CHECKUID_ALLOW_ONLY_DIR 3 #define CHECKUID_CHECK_MODE_PARAM 4 +#define CHECKUID_ALLOW_ONLY_FILE 5 extern PHPAPI int php_checkuid(const char *filename, char *fopen_mode, int mode); extern PHPAPI char *php_get_current_user(void); diff --git a/php.ini-dist b/php.ini-dist index 325937c2b9d..9d61b913f1f 100644 --- a/php.ini-dist +++ b/php.ini-dist @@ -116,6 +116,12 @@ safe_mode = Off ; then turn on safe_mode_gid. safe_mode_gid = Off +; When safe_mode is on, UID/GID checks are bypassed when +; including files from this directory and its subdirectories. +; (directory must also be in include_path or full path must +; be used when including) +safe_mode_include_dir = + ; When safe_mode is on, only executables located in the safe_mode_exec_dir ; will be allowed to be executed via the exec family of functions. safe_mode_exec_dir = diff --git a/php.ini-optimized b/php.ini-optimized index e20205c0574..e9698521b7a 100644 --- a/php.ini-optimized +++ b/php.ini-optimized @@ -84,7 +84,12 @@ safe_mode = Off safe_mode_gid = Off ; By default, Safe Mode does a UID compare ; check when opening files. If you want to ; relax this to a GID compare, then turn on - ; safe_mode_gid. + ; safe_mode_gid. (safe_mode must be On) +safe_mode_include_dir = ; When safe_mode is on, UID/GID checks are + ; bypassed when including files from this + ; directory and its subdirectories. (directory + ; must also be in include_path or full path + ; must be used when including) safe_mode_exec_dir = safe_mode_allowed_env_vars = PHP_ ; Setting certain environment variables ; may be a potential security breach. @@ -532,4 +537,4 @@ sockets.use_system_read = Off ; Use the system read() function instead of ; Local Variables: ; tab-width: 4 -; End: \ No newline at end of file +; End: diff --git a/php.ini-recommended b/php.ini-recommended index e20205c0574..e9698521b7a 100644 --- a/php.ini-recommended +++ b/php.ini-recommended @@ -84,7 +84,12 @@ safe_mode = Off safe_mode_gid = Off ; By default, Safe Mode does a UID compare ; check when opening files. If you want to ; relax this to a GID compare, then turn on - ; safe_mode_gid. + ; safe_mode_gid. (safe_mode must be On) +safe_mode_include_dir = ; When safe_mode is on, UID/GID checks are + ; bypassed when including files from this + ; directory and its subdirectories. (directory + ; must also be in include_path or full path + ; must be used when including) safe_mode_exec_dir = safe_mode_allowed_env_vars = PHP_ ; Setting certain environment variables ; may be a potential security breach. @@ -532,4 +537,4 @@ sockets.use_system_read = Off ; Use the system read() function instead of ; Local Variables: ; tab-width: 4 -; End: \ No newline at end of file +; End: