[Pkg-php-commits] [php/debian-etch] fix for CVE-2008-5658: directory traversal in the zip extension
Sean Finney
seanius at debian.org
Tue Apr 28 21:23:27 UTC 2009
Closes: #507857
---
debian/patches/148-CVE-2008-5658.patch | 437 ++++++++++++++++++++++++++++++++
1 files changed, 437 insertions(+), 0 deletions(-)
create mode 100644 debian/patches/148-CVE-2008-5658.patch
diff --git a/debian/patches/148-CVE-2008-5658.patch b/debian/patches/148-CVE-2008-5658.patch
new file mode 100644
index 0000000..aae5a17
--- /dev/null
+++ b/debian/patches/148-CVE-2008-5658.patch
@@ -0,0 +1,437 @@
+backported patch to fix CVE-2008-5658.
+unfortunately there is so much noise in TSRM that a more surgical fix does not
+seem possible according to upstream, so the new versions of the function are
+copied as local static functions to minimize the impact elsewhere.
+
+this also required a further patch in the etch version:
+
+http://cvs.php.net/viewvc.cgi/php-src/ext/zip/php_zip.c?r1=1.1.2.26&r2=1.1.2.27&pathrev=PHP_5_2&view=patch
+http://cvs.php.net/viewvc.cgi/php-src/ext/zip/php_zip.c?r1=1.1.2.20&r2=1.1.2.21&pathrev=PHP_5_2&view=patch
+
+and then a bit of manual massaging to get the patch to apply
+--- a/ext/zip/php_zip.c.old 2009-04-28 20:21:53.000000000 +0000
++++ b/ext/zip/php_zip.c 2009-04-28 20:35:13.000000000 +0000
+@@ -74,73 +74,331 @@
+
+ /* }}} */
+
++static int php_zip_realpath_r(char *path, int start, int len, int *ll, time_t *t, int use_realpath, int is_dir, int *link_is_dir TSRMLS_DC) /* {{{ */
++{
++ int i, j;
++ int directory = 0;
++ struct stat st;
++ realpath_cache_bucket *bucket;
++ char *tmp;
++
++ while (1) {
++ if (len <= start) {
++ return start;
++ }
++
++ i = len;
++ while (i > start && !IS_SLASH(path[i-1])) {
++ i--;
++ }
++
++ if (i == len ||
++ (i == len - 1 && path[i] == '.')) {
++ /* remove double slashes and '.' */
++ len = i - 1;
++ is_dir = 1;
++ continue;
++ } else if (i == len - 2 && path[i] == '.' && path[i+1] == '.') {
++ /* remove '..' and previous directory */
++ if (i - 1 <= start) {
++ return start ? start : len;
++ }
++ j = php_zip_realpath_r(path, start, i-1, ll, t, use_realpath, 1, NULL TSRMLS_CC);
++ if (j > start) {
++ j--;
++ while (j > start && !IS_SLASH(path[j])) {
++ j--;
++ }
++ if (!start) {
++ /* leading '..' must not be removed in case of relative path */
++ if (j == 0 && path[0] == '.' && path[1] == '.' &&
++ IS_SLASH(path[2])) {
++ path[3] = '.';
++ path[4] = '.';
++ path[5] = DEFAULT_SLASH;
++ j = 5;
++ } else if (j > 0 &&
++ path[j+1] == '.' && path[j+2] == '.' &&
++ IS_SLASH(path[j+3])) {
++ j += 4;
++ path[j++] = '.';
++ path[j++] = '.';
++ path[j] = DEFAULT_SLASH;
++ }
++ }
++ } else if (!start && !j) {
++ /* leading '..' must not be removed in case of relative path */
++ path[0] = '.';
++ path[1] = '.';
++ path[2] = DEFAULT_SLASH;
++ j = 2;
++ }
++ return j;
++ }
++
++ path[len] = 0;
++
++ tmp = tsrm_do_alloca(len+1);
++ memcpy(tmp, path, len+1);
++
++ {
++ if (i - 1 <= start) {
++ j = start;
++ } else {
++ /* some leading directories may be unaccessable */
++ j = php_zip_realpath_r(path, start, i-1, ll, t, use_realpath, 1, NULL TSRMLS_CC);
++ if (j > start) {
++ path[j++] = DEFAULT_SLASH;
++ }
++ }
++ if (j < 0 || j + len - i >= MAXPATHLEN-1) {
++ tsrm_free_alloca(tmp);
++ return -1;
++ }
++ memcpy(path+j, tmp+i, len-i+1);
++ j += (len-i);
++ }
++
++ tsrm_free_alloca(tmp);
++ return j;
++ }
++}
++/* }}} */
++
++#define CWD_EXPAND 0 /* expand "." and ".." but dont resolve symlinks */
++#define CWD_FILEPATH 1 /* resolve symlinks if file is exist otherwise expand */
++#define CWD_REALPATH 2 /* call realpath(), resolve symlinks. File must exist */
++
++#define CWD_STATE_FREE(s) \
++ free((s)->cwd);
++
++
++#define CWD_STATE_COPY(d, s) \
++ (d)->cwd_length = (s)->cwd_length; \
++ (d)->cwd = (char *) malloc((s)->cwd_length+1); \
++ memcpy((d)->cwd, (s)->cwd, (s)->cwd_length+1);
++
++/* Resolve path relatively to state and put the real path into state */
++/* returns 0 for ok, 1 for error */
++int php_zip_virtual_file_ex(cwd_state *state, const char *path, verify_path_func verify_path, int use_realpath) /* {{{ */
++{
++ int path_length = strlen(path);
++ char resolved_path[MAXPATHLEN];
++ int start = 1;
++ int ll = 0;
++ time_t t;
++ int ret;
++ int add_slash;
++ TSRMLS_FETCH();
++
++ if (path_length == 0 || path_length >= MAXPATHLEN-1) {
++ return 1;
++ }
++
++ /* cwd_length can be 0 when getcwd() fails.
++ * This can happen under solaris when a dir does not have read permissions
++ * but *does* have execute permissions */
++ if (!IS_ABSOLUTE_PATH(path, path_length)) {
++ if (state->cwd_length == 0) {
++ /* resolve relative path */
++ start = 0;
++ memcpy(resolved_path , path, path_length + 1);
++ } else {
++ int state_cwd_length = state->cwd_length;
++
++ if (path_length + state_cwd_length + 1 >= MAXPATHLEN-1) {
++ return 1;
++ }
++ memcpy(resolved_path, state->cwd, state_cwd_length);
++ resolved_path[state_cwd_length] = DEFAULT_SLASH;
++ memcpy(resolved_path + state_cwd_length + 1, path, path_length + 1);
++ path_length += state_cwd_length + 1;
++ }
++ } else {
++ memcpy(resolved_path, path, path_length + 1);
++ }
++
++ add_slash = (use_realpath != CWD_REALPATH) && path_length > 0 && IS_SLASH(resolved_path[path_length-1]);
++ t = CWDG(realpath_cache_ttl) ? 0 : -1;
++ path_length = php_zip_realpath_r(resolved_path, start, path_length, &ll, &t, use_realpath, 0, NULL TSRMLS_CC);
++
++ if (path_length < 0) {
++ errno = ENOENT;
++ return 1;
++ }
++
++ if (!start && !path_length) {
++ resolved_path[path_length++] = '.';
++ }
++ if (add_slash && path_length && !IS_SLASH(resolved_path[path_length-1])) {
++ if (path_length >= MAXPATHLEN-1) {
++ return -1;
++ }
++ resolved_path[path_length++] = DEFAULT_SLASH;
++ }
++ resolved_path[path_length] = 0;
++
++ if (verify_path) {
++ cwd_state old_state;
++
++ CWD_STATE_COPY(&old_state, state);
++ state->cwd_length = path_length;
++ state->cwd = (char *) realloc(state->cwd, state->cwd_length+1);
++ memcpy(state->cwd, resolved_path, state->cwd_length+1);
++ if (verify_path(state)) {
++ CWD_STATE_FREE(state);
++ *state = old_state;
++ ret = 1;
++ } else {
++ CWD_STATE_FREE(&old_state);
++ ret = 0;
++ }
++ } else {
++ state->cwd_length = path_length;
++ state->cwd = (char *) realloc(state->cwd, state->cwd_length+1);
++ memcpy(state->cwd, resolved_path, state->cwd_length+1);
++ ret = 0;
++ }
++ return (ret);
++}
++/* }}} */
++
++/* Flatten a path by creating a relative path (to .) */
++static char * php_zip_make_relative_path(char *path, int path_len) /* {{{ */
++{
++ char *path_begin = path;
++ int prev_is_slash = 0;
++ char *e = path + path_len - 1;
++ size_t pos = path_len - 1;
++ size_t i;
++
++ if (IS_SLASH(path[0])) {
++ return path + 1;
++ }
++
++ if (path_len < 1 || path == NULL) {
++ return NULL;
++ }
++
++ i = path_len;
++
++ while (1) {
++ while (i > 0 && !IS_SLASH(path[i])) {
++ i--;
++ }
++
++ if (!i) {
++ return path;
++ }
++
++ if (i >= 2 && (path[i -1] == '.' || path[i -1] == ':')) {
++ /* i is the position of . or :, add 1 for / */
++ path_begin = path + i + 1;
++ break;
++ }
++ i--;
++ }
++
++ return path_begin;
++}
++/* }}} */
++
+ #ifdef ZEND_ENGINE_2_1
+ /* {{{ php_zip_extract_file */
+ /* TODO: Simplify it */
+-static int php_zip_extract_file(struct zip * za, char *dest, char *file TSRMLS_DC)
++static int php_zip_extract_file(struct zip * za, char *dest, char *file, int file_len TSRMLS_DC)
+ {
+ php_stream_statbuf ssb;
+ struct zip_file *zf;
+ struct zip_stat sb;
+ char b[8192];
+
+- int n, len, ret, file_len;
++ int n, len, ret;
+
+ php_stream *stream;
+
+ char *fullpath;
+ char *file_dirname_fullpath;
+- char file_dirname[MAXPATHLEN + 1];
++ char file_dirname[MAXPATHLEN];
+ size_t dir_len;
+
+ char *file_basename;
+ size_t file_basename_len;
++ int is_dir_only = 0;
++ char *path_cleaned;
++ size_t path_cleaned_len;
++ cwd_state new_state;
++
++ new_state.cwd = (char*)malloc(1);
++ new_state.cwd[0] = '\0';
++ new_state.cwd_length = 0;
+
+- if (zip_stat(za, file, 0, &sb)) {
++ /* Clean/normlize the path and then transform any path (absolute or relative)
++ to a path relative to cwd (../../mydir/foo.txt > mydir/foo.txt)
++ */
++ if (php_zip_virtual_file_ex(&new_state, file, NULL, CWD_EXPAND) == 1) {
+ return 0;
+ }
++ path_cleaned = php_zip_make_relative_path(new_state.cwd, new_state.cwd_length);
++ path_cleaned_len = strlen(path_cleaned);
+
+- file_len = strlen(file);
+- memcpy(file_dirname, file, file_len);
+-
+- dir_len = php_dirname(file_dirname, file_len);
++ if (path_cleaned_len >= MAXPATHLEN || zip_stat(za, file, 0, &sb) != 0) {
++ return 0;
++ }
+
+- if (dir_len > 0) {
+- len = spprintf(&file_dirname_fullpath, 0, "%s/%s", dest, file_dirname);
++ /* it is a directory only, see #40228 */
++ if (path_cleaned_len > 1 && IS_SLASH(path_cleaned[path_cleaned_len - 1])) {
++ len = spprintf(&file_dirname_fullpath, 0, "%s/%s", dest, file);
++ is_dir_only = 1;
+ } else {
+- len = spprintf(&file_dirname_fullpath, 0, "%s", dest);
+- }
++ memcpy(file_dirname, path_cleaned, path_cleaned_len);
++ dir_len = php_dirname(file_dirname, path_cleaned_len);
++
++ if (dir_len <= 0 || (dir_len == 1 && file_dirname[0] == '.')) {
++ len = spprintf(&file_dirname_fullpath, 0, "%s", dest);
++ } else {
++ len = spprintf(&file_dirname_fullpath, 0, "%s/%s", dest, file_dirname);
++ }
+
+- php_basename(file, file_len, NULL, 0, &file_basename, &file_basename_len TSRMLS_CC);
++ php_basename(path_cleaned, path_cleaned_len, NULL, 0, &file_basename, (size_t *)&file_basename_len TSRMLS_CC);
+
+- if (SAFEMODE_CHECKFILE(file_dirname_fullpath)) {
+- efree(file_dirname_fullpath);
+- efree(file_basename);
+- return 0;
++ if (SAFEMODE_CHECKFILE(file_dirname_fullpath)) {
++ efree(file_dirname_fullpath);
++ efree(file_basename);
++ free(new_state.cwd);
++ return 0;
++ }
+ }
+
+ /* let see if the path already exists */
+ if (php_stream_stat_path(file_dirname_fullpath, &ssb) < 0) {
+- ret = php_stream_mkdir(file_dirname_fullpath, 0777, PHP_STREAM_MKDIR_RECURSIVE, NULL);
++
++ ret = php_stream_mkdir(file_dirname_fullpath, 0777, PHP_STREAM_MKDIR_RECURSIVE|REPORT_ERRORS, NULL);
+ if (!ret) {
+ efree(file_dirname_fullpath);
++ if (!is_dir_only) {
+ efree(file_basename);
++ free(new_state.cwd);
++ }
+ return 0;
+ }
+ }
+
+ /* it is a standalone directory, job done */
+- if (file[file_len - 1] == '/') {
++ if (is_dir_only) {
+ efree(file_dirname_fullpath);
+- efree(file_basename);
++ free(new_state.cwd);
+ return 1;
+ }
+
+- len = spprintf(&fullpath, 0, "%s/%s/%s", dest, file_dirname, file_basename);
++ len = spprintf(&fullpath, 0, "%s/%s", file_dirname_fullpath, file_basename);
+ if (!len) {
+ efree(file_dirname_fullpath);
+ efree(file_basename);
++ free(new_state.cwd);
+ return 0;
++ } else if (len > MAXPATHLEN) {
++ php_error_docref(NULL TSRMLS_CC, E_WARNING, "Full extraction path exceed MAXPATHLEN (%i)", MAXPATHLEN);
+ }
+
+ /* check again the full path, not sure if it
+@@ -158,6 +416,8 @@
+ efree(fullpath);
+ efree(file_dirname_fullpath);
+ efree(file_basename);
++ free(new_state.cwd);
++ free(new_state.cwd);
+ return 0;
+ }
+
+@@ -172,6 +432,7 @@
+ efree(fullpath);
+ efree(file_basename);
+ efree(file_dirname_fullpath);
++ free(new_state.cwd);
+
+ if (n<0) {
+ return 0;
+@@ -882,7 +1143,7 @@
+ int filename_len;
+ int err = 0;
+ long flags = 0;
+- char resolved_path[MAXPATHLEN + 1];
++ char resolved_path[MAXPATHLEN];
+
+ zval *this = getThis();
+ ze_zip_object *ze_obj = NULL;
+@@ -966,7 +1227,7 @@
+ struct zip_source *zs;
+ long offset_start = 0, offset_len = 0;
+ int cur_idx;
+- char resolved_path[MAXPATHLEN + 1];
++ char resolved_path[MAXPATHLEN];
+
+ if (!this) {
+ RETURN_FALSE;
+@@ -1682,7 +1943,7 @@
+ switch (Z_TYPE_P(zval_files)) {
+ case IS_STRING:
+ file = Z_STRVAL_P(zval_files);
+- if (!php_zip_extract_file(intern, pathto, file TSRMLS_CC)) {
++ if (!php_zip_extract_file(intern, pathto, file, Z_STRLEN_P(zval_files) TSRMLS_CC)) {
+ RETURN_FALSE;
+ }
+ break;
+@@ -1698,7 +1959,7 @@
+ break;
+ case IS_STRING:
+ file = Z_STRVAL_PP(zval_file);
+- if (!php_zip_extract_file(intern, pathto, file TSRMLS_CC)) {
++ if (!php_zip_extract_file(intern, pathto, file, Z_STRLEN_P(zval_files) TSRMLS_CC)) {
+ RETURN_FALSE;
+ }
+ break;
+@@ -1722,7 +1983,7 @@
+
+ for (i = 0; i < filecount; i++) {
+ file = (char*)zip_get_name(intern, i, ZIP_FL_UNCHANGED);
+- if (!php_zip_extract_file(intern, pathto, file TSRMLS_CC)) {
++ if (!php_zip_extract_file(intern, pathto, file, strlen(file) TSRMLS_CC)) {
+ RETURN_FALSE;
+ }
+ }
+@@ -1786,7 +2047,7 @@
+ RETURN_FALSE;
+ }
+
+- buffer = safe_emalloc(len + 1, 1, 1);
++ buffer = safe_emalloc(len, 1, 2);
+ n = zip_fread(zf, buffer, len);
+ if (n < 1) {
+ RETURN_EMPTY_STRING();
--
1.5.6.5
More information about the Pkg-php-commits
mailing list