Security Fixes (OpenPKG-2004.053-php): o CAN-2004-1018: shmop_write() out of bounds memory write access. (ext/shmop/shmop.c) o CAN-2004-1018: integer overflow/underflow in pack() and unpack() functions. (main/php.h, ext/standard/pack.c) o CAN-2004-1019: possible information disclosure, double free and negative reference index array underflow in deserialization code. (ext/standard/var_unserializer.re, ext/standard/var_unserializer.c) o CAN-2004-1020: addslashes() not escaping \0 correctly. (ext/standard/string.c) **** NOT NECCESSARY IN PHP 4.3.8!! **** o CAN-2004-1063: safe_mode execution directory bypass. (ext/standard/link.c, TSRM/tsrm_virtual_cwd.c) o CAN-2004-1064: arbitrary file access through path truncation. (main/safe_mode.c) o CAN-2004-1065: exif_read_data() overflow on long sectionname. (ext/exif/exif.c) o XXX-XXXX-XXXX: magic_quotes_gpc could lead to one level directory traversal with file uploads. (main/rfc1867.c) Index: ext/exif/exif.c --- ext/exif/exif.c.bigsec 2003-09-22 17:28:07.000000000 -0600 +++ ext/exif/exif.c 2004-12-16 18:26:39.000000000 -0700 @@ -2713,7 +2713,7 @@ // JPEG does not use absolute pointers instead its pointers are relative to the start // of the TIFF header in APP1 section. */ - if (offset_val<0 || offset_val+byte_count>ImageInfo->FileSize || (ImageInfo->FileType!=IMAGE_FILETYPE_TIFF_II && ImageInfo->FileType!=IMAGE_FILETYPE_TIFF_MM)) { + if (offset_val<0 || offset_val+byte_count>ImageInfo->FileSize || (ImageInfo->FileType!=IMAGE_FILETYPE_TIFF_II && ImageInfo->FileType!=IMAGE_FILETYPE_TIFF_MM && ImageInfo->FileType!=IMAGE_FILETYPE_JPEG)) { if (value_ptr < dir_entry) { /* we can read this if offset_val > 0 */ /* some files have their values in other parts of the file */ @@ -3751,7 +3751,7 @@ } } for (i=0; i<SECTION_COUNT; i++) { - sprintf(tmp, ",%s,", exif_get_sectionname(i)); + snprintf(tmp, sizeof(tmp), ",%s,", exif_get_sectionname(i)); if (strstr(sections_str, tmp)) { sections_needed |= 1<<i; } Index: ext/shmop/shmop.c --- ext/shmop/shmop.c.orig 2002-12-31 17:35:20 +0100 +++ ext/shmop/shmop.c 2004-12-16 17:20:05 +0100 @@ -316,7 +316,7 @@ RETURN_FALSE; } - if (offset > shmop->size) { + if (offset < 0 || offset > shmop->size) { php_error_docref(NULL TSRMLS_CC, E_WARNING, "offset out of range"); RETURN_FALSE; } Index: ext/standard/link.c --- ext/standard/link.c.orig 2002-12-31 17:35:31 +0100 +++ ext/standard/link.c 2004-12-16 17:20:05 +0100 @@ -65,6 +65,14 @@ } convert_to_string_ex(filename); + if (PG(safe_mode) && !php_checkuid(Z_STRVAL_PP(filename), NULL, CHECKUID_CHECK_FILE_AND_DIR)) { + RETURN_FALSE; + } + + if (php_check_open_basedir(Z_STRVAL_PP(filename) TSRMLS_CC)) { + RETURN_FALSE; + } + ret = readlink(Z_STRVAL_PP(filename), buff, MAXPATHLEN-1); if (ret == -1) { Index: ext/standard/pack.c --- ext/standard/pack.c.orig 2004-02-25 13:36:24 +0100 +++ ext/standard/pack.c 2004-12-16 17:20:05 +0100 @@ -63,6 +63,13 @@ #include <netinet/in.h> #endif +#define INC_OUTPUTPOS(a,b) \ + if ((a) < 0 || ((INT_MAX - outputpos)/(b)) < (a)) { \ + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Type %c: integer overflow in format string", code); \ + RETURN_FALSE; \ + } \ + outputpos += (a)*(b); + /* Whether machine is little endian */ char machine_little_endian; @@ -246,7 +253,7 @@ switch ((int) code) { case 'h': case 'H': - outputpos += (arg + 1) / 2; /* 4 bit per arg */ + INC_OUTPUTPOS((arg + 1) / 2,1) /* 4 bit per arg */ break; case 'a': @@ -254,34 +261,34 @@ case 'c': case 'C': case 'x': - outputpos += arg; /* 8 bit per arg */ + INC_OUTPUTPOS(arg,1) /* 8 bit per arg */ break; case 's': case 'S': case 'n': case 'v': - outputpos += arg * 2; /* 16 bit per arg */ + INC_OUTPUTPOS(arg,2) /* 16 bit per arg */ break; case 'i': case 'I': - outputpos += arg * sizeof(int); + INC_OUTPUTPOS(arg,sizeof(int)) break; case 'l': case 'L': case 'N': case 'V': - outputpos += arg * 4; /* 32 bit per arg */ + INC_OUTPUTPOS(arg,4) /* 32 bit per arg */ break; case 'f': - outputpos += arg * sizeof(float); + INC_OUTPUTPOS(arg,sizeof(float)) break; case 'd': - outputpos += arg * sizeof(double); + INC_OUTPUTPOS(arg,sizeof(double)) break; case 'X': @@ -650,6 +657,11 @@ sprintf(n, "%.*s", namelen, name); } + if (size != 0 && size != -1 && INT_MAX - size + 1 < inputpos) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Type %c: integer overflow", type); + inputpos = 0; + } + if ((inputpos + size) <= inputlen) { switch ((int) type) { case 'a': @@ -820,6 +832,10 @@ } inputpos += size; + if (inputpos < 0) { + php_error_docref(NULL TSRMLS_CC, E_WARNING, "Type %c: outside of string", type); + inputpos = 0; + } } else if (arg < 0) { /* Reached end of input for '*' repeater */ break; Index: ext/standard/var_unserializer.re --- ext/standard/var_unserializer.re.orig 2004-03-27 02:17:06 +0100 +++ ext/standard/var_unserializer.re 2004-12-16 21:09:57 +0100 @@ -62,7 +62,7 @@ if (!var_hash) return !SUCCESS; - if (id >= var_hash->used_slots) return !SUCCESS; + if (id < 0 || id >= var_hash->used_slots) return !SUCCESS; *store = &var_hash->data[id]; @@ -139,7 +139,7 @@ static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, int elements) { while (elements-- > 0) { - zval *key, *data; + zval *key, *data, *old_data; ALLOC_INIT_ZVAL(key); @@ -161,9 +161,15 @@ switch (Z_TYPE_P(key)) { case IS_LONG: + if (zend_hash_index_find(ht, Z_LVAL_P(key), (void **)&old_data)) { + var_replace(var_hash, old_data, rval); + } zend_hash_index_update(ht, Z_LVAL_P(key), &data, sizeof(data), NULL); break; case IS_STRING: + if (zend_hash_find(ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, (void **)&old_data)) { + var_replace(var_hash, old_data, rval); + } zend_hash_update(ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, &data, sizeof(data), NULL); break; @@ -398,7 +404,6 @@ #ifdef ZEND_ENGINE_2 ce = *(zend_class_entry **)ce; /* Bad hack, TBF! */ #endif - efree(class_name); } } } @@ -406,7 +411,6 @@ #ifdef ZEND_ENGINE_2 ce = *(zend_class_entry **)ce; /* Bad hack, TBF! */ #endif - efree(class_name); } *p = YYCURSOR; @@ -414,8 +418,8 @@ if (incomplete_class) { php_store_class_name(*rval, class_name, len2 TSRMLS_CC); - efree(class_name); } + efree(class_name); return object_common2(UNSERIALIZE_PASSTHRU, elements); } Index: ext/standard/var_unserializer.c --- ext/standard/var_unserializer.c.orig 2004-07-13 16:53:12 +0200 +++ ext/standard/var_unserializer.c 2004-12-16 21:10:16 +0100 @@ -63,7 +63,7 @@ if (!var_hash) return !SUCCESS; - if (id >= var_hash->used_slots) return !SUCCESS; + if (id < 0 || id >= var_hash->used_slots) return !SUCCESS; *store = &var_hash->data[id]; @@ -134,7 +134,7 @@ static inline int process_nested_data(UNSERIALIZE_PARAMETER, HashTable *ht, int elements) { while (elements-- > 0) { - zval *key, *data; + zval *key, *data, *old_data; ALLOC_INIT_ZVAL(key); @@ -156,9 +156,15 @@ switch (Z_TYPE_P(key)) { case IS_LONG: + if (zend_hash_index_find(ht, Z_LVAL_P(key), (void **)&old_data)) { + var_replace(var_hash, old_data, rval); + } zend_hash_index_update(ht, Z_LVAL_P(key), &data, sizeof(data), NULL); break; case IS_STRING: + if (zend_hash_find(ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, (void **)&old_data)) { + var_replace(var_hash, old_data, rval); + } zend_hash_update(ht, Z_STRVAL_P(key), Z_STRLEN_P(key) + 1, &data, sizeof(data), NULL); break; @@ -435,7 +441,6 @@ #ifdef ZEND_ENGINE_2 ce = *(zend_class_entry **)ce; /* Bad hack, TBF! */ #endif - efree(class_name); } } } @@ -443,7 +448,6 @@ #ifdef ZEND_ENGINE_2 ce = *(zend_class_entry **)ce; /* Bad hack, TBF! */ #endif - efree(class_name); } *p = YYCURSOR; @@ -451,8 +455,8 @@ if (incomplete_class) { php_store_class_name(*rval, class_name, len2 TSRMLS_CC); - efree(class_name); } + efree(class_name); return object_common2(UNSERIALIZE_PASSTHRU, elements); } Index: main/php.h --- main/php.h.orig 2003-09-25 01:22:32 +0200 +++ main/php.h 2004-12-16 17:20:05 +0100 @@ -226,6 +226,14 @@ #define LONG_MIN (- LONG_MAX - 1) #endif +#ifndef INT_MAX +#define INT_MAX 2147483647 +#endif + +#ifndef INT_MIN +#define INT_MIN (- INT_MAX - 1) +#endif + #define PHP_GCC_VERSION ZEND_GCC_VERSION #define PHP_ATTRIBUTE_MALLOC ZEND_ATTRIBUTE_MALLOC #define PHP_ATTRIBUTE_FORMAT ZEND_ATTRIBUTE_FORMAT Index: main/safe_mode.c --- main/safe_mode.c.orig 2003-03-17 14:50:23 +0100 +++ main/safe_mode.c 2004-12-16 17:20:05 +0100 @@ -54,13 +54,16 @@ php_stream_wrapper *wrapper = NULL; TSRMLS_FETCH(); - strlcpy(filenamecopy, filename, MAXPATHLEN); - filename=(char *)&filenamecopy; - if (!filename) { return 0; /* path must be provided */ } + if (strlcpy(filenamecopy, filename, MAXPATHLEN)>=MAXPATHLEN) { + return 0; + } + filename=(char *)&filenamecopy; + + if (fopen_mode) { if (fopen_mode[0] == 'r') { mode = CHECKUID_DISALLOW_FILE_NOT_EXISTS; Index: main/rfc1867.c --- main/rfc1867.c.bigsec 2004-12-16 18:30:00.000000000 -0700 +++ main/rfc1867.c 2004-12-16 18:36:39.000000000 -0700 @@ -60,6 +60,7 @@ #define UPLOAD_ERROR_B 2 /* Uploaded file exceeded MAX_FILE_SIZE */ #define UPLOAD_ERROR_C 3 /* Partially uploaded */ #define UPLOAD_ERROR_D 4 /* No file uploaded */ +#define UPLOAD_ERROR_E 6 /* Missing /tmp or similar directory */ void php_rfc1867_register_constants(TSRMLS_D) { @@ -68,6 +69,7 @@ REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_FORM_SIZE", UPLOAD_ERROR_B, CONST_CS | CONST_PERSISTENT); REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_PARTIAL", UPLOAD_ERROR_C, CONST_CS | CONST_PERSISTENT); REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_NO_FILE", UPLOAD_ERROR_D, CONST_CS | CONST_PERSISTENT); + REGISTER_MAIN_LONG_CONSTANT("UPLOAD_ERR_NO_TMP_DIR", UPLOAD_ERROR_E, CONST_CS | CONST_PERSISTENT); } static void normalize_protected_variable(char *varname TSRMLS_DC) @@ -757,7 +759,7 @@ while (!multipart_buffer_eof(mbuff TSRMLS_CC)) { char buff[FILLUNIT]; - char *cd=NULL,*param=NULL,*filename=NULL; + char *cd=NULL,*param=NULL,*filename=NULL,*tmp=NULL; int blen=0, wlen=0; zend_llist_clean(&header); @@ -849,12 +851,14 @@ SAFE_RETURN; } + total_bytes = cancel_upload = 0; + if (!skip_upload) { /* Handle file */ fp = php_open_temporary_file(PG(upload_tmp_dir), "php", &temp_filename TSRMLS_CC); if (!fp) { sapi_module.sapi_error(E_WARNING, "File upload error - unable to create a temporary file"); - skip_upload = 1; + cancel_upload = UPLOAD_ERROR_E; } } if (skip_upload) { @@ -863,9 +867,6 @@ continue; } - total_bytes = 0; - cancel_upload = 0; - if(strlen(filename) == 0) { #ifdef DEBUG_FILE_UPLOAD sapi_module.sapi_error(E_NOTICE, "No file uploaded"); @@ -892,10 +893,12 @@ } } } - fclose(fp); + if (fp) { + fclose(fp); + } #ifdef DEBUG_FILE_UPLOAD - if(strlen(filename) > 0 && total_bytes == 0) { + if(strlen(filename) > 0 && total_bytes == 0 && !cancel_upload) { sapi_module.sapi_error(E_WARNING, "Uploaded file size 0 - file [%s=%s] not saved", param, filename); cancel_upload = 5; } @@ -903,7 +906,9 @@ if (cancel_upload) { if (temp_filename) { - unlink(temp_filename); + if (cancel_upload != UPLOAD_ERROR_E) { /* file creation failed */ + unlink(temp_filename); + } efree(temp_filename); } temp_filename=""; @@ -956,6 +961,14 @@ #else s = strrchr(filename, '\\'); #endif + if (PG(magic_quotes_gpc)) { + s = s ? s : filename; + tmp = strrchr(s, '\''); + s = tmp > s ? tmp : s; + tmp = strrchr(s, '"'); + s = tmp > s ? tmp : s; + } + if (s && s > filename) { safe_php_register_variable(lbuf, s+1, NULL, 0 TSRMLS_CC); } else { Index: TSRM/tsrm_virtual_cwd.c --- TSRM/tsrm_virtual_cwd.c.orig 2003-07-28 20:35:34 +0200 +++ TSRM/tsrm_virtual_cwd.c 2004-12-16 21:15:08 +0100 @@ -301,15 +301,22 @@ if (path_length == 0) return (0); + if (path_length >= MAXPATHLEN) + return (1); #if !defined(TSRM_WIN32) && !defined(NETWARE) /* 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) || (state->cwd_length < 1)) { - if (use_realpath && realpath(path, resolved_path)) { - path = resolved_path; - path_length = strlen(path); + if (use_realpath) { + if (realpath(path, resolved_path)) { + path = resolved_path; + path_length = strlen(path); + } else { + /* disable for now + return 1; */ + } } } else { /* Concat current directory with relative path and then run realpath() on it */ char *tmp; @@ -325,9 +332,19 @@ memcpy(ptr, path, path_length); ptr += path_length; *ptr = '\0'; - if (use_realpath && realpath(tmp, resolved_path)) { - path = resolved_path; - path_length = strlen(path); + if (strlen(tmp) >= MAXPATHLEN) { + free(tmp); + return 1; + } + if (use_realpath) { + if (realpath(tmp, resolved_path)) { + path = resolved_path; + path_length = strlen(path); + } else { + /* disable for now + free(tmp); + return 1; */ + } } free(tmp); } @@ -818,13 +835,24 @@ CWD_API FILE *virtual_popen(const char *command, const char *type TSRMLS_DC) { int command_length; + int dir_length, extra = 0; char *command_line; - char *ptr; + char *ptr, *dir; FILE *retval; command_length = strlen(command); - ptr = command_line = (char *) malloc(command_length + sizeof("cd ; ") + CWDG(cwd).cwd_length+1); + dir_length = CWDG(cwd).cwd_length; + dir = CWDG(cwd).cwd; + while (dir_length > 0) { + if (*dir == '\'') extra+=3; + dir++; + dir_length--; + } + dir_length = CWDG(cwd).cwd_length; + dir = CWDG(cwd).cwd; + + ptr = command_line = (char *) malloc(command_length + sizeof("cd '' ; ") + dir_length +1+1); if (!command_line) { return NULL; } @@ -834,8 +862,21 @@ if (CWDG(cwd).cwd_length == 0) { *ptr++ = DEFAULT_SLASH; } else { - memcpy(ptr, CWDG(cwd).cwd, CWDG(cwd).cwd_length); - ptr += CWDG(cwd).cwd_length; + *ptr++ = '\''; + while (dir_length > 0) { + switch (*dir) { + case '\'': + *ptr++ = '\''; + *ptr++ = '\\'; + *ptr++ = '\''; + /* fall-through */ + default: + *ptr++ = *dir; + } + dir++; + dir_length--; + } + *ptr++ = '\''; } *ptr++ = ' ';