[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