diff --git a/Zend/tests/GHSA-wm6j-2649-pv75.phpt b/Zend/tests/GHSA-wm6j-2649-pv75.phpt new file mode 100644 index 000000000000..c1035938bd7b --- /dev/null +++ b/Zend/tests/GHSA-wm6j-2649-pv75.phpt @@ -0,0 +1,24 @@ +--TEST-- +GHSA-wm6j-2649-pv75: Null pointer dereference in php_mb_check_encoding() via mb_ereg_search_init() +--CREDITS-- +vi3tL0u1s +--EXTENSIONS-- +mbstring +--SKIPIF-- + +--FILE-- + +--EXPECTF-- +Deprecated: Function mb_regex_encoding() is deprecated since 8.6, because the underlying library is no longer maintained in %s on line %d + +Fatal error: Uncaught ValueError: mb_regex_encoding(): Argument #1 ($encoding) must be a valid encoding, "iso-8859-11" given in %s:%d +Stack trace: +#0 %s(%d): mb_regex_encoding('iso-8859-11') +#1 {main} + thrown in %s on line %d diff --git a/Zend/zend_compile.c b/Zend/zend_compile.c index a96af71aa900..ee57e1dafb87 100644 --- a/Zend/zend_compile.c +++ b/Zend/zend_compile.c @@ -2198,7 +2198,7 @@ ZEND_API size_t zend_dirname(char *path, size_t len) /* Note that on Win32 CWD is per drive (heritage from CP/M). * This means dirname("c:foo") maps to "c:." or "c:" - which means CWD on C: drive. */ - if ((2 <= len) && isalpha((int)((unsigned char *)path)[0]) && (':' == path[1])) { + if ((2 <= len) && isalpha((unsigned char)path[0]) && (':' == path[1])) { /* Skip over the drive spec (if any) so as not to change */ path += 2; len_adjust += 2; diff --git a/Zend/zend_ini.c b/Zend/zend_ini.c index e4aec28c7b07..99df90486632 100644 --- a/Zend/zend_ini.c +++ b/Zend/zend_ini.c @@ -584,7 +584,7 @@ static const char *zend_ini_consume_quantity_prefix(const char *const digits, co ++digits_consumed; } - if (digits_consumed[0] == '0' && !isdigit(digits_consumed[1])) { + if (digits_consumed[0] == '0' && !isdigit((unsigned char)digits_consumed[1])) { /* Value is just 0 */ if ((digits_consumed+1) == str_end) { return digits_consumed; @@ -642,7 +642,7 @@ static zend_ulong zend_ini_parse_quantity_internal(const zend_string *value, zen } /* if there is no digit after +/- */ - if (!isdigit(digits[0])) { + if (!isdigit((unsigned char)digits[0])) { /* Escape the string to avoid null bytes and to make non-printable chars * visible */ smart_str_append_escaped(&invalid, ZSTR_VAL(value), ZSTR_LEN(value)); @@ -656,7 +656,7 @@ static zend_ulong zend_ini_parse_quantity_internal(const zend_string *value, zen } int base = 0; - if (digits[0] == '0' && !isdigit(digits[1])) { + if (digits[0] == '0' && !isdigit((unsigned char)digits[1])) { /* Value is just 0 */ if ((digits+1) == str_end) { *errstr = NULL; diff --git a/Zend/zend_operators.c b/Zend/zend_operators.c index 8d861adb394e..c54f4f0c2a22 100644 --- a/Zend/zend_operators.c +++ b/Zend/zend_operators.c @@ -3321,8 +3321,8 @@ ZEND_API int ZEND_FASTCALL zend_binary_strcasecmp_l(const char *s1, size_t len1, len = MIN(len1, len2); while (len--) { - c1 = zend_tolower((int)*(unsigned char *)s1++); - c2 = zend_tolower((int)*(unsigned char *)s2++); + c1 = zend_tolower((unsigned char)*(s1++)); + c2 = zend_tolower((unsigned char)*(s2++)); if (c1 != c2) { return c1 - c2; } @@ -3342,8 +3342,8 @@ ZEND_API int ZEND_FASTCALL zend_binary_strncasecmp_l(const char *s1, size_t len1 } len = MIN(length, MIN(len1, len2)); while (len--) { - c1 = zend_tolower((int)*(unsigned char *)s1++); - c2 = zend_tolower((int)*(unsigned char *)s2++); + c1 = zend_tolower((unsigned char)*(s1++)); + c2 = zend_tolower((unsigned char)*(s2++)); if (c1 != c2) { return c1 - c2; } diff --git a/Zend/zend_virtual_cwd.c b/Zend/zend_virtual_cwd.c index f8dabd507783..da27e9fd5426 100644 --- a/Zend/zend_virtual_cwd.c +++ b/Zend/zend_virtual_cwd.c @@ -193,7 +193,7 @@ void virtual_cwd_main_cwd_init(uint8_t reinit) /* {{{ */ main_cwd_state.cwd_length = strlen(cwd); #ifdef ZEND_WIN32 if (main_cwd_state.cwd_length >= 2 && cwd[1] == ':') { - cwd[0] = toupper(cwd[0]); + cwd[0] = toupper((unsigned char)cwd[0]); } #endif main_cwd_state.cwd = strdup(cwd); @@ -269,7 +269,7 @@ CWD_API char *virtual_getcwd_ex(size_t *length) /* {{{ */ *length = state->cwd_length+1; retval = (char *) emalloc(*length+1); memcpy(retval, state->cwd, *length); - retval[0] = toupper(retval[0]); + retval[0] = toupper((unsigned char)retval[0]); retval[*length-1] = DEFAULT_SLASH; retval[*length] = '\0'; return retval; @@ -1112,7 +1112,7 @@ CWD_API int virtual_file_ex(cwd_state *state, const char *path, verify_path_func if (resolved_path[start] == 0) { goto verify; } - resolved_path[start] = toupper(resolved_path[start]); + resolved_path[start] = toupper((unsigned char)resolved_path[start]); start++; } resolved_path[start++] = DEFAULT_SLASH; @@ -1120,13 +1120,13 @@ CWD_API int virtual_file_ex(cwd_state *state, const char *path, verify_path_func if (resolved_path[start] == 0) { goto verify; } - resolved_path[start] = toupper(resolved_path[start]); + resolved_path[start] = toupper((unsigned char)resolved_path[start]); start++; } resolved_path[start++] = DEFAULT_SLASH; } else if (IS_ABSOLUTE_PATH(resolved_path, path_length)) { /* skip DRIVE name */ - resolved_path[0] = toupper(resolved_path[0]); + resolved_path[0] = toupper((unsigned char)resolved_path[0]); resolved_path[2] = DEFAULT_SLASH; if (path_length == 2) { resolved_path[3] = '\0'; diff --git a/Zend/zend_virtual_cwd.h b/Zend/zend_virtual_cwd.h index 0c543bba5c68..ad30c0323892 100644 --- a/Zend/zend_virtual_cwd.h +++ b/Zend/zend_virtual_cwd.h @@ -85,7 +85,7 @@ typedef unsigned short mode_t; #define IS_UNC_PATH(path, len) \ (len >= 2 && IS_SLASH(path[0]) && IS_SLASH(path[1])) #define IS_ABSOLUTE_PATH(path, len) \ - (len >= 2 && (/* is local */isalpha(path[0]) && path[1] == ':' || /* is UNC */IS_SLASH(path[0]) && IS_SLASH(path[1]))) + (len >= 2 && (/* is local */isalpha((unsigned char)(path)[0]) && path[1] == ':' || /* is UNC */IS_SLASH(path[0]) && IS_SLASH(path[1]))) #else #ifdef HAVE_DIRENT_H diff --git a/ext/com_dotnet/com_extension.c b/ext/com_dotnet/com_extension.c index 1fe34cce06b5..e9c6e46884ce 100644 --- a/ext/com_dotnet/com_extension.c +++ b/ext/com_dotnet/com_extension.c @@ -116,11 +116,11 @@ static PHP_INI_MH(OnTypeLibFileUpdate) } /* Remove leading/training white spaces on search_string */ - while (isspace(*typelib_name)) {/* Ends on '\0' in worst case */ + while (isspace((unsigned char)*typelib_name)) {/* Ends on '\0' in worst case */ typelib_name ++; } ptr = typelib_name + strlen(typelib_name) - 1; - while ((ptr != typelib_name) && isspace(*ptr)) { + while ((ptr != typelib_name) && isspace((unsigned char)*ptr)) { *ptr = '\0'; ptr--; } diff --git a/ext/date/lib/parse_date.c b/ext/date/lib/parse_date.c index 65d99806642d..eb324f8430c5 100644 --- a/ext/date/lib/parse_date.c +++ b/ext/date/lib/parse_date.c @@ -518,7 +518,7 @@ static timelib_sll timelib_get_nr(const char **ptr, int max_length) static void timelib_skip_day_suffix(const char **ptr) { - if (isspace(**ptr)) { + if (isspace((unsigned char)**ptr)) { return; } if (!timelib_strncasecmp(*ptr, "nd", 2) || !timelib_strncasecmp(*ptr, "rd", 2) ||!timelib_strncasecmp(*ptr, "st", 2) || !timelib_strncasecmp(*ptr, "th", 2)) { @@ -859,7 +859,7 @@ static timelib_long timelib_parse_tz_cor(const char **ptr, int *tz_not_found) *tz_not_found = 1; - while (isdigit(**ptr) || **ptr == ':') { + while (isdigit((unsigned char)**ptr) || **ptr == ':') { ++*ptr; } end = *ptr; @@ -924,7 +924,7 @@ static timelib_long timelib_parse_tz_minutes(const char **ptr, timelib_time *t) } ++*ptr; - while (isdigit(**ptr)) { + while (isdigit((unsigned char)**ptr)) { ++*ptr; } diff --git a/ext/date/lib/parse_date.re b/ext/date/lib/parse_date.re index ffb3e8e35913..f4a392753249 100644 --- a/ext/date/lib/parse_date.re +++ b/ext/date/lib/parse_date.re @@ -516,7 +516,7 @@ static timelib_sll timelib_get_nr(const char **ptr, int max_length) static void timelib_skip_day_suffix(const char **ptr) { - if (isspace(**ptr)) { + if (isspace((unsigned char)**ptr)) { return; } if (!timelib_strncasecmp(*ptr, "nd", 2) || !timelib_strncasecmp(*ptr, "rd", 2) ||!timelib_strncasecmp(*ptr, "st", 2) || !timelib_strncasecmp(*ptr, "th", 2)) { @@ -857,7 +857,7 @@ static timelib_long timelib_parse_tz_cor(const char **ptr, int *tz_not_found) *tz_not_found = 1; - while (isdigit(**ptr) || **ptr == ':') { + while (isdigit((unsigned char)**ptr) || **ptr == ':') { ++*ptr; } end = *ptr; @@ -922,7 +922,7 @@ static timelib_long timelib_parse_tz_minutes(const char **ptr, timelib_time *t) } ++*ptr; - while (isdigit(**ptr)) { + while (isdigit((unsigned char)**ptr)) { ++*ptr; } diff --git a/ext/date/lib/parse_iso_intervals.c b/ext/date/lib/parse_iso_intervals.c index 1fc0ede9679c..5dcbfe350639 100644 --- a/ext/date/lib/parse_iso_intervals.c +++ b/ext/date/lib/parse_iso_intervals.c @@ -985,10 +985,10 @@ void timelib_strtointerval(const char *s, size_t len, in.errors->error_messages = NULL; if (len > 0) { - while (isspace(*s) && s < e) { + while (isspace((unsigned char)*s) && s < e) { s++; } - while (isspace(*e) && e > s) { + while (isspace((unsigned char)*e) && e > s) { e--; } } diff --git a/ext/date/lib/parse_iso_intervals.re b/ext/date/lib/parse_iso_intervals.re index 2a394156f98d..009420077513 100644 --- a/ext/date/lib/parse_iso_intervals.re +++ b/ext/date/lib/parse_iso_intervals.re @@ -343,10 +343,10 @@ void timelib_strtointerval(const char *s, size_t len, in.errors->error_messages = NULL; if (len > 0) { - while (isspace(*s) && s < e) { + while (isspace((unsigned char)*s) && s < e) { s++; } - while (isspace(*e) && e > s) { + while (isspace((unsigned char)*e) && e > s) { e--; } } diff --git a/ext/date/lib/timelib.c b/ext/date/lib/timelib.c index 6473a2798a80..faf383a5fa12 100644 --- a/ext/date/lib/timelib.c +++ b/ext/date/lib/timelib.c @@ -126,7 +126,7 @@ void timelib_time_tz_abbr_update(timelib_time* tm, const char* tz_abbr) TIMELIB_TIME_FREE(tm->tz_abbr); tm->tz_abbr = timelib_strdup(tz_abbr); for (i = 0; i < tz_abbr_len; i++) { - tm->tz_abbr[i] = toupper(tz_abbr[i]); + tm->tz_abbr[i] = toupper((unsigned char)tz_abbr[i]); } } diff --git a/ext/filter/logical_filters.c b/ext/filter/logical_filters.c index c6653f29e3d7..45acf6d47541 100644 --- a/ext/filter/logical_filters.c +++ b/ext/filter/logical_filters.c @@ -533,21 +533,21 @@ static bool php_filter_validate_domain_ex(const zend_string *domain, zend_long f } /* First char must be alphanumeric */ - if(*s == '.' || (hostname && !isalnum((int)*(unsigned char *)s))) { + if(*s == '.' || (hostname && !isalnum((unsigned char)*s))) { return false; } while (s < e) { if (*s == '.') { /* The first and the last character of a label must be alphanumeric */ - if (*(s + 1) == '.' || (hostname && (!isalnum((int)*(unsigned char *)(s - 1)) || !isalnum((int)*(unsigned char *)(s + 1))))) { + if (*(s + 1) == '.' || (hostname && (!isalnum((unsigned char)s[-1]) || !isalnum((unsigned char)s[1])))) { return false; } /* Reset label length counter */ i = 1; } else { - if (i > 63 || (hostname && (*s != '-' || *(s + 1) == '\0') && !isalnum((int)*(unsigned char *)s))) { + if (i > 63 || (hostname && (*s != '-' || *(s + 1) == '\0') && !isalnum((unsigned char)*s))) { return false; } @@ -575,9 +575,9 @@ static bool is_userinfo_valid(const zend_string *str) const char *p = ZSTR_VAL(str); while (p - ZSTR_VAL(str) < ZSTR_LEN(str)) { static const char *valid = "-._~!$&'()*+,;=:"; - if (isalpha(*p) || isdigit(*p) || strchr(valid, *p)) { + if (isalpha((unsigned char)*p) || isdigit((unsigned char)*p) || strchr(valid, *p)) { p++; - } else if (*p == '%' && p - ZSTR_VAL(str) <= ZSTR_LEN(str) - 3 && isdigit(*(p+1)) && isxdigit(*(p+2))) { + } else if (*p == '%' && p - ZSTR_VAL(str) <= ZSTR_LEN(str) - 3 && isdigit((unsigned char)p[1]) && isxdigit((unsigned char)p[2])) { p += 3; } else { return false; diff --git a/ext/ftp/ftp.c b/ext/ftp/ftp.c index 7172ac3e960f..66a05a95f4ed 100644 --- a/ext/ftp/ftp.c +++ b/ext/ftp/ftp.c @@ -483,7 +483,7 @@ void ftp_raw(ftpbuf_t *ftp, const char *cmd, const size_t cmd_len, zval *return_ array_init(return_value); while (ftp_readline(ftp)) { add_next_index_string(return_value, ftp->inbuf); - if (isdigit(ftp->inbuf[0]) && isdigit(ftp->inbuf[1]) && isdigit(ftp->inbuf[2]) && ftp->inbuf[3] == ' ') { + if (isdigit((unsigned char)ftp->inbuf[0]) && isdigit((unsigned char)ftp->inbuf[1]) && isdigit((unsigned char)ftp->inbuf[2]) && ftp->inbuf[3] == ' ') { return; } } @@ -789,7 +789,7 @@ bool ftp_pasv(ftpbuf_t *ftp, int pasv) return false; } /* parse out the IP and port */ - for (ptr = ftp->inbuf; *ptr && !isdigit(*ptr); ptr++); + for (ptr = ftp->inbuf; *ptr && !isdigit((unsigned char)*ptr); ptr++); n = sscanf(ptr, "%lu,%lu,%lu,%lu,%lu,%lu", &b[0], &b[1], &b[2], &b[3], &b[4], &b[5]); if (n != 6) { return false; @@ -1100,7 +1100,7 @@ time_t ftp_mdtm(ftpbuf_t *ftp, const char *path, const size_t path_len) return -1; } /* parse out the timestamp */ - for (ptr = ftp->inbuf; *ptr && !isdigit(*ptr); ptr++); + for (ptr = ftp->inbuf; *ptr && !isdigit((unsigned char)*ptr); ptr++); n = sscanf(ptr, "%4d%2d%2d%2d%2d%2d", &tm.tm_year, &tm.tm_mon, &tm.tm_mday, &tm.tm_hour, &tm.tm_min, &tm.tm_sec); if (n != 6) { return -1; @@ -1276,13 +1276,13 @@ static bool ftp_getresp(ftpbuf_t *ftp) } /* Break out when the end-tag is found */ - if (isdigit(ftp->inbuf[0]) && isdigit(ftp->inbuf[1]) && isdigit(ftp->inbuf[2]) && ftp->inbuf[3] == ' ') { + if (isdigit((unsigned char)ftp->inbuf[0]) && isdigit((unsigned char)ftp->inbuf[1]) && isdigit((unsigned char)ftp->inbuf[2]) && ftp->inbuf[3] == ' ') { break; } } /* translate the tag */ - if (!isdigit(ftp->inbuf[0]) || !isdigit(ftp->inbuf[1]) || !isdigit(ftp->inbuf[2])) { + if (!isdigit((unsigned char)ftp->inbuf[0]) || !isdigit((unsigned char)ftp->inbuf[1]) || !isdigit((unsigned char)ftp->inbuf[2])) { return false; } diff --git a/ext/gd/libgd/gd_xbm.c b/ext/gd/libgd/gd_xbm.c index 36eff58725dd..bd81b8685b20 100644 --- a/ext/gd/libgd/gd_xbm.c +++ b/ext/gd/libgd/gd_xbm.c @@ -189,7 +189,7 @@ void gdImageXbmCtx(gdImagePtr image, char* file_name, int fg, gdIOCtx * out) } else { for (i=0; imime_name) { - if (strncasecmp((*encoding)->mime_name, name, name_len) == 0 && (*encoding)->mime_name[name_len] == '\0') { + size_t mime_len = strlen((*encoding)->mime_name); + if (mime_len == name_len && strncasecmp((*encoding)->mime_name, name, name_len) == 0) { return *encoding; } } @@ -352,7 +353,8 @@ const mbfl_encoding *mbfl_name2encoding_ex(const char *name, size_t name_len) for (encoding = mbfl_encoding_ptr_list; *encoding; encoding++) { if ((*encoding)->aliases) { for (const char **alias = (*encoding)->aliases; *alias; alias++) { - if (strncasecmp(name, *alias, name_len) == 0 && (*alias)[name_len] == '\0') { + size_t alias_len = strlen(*alias); + if (alias_len == name_len && strncasecmp(name, *alias, name_len) == 0) { return *encoding; } } diff --git a/ext/mbstring/mbstring.c b/ext/mbstring/mbstring.c index 91474510bea5..faa21b11a0f7 100644 --- a/ext/mbstring/mbstring.c +++ b/ext/mbstring/mbstring.c @@ -624,7 +624,7 @@ static char *php_mb_rfc1867_getword(const zend_encoding *encoding, char **line, static char *php_mb_rfc1867_getword_conf(const zend_encoding *encoding, char *str) /* {{{ */ { - while (*str && isspace(*(unsigned char *)str)) { + while (*str && isspace((unsigned char)*str)) { ++str; } @@ -640,7 +640,7 @@ static char *php_mb_rfc1867_getword_conf(const zend_encoding *encoding, char *st } else { char *strend = str; - while (*strend && !isspace(*(unsigned char *)strend)) { + while (*strend && !isspace((unsigned char)*strend)) { ++strend; } return php_mb_rfc1867_substring_conf(encoding, str, strend - str, 0); diff --git a/ext/mbstring/php_mbregex.c b/ext/mbstring/php_mbregex.c index baf9c57f41fb..1043c4a46eb2 100644 --- a/ext/mbstring/php_mbregex.c +++ b/ext/mbstring/php_mbregex.c @@ -407,8 +407,13 @@ int php_mb_regex_set_mbctype(const char *encname) if (mbctype == ONIG_ENCODING_UNDEF) { return FAILURE; } + const mbfl_encoding *mbfl_enc = mbfl_name2encoding(encname); + if (mbfl_enc == NULL) { + /* Encoding supported by Oniguruma but not by mbfl */ + return FAILURE; + } MBREX(current_mbctype) = mbctype; - MBREX(current_mbctype_mbfl_encoding) = mbfl_name2encoding(encname); + MBREX(current_mbctype_mbfl_encoding) = mbfl_enc; return SUCCESS; } /* }}} */ @@ -777,7 +782,7 @@ static inline void mb_regex_substitute( continue; } if (name_end[0] == delim) break; - if (maybe_num && !isdigit(name_end[0])) maybe_num = 0; + if (maybe_num && !isdigit((unsigned char)name_end[0])) maybe_num = 0; name_end++; } p = name_end + 1; diff --git a/ext/mbstring/tests/GHSA-74r9-qxhc-fx53.phpt b/ext/mbstring/tests/GHSA-74r9-qxhc-fx53.phpt new file mode 100644 index 000000000000..e58be7e50d8a --- /dev/null +++ b/ext/mbstring/tests/GHSA-74r9-qxhc-fx53.phpt @@ -0,0 +1,50 @@ +--TEST-- +GHSA-74r9-qxhc-fx53: Out-of-bounds access in mbfl_name2encoding_ex() +--CREDITS-- +Akshay Jain (AkshayJainG) +--EXTENSIONS-- +mbstring +--FILE-- +getMessage(), "\n"; + } +} + +ini_set('mbstring.detect_order', $encoding); +ini_set('mbstring.detect_order', $alias); +ini_set('mbstring.http_output', $encoding); +ini_set('mbstring.http_output', $alias); + +test(fn () => mb_convert_encoding('foo', $encoding, $encoding)); +test(fn () => mb_convert_encoding('foo', $alias, $alias)); +test(fn () => mb_detect_encoding('foo', $encoding)); +test(fn () => mb_detect_encoding('foo', $alias)); +test(fn () => mb_convert_variables($encoding, $alias, $var)); +test(fn () => mb_detect_order($encoding)); +test(fn () => mb_detect_order($alias)); + +?> +--EXPECTF-- +Warning: ini_set(): INI setting contains invalid encoding "UTF-8" in %s on line %d + +Warning: ini_set(): INI setting contains invalid encoding "binary" in %s on line %d + +Deprecated: ini_set(): Use of mbstring.http_output is deprecated in %s on line %d + +Deprecated: ini_set(): Use of mbstring.http_output is deprecated in %s on line %d +ValueError: mb_convert_encoding(): Argument #3 ($from_encoding) contains invalid encoding "UTF-8" +ValueError: mb_convert_encoding(): Argument #3 ($from_encoding) contains invalid encoding "binary" +ValueError: mb_detect_encoding(): Argument #2 ($encodings) contains invalid encoding "UTF-8" +ValueError: mb_detect_encoding(): Argument #2 ($encodings) contains invalid encoding "binary" +ValueError: mb_convert_variables(): Argument #2 ($from_encoding) contains invalid encoding "binary" +ValueError: mb_detect_order(): Argument #1 ($encoding) contains invalid encoding "UTF-8" +ValueError: mb_detect_order(): Argument #1 ($encoding) contains invalid encoding "binary" diff --git a/ext/mysqlnd/mysqlnd_result.c b/ext/mysqlnd/mysqlnd_result.c index 1f811c8795df..b4d0bdbd8d2c 100644 --- a/ext/mysqlnd/mysqlnd_result.c +++ b/ext/mysqlnd/mysqlnd_result.c @@ -990,7 +990,7 @@ MYSQLND_METHOD(mysqlnd_res, fetch_into)(MYSQLND_RES * result, const unsigned int HashTable *row_ht = Z_ARRVAL_P(return_value); MYSQLND_FIELD *field = meta->fields; - for (unsigned i = 0; i < meta->field_count; i++) { + for (unsigned i = 0; i < meta->field_count; i++, field++) { zval *data = &row_data[i]; if (flags & MYSQLND_FETCH_NUM) { diff --git a/ext/pcre/php_pcre.c b/ext/pcre/php_pcre.c index 8a3eef28b034..29c2551f38f6 100644 --- a/ext/pcre/php_pcre.c +++ b/ext/pcre/php_pcre.c @@ -620,7 +620,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo /* Parse through the leading whitespace, and display a warning if we get to the end without encountering a delimiter. */ - while (isspace((int)*(unsigned char *)p)) p++; + while (isspace((unsigned char)*p)) p++; if (p >= end_p) { if (key != regex) { zend_string_release_ex(key, 0); @@ -633,7 +633,7 @@ PHPAPI pcre_cache_entry* pcre_get_compiled_regex_cache_ex(zend_string *regex, bo /* Get the delimiter and display a warning if it is alphanumeric or a backslash. */ delimiter = *p++; - if (isalnum((int)*(unsigned char *)&delimiter) || delimiter == '\\' || delimiter == '\0') { + if (isalnum((unsigned char)delimiter) || delimiter == '\\' || delimiter == '\0') { if (key != regex) { zend_string_release_ex(key, 0); } diff --git a/ext/pdo/pdo.c b/ext/pdo/pdo.c index 9a5f814304fc..5b881b0f56da 100644 --- a/ext/pdo/pdo.c +++ b/ext/pdo/pdo.c @@ -239,7 +239,7 @@ PDO_API int php_pdo_parse_data_source(const char *data_source, zend_ulong data_s } } - while (i < data_source_len && isspace(data_source[i])) { + while (i < data_source_len && isspace((unsigned char)data_source[i])) { i++; } diff --git a/ext/pdo/pdo_sql_parser.re b/ext/pdo/pdo_sql_parser.re index d154a85287f2..d77425531cc4 100644 --- a/ext/pdo/pdo_sql_parser.re +++ b/ext/pdo/pdo_sql_parser.re @@ -115,7 +115,7 @@ PDO_API int pdo_parse_params(pdo_stmt_t *stmt, zend_string *inquery, zend_string if (t == PDO_PARSER_BIND) { ptrdiff_t len = s.cur - s.tok; - if ((ZSTR_VAL(inquery) < (s.cur - len)) && isalnum(*(s.cur - len - 1))) { + if ((ZSTR_VAL(inquery) < (s.cur - len)) && isalnum((unsigned char)s.cur[-len - 1])) { continue; } query_type |= PDO_PLACEHOLDER_NAMED; diff --git a/ext/pdo_firebird/firebird_driver.c b/ext/pdo_firebird/firebird_driver.c index 8193132beaf4..c20969aac2b0 100644 --- a/ext/pdo_firebird/firebird_driver.c +++ b/ext/pdo_firebird/firebird_driver.c @@ -293,7 +293,7 @@ static FbTokenType php_firebird_get_token(const char** begin, const char* end) return ret; } -static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTable* named_params) +static int php_firebird_preprocess(const zend_string* sql, char* sql_out, size_t* sql_out_len, HashTable* named_params) { bool passAsIs = true, execBlock = false; zend_long pindex = -1; @@ -324,7 +324,7 @@ static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTa if (l > 252) { return 0; } - strncpy(ident, i, l); + memcpy(ident, i, l); ident[l] = '\0'; if (!strcasecmp(ident, "EXECUTE")) { @@ -349,7 +349,7 @@ static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTa if (l > 252) { return 0; } - strncpy(ident2, i2, l); + memcpy(ident2, i2, l); ident2[l] = '\0'; execBlock = !strcasecmp(ident2, "BLOCK"); passAsIs = false; @@ -365,11 +365,15 @@ static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTa if (passAsIs) { - strcpy(sql_out, ZSTR_VAL(sql)); + memcpy(sql_out, ZSTR_VAL(sql), ZSTR_LEN(sql)); + sql_out[ZSTR_LEN(sql)] = '\0'; + *sql_out_len = ZSTR_LEN(sql); return 1; } - strncat(sql_out, start, p - start); + char *sql_out_p = sql_out; + memcpy(sql_out_p, start, p - start); + sql_out_p += p - start; while (p < end) { @@ -377,10 +381,12 @@ static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTa tok = php_firebird_get_token(&p, end); switch (tok) { - case ttParamMark: - tok = php_firebird_get_token(&p, end); + case ttParamMark: { + const char* p_peek = p; + tok = php_firebird_get_token(&p_peek, end); if (tok == ttIdent /*|| tok == ttString*/) { + p = p_peek; ++pindex; l = p - start; /* check the length of the identifier */ @@ -389,7 +395,7 @@ static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTa if (l > 253) { return 0; } - strncpy(pname, start, l); + memcpy(pname, start, l); pname[l] = '\0'; if (named_params) { @@ -398,7 +404,7 @@ static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTa zend_hash_str_update(named_params, pname, l, &tmp); } - strcat(sql_out, "?"); + *sql_out_p++ = '?'; } else { @@ -408,10 +414,11 @@ static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTa return 0; } ++pindex; - strncat(sql_out, start, p - start); + memcpy(sql_out_p, start, p - start); + sql_out_p += p - start; } break; - + } case ttIdent: if (execBlock) { @@ -423,11 +430,14 @@ static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTa if (l > 252) { return 0; } - strncpy(ident, start, l); + memcpy(ident, start, l); ident[l] = '\0'; if (!strcasecmp(ident, "AS")) { - strncat(sql_out, start, end - start); + memcpy(sql_out_p, start, end - start); + sql_out_p += end - start; + *sql_out_p = '\0'; + *sql_out_len = sql_out_p - sql_out; return 1; } } @@ -438,7 +448,8 @@ static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTa case ttComment: case ttString: case ttOther: - strncat(sql_out, start, p - start); + memcpy(sql_out_p, start, p - start); + sql_out_p += p - start; break; case ttBrokenComment: @@ -455,6 +466,8 @@ static int php_firebird_preprocess(const zend_string* sql, char* sql_out, HashTa return 0; } } + *sql_out_p = '\0'; + *sql_out_len = sql_out_p - sql_out; return 1; } @@ -789,7 +802,7 @@ static zend_long firebird_handle_doer(pdo_dbh_t *dbh, const zend_string *sql) /* static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *unquoted, enum pdo_param_type paramtype) { size_t qcount = 0; - char const *co, *l, *r; + char const *co, *l; char *c; size_t quotedlen; zend_string *quoted_str; @@ -798,9 +811,15 @@ static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *un return ZSTR_INIT_LITERAL("''", 0); } + const char * const end = ZSTR_VAL(unquoted) + ZSTR_LEN(unquoted); + /* Firebird only requires single quotes to be doubled if string lengths are used */ /* count the number of ' characters */ - for (co = ZSTR_VAL(unquoted); (co = strchr(co,'\'')); qcount++, co++); + for (co = ZSTR_VAL(unquoted); co < end; co++) { + if (*co == '\'') { + qcount++; + } + } if (UNEXPECTED(ZSTR_LEN(unquoted) + 2 > ZSTR_MAX_LEN - qcount)) { return NULL; @@ -812,15 +831,14 @@ static zend_string* firebird_handle_quoter(pdo_dbh_t *dbh, const zend_string *un *c++ = '\''; /* foreach (chunk that ends in a quote) */ - for (l = ZSTR_VAL(unquoted); (r = strchr(l,'\'')); l = r+1) { - strncpy(c, l, r-l+1); - c += (r-l+1); - /* add the second quote */ - *c++ = '\''; + for (l = ZSTR_VAL(unquoted); l < end; l++) { + *c++ = *l; + if (*l == '\'') { + /* add the second quote */ + *c++ = '\''; + } } - /* copy the remainder */ - strncpy(c, l, quotedlen-(c-ZSTR_VAL(quoted_str))-1); ZSTR_VAL(quoted_str)[quotedlen-1] = '\''; ZSTR_VAL(quoted_str)[quotedlen] = '\0'; @@ -998,6 +1016,7 @@ static int php_firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const zend_string *sq { pdo_firebird_db_handle *H = (pdo_firebird_db_handle *)dbh->driver_data; char *new_sql; + size_t new_sql_len; /* allocate the statement */ if (isc_dsql_allocate_statement(H->isc_status, &H->db, s)) { @@ -1009,14 +1028,14 @@ static int php_firebird_alloc_prepare_stmt(pdo_dbh_t *dbh, const zend_string *sq we need to replace :foo by ?, and store the name we just replaced */ new_sql = emalloc(ZSTR_LEN(sql)+1); new_sql[0] = '\0'; - if (!php_firebird_preprocess(sql, new_sql, named_params)) { + if (!php_firebird_preprocess(sql, new_sql, &new_sql_len, named_params)) { php_firebird_error_with_info(dbh, "07000", strlen("07000"), NULL, 0); efree(new_sql); return 0; } /* prepare the statement */ - if (isc_dsql_prepare(H->isc_status, &H->tr, s, 0, new_sql, H->sql_dialect, out_sqlda)) { + if (isc_dsql_prepare(H->isc_status, &H->tr, s, new_sql_len, new_sql, H->sql_dialect, out_sqlda)) { php_firebird_error(dbh); efree(new_sql); return 0; diff --git a/ext/pdo_firebird/tests/ghsa-w476-322c-wpvm.phpt b/ext/pdo_firebird/tests/ghsa-w476-322c-wpvm.phpt new file mode 100644 index 000000000000..3046fbc011b7 --- /dev/null +++ b/ext/pdo_firebird/tests/ghsa-w476-322c-wpvm.phpt @@ -0,0 +1,46 @@ +--TEST-- +GHSA-w476-322c-wpvm: SQL injection in pdo_firebird via NUL bytes in quoted strings +--EXTENSIONS-- +pdo_firebird +--SKIPIF-- + +--XLEAK-- +A bug in firebird causes a memory leak when calling `isc_attach_database()`. +See https://github.com/FirebirdSQL/firebird/issues/7849 +--FILE-- +exec('CREATE TABLE ghsa_w476_322c_wpvm (name VARCHAR(255))'); + +$param = $dbh->quote("\0"); +$param2 = $dbh->quote('or 1=1--'); +var_export($param); +echo("\n"); + +echo "prepare: "; +$stmt = $dbh->prepare("SELECT * FROM ghsa_w476_322c_wpvm WHERE name = {$param} AND name = {$param2}"); +$stmt->execute(); +echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)) . "\n"; + +echo "query: "; +$stmt = $dbh->query("SELECT * FROM ghsa_w476_322c_wpvm WHERE name = {$param} AND name = {$param2}"); +echo json_encode($stmt->fetchAll(PDO::FETCH_ASSOC)) . "\n"; + +echo "exec: "; +$affectedRows = $dbh->exec("UPDATE ghsa_w476_322c_wpvm SET name = 'updated' WHERE name = {$param} AND name = {$param2}"); +echo $affectedRows . "\n"; +?> +--CLEAN-- +exec("DROP TABLE ghsa_w476_322c_wpvm"); +?> +--EXPECT-- +'\'' . "\0" . '\'' +prepare: [] +query: [] +exec: 0 diff --git a/ext/soap/php_encoding.c b/ext/soap/php_encoding.c index 1836ef3af7e8..fece87e9797e 100644 --- a/ext/soap/php_encoding.c +++ b/ext/soap/php_encoding.c @@ -349,6 +349,7 @@ static bool soap_check_xml_ref(zval *data, xmlNodePtr node) static void soap_add_xml_ref(zval *data, xmlNodePtr node) { if (SOAP_GLOBAL(ref_map)) { + Z_TRY_ADDREF_P(data); zend_hash_index_update(SOAP_GLOBAL(ref_map), (zend_ulong)(uintptr_t)node, data); } } @@ -2783,7 +2784,7 @@ static zval *to_zval_map(zval *ret, encodeTypePtr type, xmlNodePtr data) } xmlValue = get_node(item->children, "value"); - if (!xmlKey) { + if (!xmlValue) { soap_error0(E_ERROR, "Encoding: Can't decode apache map, missing value"); } @@ -3520,7 +3521,7 @@ void encode_reset_ns(void) } else { SOAP_GLOBAL(ref_map) = emalloc(sizeof(HashTable)); } - zend_hash_init(SOAP_GLOBAL(ref_map), 0, NULL, NULL, 0); + zend_hash_init(SOAP_GLOBAL(ref_map), 0, NULL, ZVAL_PTR_DTOR, 0); } void encode_finish(void) diff --git a/ext/soap/soap.c b/ext/soap/soap.c index cbe8b05312ff..5cc87e7cc905 100644 --- a/ext/soap/soap.c +++ b/ext/soap/soap.c @@ -1592,12 +1592,20 @@ PHP_METHOD(SoapServer, handle) instanceof_function(Z_OBJCE(h->retval), soap_fault_class_entry)) { php_output_discard(); soap_server_fault_ex(function, &h->retval, h); - if (service->type == SOAP_CLASS && soap_obj) {zval_ptr_dtor(soap_obj);} + if (service->type == SOAP_CLASS && soap_obj) { + if (service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { + zval_ptr_dtor(soap_obj); + } + } goto fail; } else if (EG(exception)) { php_output_discard(); _soap_server_exception(service, function, ZEND_THIS); - if (service->type == SOAP_CLASS && soap_obj) {zval_ptr_dtor(soap_obj);} + if (service->type == SOAP_CLASS && soap_obj) { + if (service->soap_class.persistence != SOAP_PERSISTENCE_SESSION) { + zval_ptr_dtor(soap_obj); + } + } goto fail; } } else if (h->mustUnderstand) { diff --git a/ext/soap/tests/GHSA-85c2-q967-79q5.phpt b/ext/soap/tests/GHSA-85c2-q967-79q5.phpt new file mode 100644 index 000000000000..8bcac26ad187 --- /dev/null +++ b/ext/soap/tests/GHSA-85c2-q967-79q5.phpt @@ -0,0 +1,61 @@ +--TEST-- +GHSA-85c2-q967-79q5: Stale SOAP_GLOBAL(ref_map) pointer with Apache Map +--CREDITS-- +brettgervasoni +--EXTENSIONS-- +soap +--FILE-- + + + + + + + + foo + bar + + + foo + baz + + + + + + +XML; + +$s = new SoapServer(null, ['uri' => 'urn:a']); +$s->setClass(Handler::class); +$s->handle($envelope); +var_dump($result); + +?> +--EXPECTF-- + + +array(2) { + [0]=> + array(1) { + ["foo"]=> + string(3) "baz" + } + [1]=> + object(stdClass)#%d (1) { + ["object"]=> + string(3) "bar" + } +} diff --git a/ext/soap/tests/GHSA-hmxp-6pc4-f3vv.phpt b/ext/soap/tests/GHSA-hmxp-6pc4-f3vv.phpt new file mode 100644 index 000000000000..e46ab2e4607d --- /dev/null +++ b/ext/soap/tests/GHSA-hmxp-6pc4-f3vv.phpt @@ -0,0 +1,39 @@ +--TEST-- +GHSA-hmxp-6pc4-f3vv: Null pointer dereference on missing Apache map value +--CREDITS-- +Ilia Alshanetsky (iliaal) +--EXTENSIONS-- +soap +--FILE-- + + + + + + + hello + + + + +XML; + +$server = new SoapServer(null, [ + 'uri' => 'urn:test', + 'typemap' => [['type_name' => 'anything']], +]); +$server->addFunction('test'); +function test($m) { return null; } +$server->handle($request); + +?> +--EXPECT-- + +SOAP-ENV:ServerSOAP-ERROR: Encoding: Can't decode apache map, missing value diff --git a/ext/soap/tests/GHSA-m33r-qmcv-p97q.phpt b/ext/soap/tests/GHSA-m33r-qmcv-p97q.phpt new file mode 100644 index 000000000000..bcf441ccd18a --- /dev/null +++ b/ext/soap/tests/GHSA-m33r-qmcv-p97q.phpt @@ -0,0 +1,58 @@ +--TEST-- +GHSA-m33r-qmcv-p97q: Use-after-free after header parsing failure with SOAP_PERSISTENCE_SESSION +--CREDITS-- +Ilia Alshanetsky (iliaal) +--EXTENSIONS-- +soap +session +--FILE-- + 'urn:a']); +$srv->setClass(Handler::class); +$srv->setPersistence(SOAP_PERSISTENCE_SESSION); + +$srv->handle(<< + + + + + + + + +XML); + +$srv->handle(<< + + + + + + + + +XML); + +?> +--EXPECT-- + +SOAP-ENV:Serverdenied + +SOAP-ENV:Serverdenied diff --git a/ext/standard/dl.c b/ext/standard/dl.c index 63f627e51d88..a6d0ced6fa86 100644 --- a/ext/standard/dl.c +++ b/ext/standard/dl.c @@ -91,7 +91,7 @@ PHPAPI void *php_load_shlib(const char *path, char **errp) size_t i = strlen(err); (*errp)=estrdup(err); php_win32_error_msg_free(err); - while (i > 0 && isspace((*errp)[i-1])) { (*errp)[i-1] = '\0'; i--; } + while (i > 0 && isspace((unsigned char)(*errp)[i-1])) { (*errp)[i-1] = '\0'; i--; } } else { (*errp) = estrdup(""); } diff --git a/ext/standard/exec.c b/ext/standard/exec.c index 0e5903420a3e..5c9e5c6076a8 100644 --- a/ext/standard/exec.c +++ b/ext/standard/exec.c @@ -79,7 +79,7 @@ PHP_MINIT_FUNCTION(exec) static size_t strip_trailing_whitespace(char *buf, size_t bufl) { size_t l = bufl; - while (l-- > 0 && isspace(((unsigned char *)buf)[l])); + while (l-- > 0 && isspace((unsigned char)buf[l])); if (l != (bufl - 1)) { bufl = l + 1; buf[bufl] = '\0'; diff --git a/ext/standard/file.c b/ext/standard/file.c index 1841c242b870..ba5b26f6d222 100644 --- a/ext/standard/file.c +++ b/ext/standard/file.c @@ -1860,7 +1860,7 @@ PHPAPI HashTable *php_fgetcsv(php_stream *stream, char delimiter, char enclosure inc_len = (bptr < limit ? (*bptr == '\0' ? 1 : php_mblen(bptr, limit - bptr)): 0); if (inc_len == 1) { char *tmp = bptr; - while ((*tmp != delimiter) && isspace((int)*(unsigned char *)tmp)) { + while ((*tmp != delimiter) && isspace((unsigned char)*tmp)) { tmp++; } if (*tmp == enclosure && tmp < limit) { diff --git a/ext/standard/filters.c b/ext/standard/filters.c index 90dd471cf848..5ee7e93c40ce 100644 --- a/ext/standard/filters.c +++ b/ext/standard/filters.c @@ -1000,7 +1000,7 @@ static php_conv_err_t php_conv_qprint_decode_convert(php_conv_qprint_decode *ins goto out; } - if (!isxdigit((int) *ps)) { + if (!isxdigit(*ps)) { err = PHP_CONV_ERR_INVALID_SEQ; goto out; } diff --git a/ext/standard/formatted_print.c b/ext/standard/formatted_print.c index 4324e7c14dd7..f35d196fb971 100644 --- a/ext/standard/formatted_print.c +++ b/ext/standard/formatted_print.c @@ -374,7 +374,7 @@ php_sprintf_getnumber(char **buffer, size_t *len) int php_sprintf_get_argnum(char **format, size_t *format_len) { char *temppos = *format; - while (isdigit((int) *temppos)) temppos++; + while (isdigit((unsigned char)*temppos)) temppos++; if (*temppos != '$') { return ARG_NUM_NEXT; } @@ -466,7 +466,7 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n PRINTF_DEBUG(("sprintf: first looking at '%c', inpos=%zu\n", *format, format - format_orig)); - if (isalpha((int)*format)) { + if (isalpha((unsigned char)*format)) { width = precision = 0; argnum = ARG_NUM_NEXT; } else { @@ -535,7 +535,7 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n } width = Z_LVAL_P(tmp); adjusting |= ADJ_WIDTH; - } else if (isdigit((int)*format)) { + } else if (isdigit((unsigned char)*format)) { PRINTF_DEBUG(("sprintf: getting width\n")); if ((width = php_sprintf_getnumber(&format, &format_len)) < 0) { zend_value_error("Width must be between 0 and %d", INT_MAX); @@ -580,7 +580,7 @@ php_formatted_print(char *format, size_t format_len, zval *args, int argc, int n precision = Z_LVAL_P(tmp); adjusting |= ADJ_PRECISION; expprec = 1; - } else if (isdigit((int)*format)) { + } else if (isdigit((unsigned char)*format)) { if ((precision = php_sprintf_getnumber(&format, &format_len)) < 0) { zend_value_error("Precision must be between 0 and %d", INT_MAX); goto fail; diff --git a/ext/standard/ftp_fopen_wrapper.c b/ext/standard/ftp_fopen_wrapper.c index 73407aaa401c..e1f3a88d2497 100644 --- a/ext/standard/ftp_fopen_wrapper.c +++ b/ext/standard/ftp_fopen_wrapper.c @@ -77,8 +77,8 @@ static inline int get_ftp_result(php_stream *stream, char *buffer, size_t buffer { buffer[0] = '\0'; /* in case read fails to read anything */ while (php_stream_gets(stream, buffer, buffer_size-1) && - !(isdigit((int) buffer[0]) && isdigit((int) buffer[1]) && - isdigit((int) buffer[2]) && buffer[3] == ' ')); + !(isdigit((unsigned char)buffer[0]) && isdigit((unsigned char)buffer[1]) && + isdigit((unsigned char)buffer[2]) && buffer[3] == ' ')); return strtol(buffer, NULL, 10); } /* }}} */ @@ -233,7 +233,7 @@ static php_stream *php_ftp_fopen_connect(php_stream_wrapper *wrapper, const char #define PHP_FTP_CNTRL_CHK(val, val_len, err_msg) { \ unsigned char *s = (unsigned char *) val, *e = (unsigned char *) s + val_len; \ while (s < e) { \ - if (iscntrl(*s)) { \ + if (iscntrl((unsigned char)*s)) { \ php_stream_wrapper_log_error(wrapper, options, err_msg, val); \ goto connect_errexit; \ } \ @@ -342,14 +342,14 @@ static unsigned short php_fopen_do_pasv(php_stream *stream, char *ip, size_t ip_ /* parse pasv command (129, 80, 95, 25, 13, 221) */ tpath = tmp_line; /* skip over the "227 Some message " part */ - for (tpath += 4; *tpath && !isdigit((int) *tpath); tpath++); + for (tpath += 4; *tpath && !isdigit((unsigned char)*tpath); tpath++); if (!*tpath) { return 0; } /* skip over the host ip, to get the port */ hoststart = tpath; for (i = 0; i < 4; i++) { - for (; isdigit((int) *tpath); tpath++); + for (; isdigit((unsigned char)*tpath); tpath++); if (*tpath != ',') { return 0; } @@ -830,7 +830,7 @@ static int php_stream_ftp_url_stat(php_stream_wrapper *wrapper, const char *url, struct tm tm, tmbuf, *gmt; time_t stamp; - while ((size_t)(p - tmp_line) < sizeof(tmp_line) && !isdigit(*p)) { + while ((size_t)(p - tmp_line) < sizeof(tmp_line) && !isdigit((unsigned char)*p)) { p++; } diff --git a/ext/standard/html.c b/ext/standard/html.c index c8920f497d75..1e761c971eda 100644 --- a/ext/standard/html.c +++ b/ext/standard/html.c @@ -676,8 +676,8 @@ static inline zend_result process_numeric_entity(const char **buf, unsigned *cod /* strtol allows whitespace and other stuff in the beginning * we're not interested */ - if ((hexadecimal && !isxdigit(**buf)) || - (!hexadecimal && !isdigit(**buf))) { + if ((hexadecimal && !isxdigit((unsigned char)**buf)) || + (!hexadecimal && !isdigit((unsigned char)**buf))) { return FAILURE; } diff --git a/ext/standard/math.c b/ext/standard/math.c index 66fbfeaa9428..1898d210ce65 100644 --- a/ext/standard/math.c +++ b/ext/standard/math.c @@ -859,9 +859,9 @@ PHPAPI void _php_math_basetozval(zend_string *str, int base, zval *ret) e = s + ZSTR_LEN(str); /* Skip leading whitespace */ - while (s < e && isspace(*s)) s++; + while (s < e && isspace((unsigned char)*s)) s++; /* Skip trailing whitespace */ - while (s < e && isspace(*(e-1))) e--; + while (s < e && isspace((unsigned char)e[-1])) e--; if (e - s >= 2) { if (base == 16 && s[0] == '0' && (s[1] == 'x' || s[1] == 'X')) s += 2; @@ -1174,7 +1174,7 @@ PHPAPI zend_string *_php_math_number_format_ex(double d, int dec, const char *de tmpbuf = strpprintf(0, "%.*F", dec, d); if (tmpbuf == NULL) { return NULL; - } else if (!isdigit((int)ZSTR_VAL(tmpbuf)[0])) { + } else if (!isdigit((unsigned char)ZSTR_VAL(tmpbuf)[0])) { return tmpbuf; } diff --git a/ext/standard/metaphone.c b/ext/standard/metaphone.c index 816e5d28220e..8f33057de5ac 100644 --- a/ext/standard/metaphone.c +++ b/ext/standard/metaphone.c @@ -78,7 +78,7 @@ static const char _codes[26] = /* Note: these functions require an uppercase letter input! */ static zend_always_inline char encode(char c) { - if (isalpha(c)) { + if (isalpha((unsigned char)c)) { ZEND_ASSERT(c >= 'A' && c <= 'Z'); return _codes[(c - 'A')]; } else { @@ -107,7 +107,7 @@ static zend_always_inline char encode(char c) { /* I suppose I could have been using a character pointer instead of * accessing the array directly... */ -#define Convert_Raw(c) toupper(c) +#define Convert_Raw(c) toupper((unsigned char)c) /* Look at the next letter in the word */ #define Read_Raw_Next_Letter (word[w_idx+1]) #define Read_Next_Letter (Convert_Raw(Read_Raw_Next_Letter)) @@ -121,14 +121,14 @@ static zend_always_inline char encode(char c) { /* Look two letters down. It makes sure you don't walk off the string. */ #define Read_After_Next_Letter (Read_Raw_Next_Letter != '\0' ? Convert_Raw(word[w_idx+2]) \ : '\0') -#define Look_Ahead_Letter(n) (toupper(Lookahead((char *) word+w_idx, n))) +#define Look_Ahead_Letter(n) (toupper((unsigned char)Lookahead((char *) word+w_idx, n))) /* Allows us to safely look ahead an arbitrary # of letters */ /* I probably could have just used strlen... */ -static char Lookahead(char *word, int how_far) +static char Lookahead(char *word, size_t how_far) { - int idx; + size_t idx; for (idx = 0; word[idx] != '\0' && idx < how_far; idx++); /* Edge forward in the string... */ @@ -163,12 +163,12 @@ static char Lookahead(char *word, int how_far) #define Phone_Len (p_idx) /* Note is a letter is a 'break' in the word */ -#define Isbreak(c) (!isalpha(c)) +#define Isbreak(c) (!isalpha((unsigned char)(c))) /* {{{ metaphone */ static void metaphone(unsigned char *word, size_t word_len, zend_long max_phonemes, zend_string **phoned_word, int traditional) { - int w_idx = 0; /* point in the phonization we're at. */ + size_t w_idx = 0; /* point in the phonization we're at. */ size_t p_idx = 0; /* end of the phoned phrase */ size_t max_buffer_len = 0; /* maximum length of the destination buffer */ char curr_letter; @@ -187,7 +187,7 @@ static void metaphone(unsigned char *word, size_t word_len, zend_long max_phonem /*-- The first phoneme has to be processed specially. --*/ /* Find our first letter */ - for (; !isalpha(curr_letter = Read_Raw_Curr_Letter); w_idx++) { + for (; !isalpha((unsigned char)(curr_letter = Read_Raw_Curr_Letter)); w_idx++) { /* On the off chance we were given nothing but crap... */ if (curr_letter == '\0') { End_Phoned_Word(); @@ -275,7 +275,7 @@ static void metaphone(unsigned char *word, size_t word_len, zend_long max_phonem */ /* Ignore non-alphas */ - if (!isalpha(curr_letter)) + if (!isalpha((unsigned char)curr_letter)) continue; curr_letter = Convert_Raw(curr_letter); diff --git a/ext/standard/quot_print.c b/ext/standard/quot_print.c index f4954a88f8fa..e9d8fafb9987 100644 --- a/ext/standard/quot_print.c +++ b/ext/standard/quot_print.c @@ -210,11 +210,11 @@ PHP_FUNCTION(quoted_printable_decode) switch (str_in[i]) { case '=': if (str_in[i + 1] && str_in[i + 2] && - isxdigit((int) str_in[i + 1]) && - isxdigit((int) str_in[i + 2])) + isxdigit((unsigned char)str_in[i + 1]) && + isxdigit((unsigned char)str_in[i + 2])) { - ZSTR_VAL(str_out)[j++] = (php_hex2int((int) str_in[i + 1]) << 4) - + php_hex2int((int) str_in[i + 2]); + ZSTR_VAL(str_out)[j++] = (php_hex2int((unsigned char)str_in[i + 1]) << 4) + + php_hex2int((unsigned char)str_in[i + 2]); i += 3; } else /* check for soft line break according to RFC 2045*/ { k = 1; diff --git a/ext/standard/scanf.c b/ext/standard/scanf.c index 980009c30640..5aeb585fea23 100644 --- a/ext/standard/scanf.c +++ b/ext/standard/scanf.c @@ -343,7 +343,7 @@ PHPAPI int ValidateFormat(char *format, int numVars, int *totalSubs) goto xpgCheckDone; } - if ( isdigit( (int)*ch ) ) { + if ( isdigit( (unsigned char)*ch ) ) { /* * Check for an XPG3-style %n$ specification. Note: there * must not be a mixture of XPG3 specs and non-XPG3 specs @@ -654,9 +654,9 @@ PHPAPI int php_sscanf_internal( char *string, char *format, /* * If we see whitespace in the format, skip whitespace in the string. */ - if ( isspace( (int)*ch ) ) { + if ( isspace( (unsigned char)*ch ) ) { sch = *string; - while ( isspace( (int)sch ) ) { + while ( isspace( (unsigned char)sch ) ) { if (*string == '\0') { goto done; } @@ -807,7 +807,7 @@ PHPAPI int php_sscanf_internal( char *string, char *format, if (!(flags & SCAN_NOSKIP)) { while (*string != '\0') { sch = *string; - if (! isspace((int)sch) ) { + if (! isspace((unsigned char)sch) ) { break; } string++; @@ -833,7 +833,7 @@ PHPAPI int php_sscanf_internal( char *string, char *format, end = string; while (*end != '\0') { sch = *end; - if ( isspace( (int)sch ) ) { + if ( isspace( (unsigned char)sch ) ) { break; } end++; diff --git a/ext/standard/soundex.c b/ext/standard/soundex.c index 527ef9537af3..f06c3ac98572 100644 --- a/ext/standard/soundex.c +++ b/ext/standard/soundex.c @@ -63,7 +63,7 @@ PHP_FUNCTION(soundex) /* BUG: should also map here accented letters used in non */ /* English words or names (also found in English text!): */ /* esstsett, thorn, n-tilde, c-cedilla, s-caron, ... */ - code = toupper((int)(unsigned char)str[i]); + code = toupper((unsigned char)str[i]); if (code >= 'A' && code <= 'Z') { if (_small == 0) { /* remember first valid char */ diff --git a/ext/standard/string.c b/ext/standard/string.c index 89b4e51e6c2c..7ded14366a9f 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -3760,9 +3760,9 @@ PHPAPI void php_stripcslashes(zend_string *str) case 'f': *target++='\f'; nlen--; break; case '\\': *target++='\\'; nlen--; break; case 'x': - if (source+1 < end && isxdigit((int)(*(source+1)))) { + if (source+1 < end && isxdigit((unsigned char)source[1])) { numtmp[0] = *++source; - if (source+1 < end && isxdigit((int)(*(source+1)))) { + if (source+1 < end && isxdigit((unsigned char)source[1])) { numtmp[1] = *++source; numtmp[2] = '\0'; nlen-=3; @@ -4617,7 +4617,7 @@ PHP_FUNCTION(hebrev) do { if (block_type == _HEB_BLOCK_TYPE_HEB) { - while ((isheb((int)*(tmp+1)) || _isblank((int)*(tmp+1)) || ispunct((int)*(tmp+1)) || (int)*(tmp+1)=='\n' ) && block_end block_start) { + while ((_isblank((int)*tmp) || ispunct((unsigned char)*tmp)) && *tmp!='/' && *tmp!='-' && block_end > block_start) { tmp--; block_end--; } @@ -5067,7 +5067,7 @@ static bool php_tag_find(char *tag, size_t len, const char *set) { done = true; break; default: - if (!isspace((int)c)) { + if (!isspace((unsigned char)c)) { if (state == 0) { state=1; } @@ -5157,7 +5157,7 @@ PHPAPI size_t php_strip_tags_ex(char *rbuf, size_t len, const char *allow, size_ if (in_q) { break; } - if (isspace(*(p + 1)) && !allow_tag_spaces) { + if (isspace((unsigned char)p[1]) && !allow_tag_spaces) { *(rp++) = c; break; } @@ -5204,7 +5204,7 @@ PHPAPI size_t php_strip_tags_ex(char *rbuf, size_t len, const char *allow, size_ if (in_q) { break; } - if (isspace(*(p + 1)) && !allow_tag_spaces) { + if (isspace((unsigned char)p[1]) && !allow_tag_spaces) { goto reg_char_1; } depth++; diff --git a/ext/standard/strnatcmp.c b/ext/standard/strnatcmp.c index 3c3f5a99232f..583cbb5dc47a 100644 --- a/ext/standard/strnatcmp.c +++ b/ext/standard/strnatcmp.c @@ -40,12 +40,12 @@ compare_right(char const **a, char const *aend, char const **b, char const *bend both numbers to know that they have the same magnitude, so we remember it in BIAS. */ for(;; (*a)++, (*b)++) { - if ((*a == aend || !isdigit((int)(unsigned char)**a)) && - (*b == bend || !isdigit((int)(unsigned char)**b))) + if ((*a == aend || !isdigit((unsigned char)**a)) && + (*b == bend || !isdigit((unsigned char)**b))) return bias; - else if (*a == aend || !isdigit((int)(unsigned char)**a)) + else if (*a == aend || !isdigit((unsigned char)**a)) return -1; - else if (*b == bend || !isdigit((int)(unsigned char)**b)) + else if (*b == bend || !isdigit((unsigned char)**b)) return +1; else if (**a < **b) { if (!bias) @@ -67,12 +67,12 @@ compare_left(char const **a, char const *aend, char const **b, char const *bend) /* Compare two left-aligned numbers: the first to have a different value wins. */ for(;; (*a)++, (*b)++) { - if ((*a == aend || !isdigit((int)(unsigned char)**a)) && - (*b == bend || !isdigit((int)(unsigned char)**b))) + if ((*a == aend || !isdigit((unsigned char)**a)) && + (*b == bend || !isdigit((unsigned char)**b))) return 0; - else if (*a == aend || !isdigit((int)(unsigned char)**a)) + else if (*a == aend || !isdigit((unsigned char)**a)) return -1; - else if (*b == bend || !isdigit((int)(unsigned char)**b)) + else if (*b == bend || !isdigit((unsigned char)**b)) return +1; else if (**a < **b) return -1; @@ -103,27 +103,27 @@ PHPAPI int strnatcmp_ex(char const *a, size_t a_len, char const *b, size_t b_len ca = *ap; cb = *bp; /* skip over leading zeros */ - while (ca == '0' && (ap+1 < aend) && isdigit((int)(unsigned char)*(ap+1))) { + while (ca == '0' && (ap+1 < aend) && isdigit((unsigned char)ap[1])) { ca = *++ap; } - while (cb == '0' && (bp+1 < bend) && isdigit((int)(unsigned char)*(bp+1))) { + while (cb == '0' && (bp+1 < bend) && isdigit((unsigned char)bp[1])) { cb = *++bp; } while (1) { /* Skip consecutive whitespace */ - while (isspace((int)(unsigned char)ca)) { + while (isspace(ca)) { ca = *++ap; } - while (isspace((int)(unsigned char)cb)) { + while (isspace(cb)) { cb = *++bp; } /* process run of digits */ - if (isdigit((int)(unsigned char)ca) && isdigit((int)(unsigned char)cb)) { + if (isdigit(ca) && isdigit(cb)) { fractional = (ca == '0' || cb == '0'); if (fractional) @@ -147,8 +147,8 @@ PHPAPI int strnatcmp_ex(char const *a, size_t a_len, char const *b, size_t b_len } if (is_case_insensitive) { - ca = toupper((int)(unsigned char)ca); - cb = toupper((int)(unsigned char)cb); + ca = toupper(ca); + cb = toupper(cb); } if (ca < cb) diff --git a/ext/standard/tests/GHSA-96wq-48vp-hh57.phpt b/ext/standard/tests/GHSA-96wq-48vp-hh57.phpt new file mode 100644 index 000000000000..cf9a40062f84 --- /dev/null +++ b/ext/standard/tests/GHSA-96wq-48vp-hh57.phpt @@ -0,0 +1,22 @@ +--TEST-- +GHSA-96wq-48vp-hh57: signed integer overflow of char array offset +--CREDITS-- +Aleksey Solovev (Positive Technologies) +--INI-- +memory_limit=3G +--SKIPIF-- + +--FILE-- + +===DONE=== +--EXPECT-- +===DONE=== diff --git a/ext/standard/type.c b/ext/standard/type.c index 7546216e5049..fc681a3c50a7 100644 --- a/ext/standard/type.c +++ b/ext/standard/type.c @@ -157,7 +157,7 @@ PHP_FUNCTION(intval) char *strval = Z_STRVAL_P(num); size_t strlen = Z_STRLEN_P(num); - while (isspace(*strval) && strlen) { + while (isspace((unsigned char)*strval) && strlen) { strval++; strlen--; } diff --git a/ext/standard/url.c b/ext/standard/url.c index 089dca315f43..ac303027a722 100644 --- a/ext/standard/url.c +++ b/ext/standard/url.c @@ -101,7 +101,7 @@ PHPAPI php_url *php_url_parse_ex2(char const *str, size_t length, bool *has_port p = s; while (p < e) { /* scheme = 1*[ lowalpha | digit | "+" | "-" | "." ] */ - if (!isalpha(*p) && !isdigit(*p) && *p != '+' && *p != '.' && *p != '-') { + if (!isalpha((unsigned char)*p) && !isdigit((unsigned char)*p) && *p != '+' && *p != '.' && *p != '-') { if (e + 1 < ue && e < binary_strcspn(s, ue, "?#")) { goto parse_port; } else if (s + 1 < ue && *s == '/' && *(s + 1) == '/') { /* relative-scheme URL */ @@ -130,7 +130,7 @@ PHPAPI php_url *php_url_parse_ex2(char const *str, size_t length, bool *has_port * correctly parse things like a.com:80 */ p = e + 1; - while (p < ue && isdigit(*p)) { + while (p < ue && isdigit((unsigned char)*p)) { p++; } @@ -170,7 +170,7 @@ PHPAPI php_url *php_url_parse_ex2(char const *str, size_t length, bool *has_port p = e + 1; pp = p; - while (pp < ue && pp - p < 6 && isdigit(*pp)) { + while (pp < ue && pp - p < 6 && isdigit((unsigned char)*pp)) { pp++; } @@ -589,8 +589,8 @@ PHPAPI size_t php_url_decode_ex(char *dest, const char *src, size_t src_len) if (*data == '+') { *dest = ' '; } - else if (*data == '%' && src_len >= 2 && isxdigit((int) *(data + 1)) - && isxdigit((int) *(data + 2))) { + else if (*data == '%' && src_len >= 2 && isxdigit((unsigned char)data[1]) + && isxdigit((unsigned char)data[2])) { *dest = (char) php_htoi(data + 1); data += 2; src_len -= 2; @@ -662,8 +662,8 @@ PHPAPI size_t php_raw_url_decode_ex(char *dest, const char *src, size_t src_len) const char *data = src; while (src_len--) { - if (*data == '%' && src_len >= 2 && isxdigit((int) *(data + 1)) - && isxdigit((int) *(data + 2))) { + if (*data == '%' && src_len >= 2 && isxdigit((unsigned char)data[1]) + && isxdigit((unsigned char)data[2])) { *dest = (char) php_htoi(data + 1); data += 2; src_len -= 2; @@ -730,7 +730,7 @@ PHP_FUNCTION(get_headers) c = *p; *p = '\0'; s = p + 1; - while (isspace((int)*(unsigned char *)s)) { + while (isspace((unsigned char)*s)) { s++; } diff --git a/ext/standard/url_scanner_ex.re b/ext/standard/url_scanner_ex.re index 137bfd798147..506f2d08378e 100644 --- a/ext/standard/url_scanner_ex.re +++ b/ext/standard/url_scanner_ex.re @@ -83,7 +83,7 @@ static zend_result php_ini_on_update_tags(zend_ini_entry *entry, zend_string *ne *val++ = '\0'; for (q = key; *q; q++) { - *q = tolower(*q); + *q = tolower((unsigned char)*q); } keylen = q - key; str = zend_string_init(key, keylen, 1); @@ -135,7 +135,7 @@ static zend_result php_ini_on_update_hosts(zend_ini_entry *entry, zend_string *n char *q; for (q = key; *q; q++) { - *q = tolower(*q); + *q = tolower((unsigned char)*q); } keylen = q - key; if (keylen > 0) { @@ -461,7 +461,7 @@ static inline void handle_tag(STD_PARA) } smart_str_appendl(&ctx->tag, start, YYCURSOR - start); for (i = 0; i < ZSTR_LEN(ctx->tag.s); i++) - ZSTR_VAL(ctx->tag.s)[i] = tolower((int)(unsigned char)ZSTR_VAL(ctx->tag.s)[i]); + ZSTR_VAL(ctx->tag.s)[i] = tolower((unsigned char)ZSTR_VAL(ctx->tag.s)[i]); /* intentionally using str_find here, in case the hash value is set, but the string val is changed later */ if ((ctx->lookup_data = zend_hash_str_find_ptr(ctx->tags, ZSTR_VAL(ctx->tag.s), ZSTR_LEN(ctx->tag.s))) != NULL) { ok = 1; diff --git a/ext/standard/versioning.c b/ext/standard/versioning.c index 99b4a053dd44..dc7ca71af469 100644 --- a/ext/standard/versioning.c +++ b/ext/standard/versioning.c @@ -43,8 +43,8 @@ php_canonicalize_version(const char *version) * s/([^\d\.])([^\D\.])/$1.$2/g; * s/([^\D\.])([^\d\.])/$1.$2/g; */ -#define isdig(x) (isdigit(x)&&(x)!='.') -#define isndig(x) (!isdigit(x)&&(x)!='.') +#define isdig(x) (isdigit((unsigned char)(x))&&(x)!='.') +#define isndig(x) (!isdigit((unsigned char)(x))&&(x)!='.') #define isspecialver(x) ((x)=='-'||(x)=='_'||(x)=='+') lq = *(q - 1); @@ -57,7 +57,7 @@ php_canonicalize_version(const char *version) *q++ = '.'; } *q++ = *p; - } else if (!isalnum(*p)) { + } else if (!isalnum((unsigned char)*p)) { if (lq != '.') { *q++ = '.'; } @@ -158,17 +158,17 @@ php_version_compare(const char *orig_ver1, const char *orig_ver2) if ((n2 = strchr(p2, '.')) != NULL) { *n2 = '\0'; } - if (isdigit(*p1) && isdigit(*p2)) { + if (isdigit((unsigned char)*p1) && isdigit((unsigned char)*p2)) { /* compare element numerically */ l1 = strtol(p1, NULL, 10); l2 = strtol(p2, NULL, 10); compare = ZEND_NORMALIZE_BOOL(l1 - l2); - } else if (!isdigit(*p1) && !isdigit(*p2)) { + } else if (!isdigit((unsigned char)*p1) && !isdigit((unsigned char)*p2)) { /* compare element names */ compare = compare_special_version_forms(p1, p2); } else { /* mix of names and numbers */ - if (isdigit(*p1)) { + if (isdigit((unsigned char)*p1)) { compare = compare_special_version_forms("#N#", p2); } else { compare = compare_special_version_forms(p1, "#N#"); @@ -186,13 +186,13 @@ php_version_compare(const char *orig_ver1, const char *orig_ver2) } if (compare == 0) { if (n1 != NULL) { - if (isdigit(*p1)) { + if (isdigit((unsigned char)*p1)) { compare = 1; } else { compare = php_version_compare(p1, "#N#"); } } else if (n2 != NULL) { - if (isdigit(*p2)) { + if (isdigit((unsigned char)*p2)) { compare = -1; } else { compare = php_version_compare("#N#", p2); diff --git a/main/SAPI.c b/main/SAPI.c index 144f727dd1fe..62e1c89e4bb9 100644 --- a/main/SAPI.c +++ b/main/SAPI.c @@ -189,7 +189,7 @@ SAPI_API void sapi_read_post_data(void) *p = 0; break; default: - *p = tolower(*p); + *p = tolower((unsigned char)*p); break; } } @@ -731,10 +731,10 @@ SAPI_API int sapi_header_op(sapi_header_op_enum op, void *arg) } /* cut off trailing spaces, linefeeds and carriage-returns */ - if (header_line_len && isspace(header_line[header_line_len-1])) { + if (header_line_len && isspace((unsigned char)header_line[header_line_len - 1])) { do { header_line_len--; - } while(header_line_len && isspace(header_line[header_line_len-1])); + } while(header_line_len && isspace((unsigned char)header_line[header_line_len - 1])); header_line[header_line_len]='\0'; } diff --git a/main/fopen_wrappers.c b/main/fopen_wrappers.c index a9e159efc8bf..c0f88ad3b5ba 100644 --- a/main/fopen_wrappers.c +++ b/main/fopen_wrappers.c @@ -508,7 +508,7 @@ PHPAPI zend_string *php_resolve_path(const char *filename, size_t filename_lengt } /* Don't resolve paths which contain protocol (except of file://) */ - for (p = filename; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++); + for (p = filename; isalnum((unsigned char)*p) || *p == '+' || *p == '-' || *p == '.'; p++); if ((*p == ':') && (p - filename > 1) && (p[1] == '/') && (p[2] == '/')) { wrapper = php_stream_locate_url_wrapper(filename, &actual_path, STREAM_OPEN_FOR_INCLUDE); if (wrapper == &php_plain_files_wrapper) { @@ -540,7 +540,7 @@ PHPAPI zend_string *php_resolve_path(const char *filename, size_t filename_lengt /* Check for stream wrapper */ int is_stream_wrapper = 0; - for (p = ptr; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++); + for (p = ptr; isalnum((unsigned char)*p) || *p == '+' || *p == '-' || *p == '.'; p++); if ((*p == ':') && (p - ptr > 1) && (p[1] == '/') && (p[2] == '/')) { /* .:// or ..:// is not a stream wrapper */ if (p[-1] != '.' || p[-2] != '.' || p - 2 != ptr) { @@ -615,7 +615,7 @@ PHPAPI zend_string *php_resolve_path(const char *filename, size_t filename_lengt actual_path = trypath; /* Check for stream wrapper */ - for (p = trypath; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++); + for (p = trypath; isalnum((unsigned char)*p) || *p == '+' || *p == '-' || *p == '.'; p++); if ((*p == ':') && (p - trypath > 1) && (p[1] == '/') && (p[2] == '/')) { wrapper = php_stream_locate_url_wrapper(trypath, &actual_path, STREAM_OPEN_FOR_INCLUDE); if (!wrapper) { diff --git a/main/php_ini.c b/main/php_ini.c index 43e1c115e6ea..a3b33da65bab 100644 --- a/main/php_ini.c +++ b/main/php_ini.c @@ -37,7 +37,7 @@ char *tmp = path; \ while (*tmp) { \ if (*tmp == '\\') *tmp = '/'; \ - else *tmp = tolower(*tmp); \ + else *tmp = tolower((unsigned char)*tmp); \ tmp++; \ } \ } diff --git a/main/php_ini_builder.c b/main/php_ini_builder.c index 1063d014bc2a..cdc10f0b5e25 100644 --- a/main/php_ini_builder.c +++ b/main/php_ini_builder.c @@ -65,7 +65,7 @@ PHPAPI void php_ini_builder_define(struct php_ini_builder *b, const char *arg) if (val != NULL) { val++; - if (!isalnum(*val) && *val != '"' && *val != '\'' && *val != '\0') { + if (!isalnum((unsigned char)*val) && *val != '"' && *val != '\'' && *val != '\0') { php_ini_builder_quoted(b, arg, val - arg - 1, val, arg + len - val); } else { php_ini_builder_realloc(b, len + strlen("\n")); diff --git a/main/php_variables.c b/main/php_variables.c index 7453014fa2cc..2121bfd8b35b 100644 --- a/main/php_variables.c +++ b/main/php_variables.c @@ -219,7 +219,7 @@ PHPAPI void php_register_variable_ex(const char *var_name, zval *val, zval *trac ip++; index_s = ip; - if (isspace(*ip)) { + if (isspace((unsigned char)*ip)) { ip++; } if (*ip==']') { @@ -542,7 +542,7 @@ SAPI_API SAPI_TREAT_DATA_FUNC(php_default_treat_data) if (arg == PARSE_COOKIE) { /* Remove leading spaces from cookie names, needed for multi-cookie header where ; can be followed by a space */ - while (isspace(*var)) { + while (isspace((unsigned char)*var)) { var++; } if (var == val || *var == '\0') { diff --git a/main/rfc1867.c b/main/rfc1867.c index 0f55a380a85e..e40a1a9e3008 100644 --- a/main/rfc1867.c +++ b/main/rfc1867.c @@ -390,7 +390,7 @@ static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header) } /* space in the beginning means same header */ - if (!isspace(line[0])) { + if (!isspace((unsigned char)line[0])) { value = strchr(line, ':'); } @@ -406,7 +406,7 @@ static int multipart_buffer_headers(multipart_buffer *self, zend_llist *header) } *value = '\0'; - do { value++; } while (isspace(*value)); + do { value++; } while (isspace((unsigned char)*value)); key = estrdup(line); smart_string_appends(&buf_value, value); @@ -503,7 +503,7 @@ static char *substring_conf(char *start, int len, char quote) static char *php_ap_getword_conf(const zend_encoding *encoding, char *str) { - while (*str && isspace(*str)) { + while (*str && isspace((unsigned char)*str)) { ++str; } @@ -519,7 +519,7 @@ static char *php_ap_getword_conf(const zend_encoding *encoding, char *str) } else { char *strend = str; - while (*strend && !isspace(*strend)) { + while (*strend && !isspace((unsigned char)*strend)) { ++strend; } return substring_conf(str, strend - str, 0); @@ -795,7 +795,7 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) goto fileupload_done; } - while (isspace(*cd)) { + while (isspace((unsigned char)*cd)) { ++cd; } @@ -803,7 +803,7 @@ SAPI_API SAPI_POST_HANDLER_FUNC(rfc1867_post_handler) { char *key = NULL, *word = pair; - while (isspace(*cd)) { + while (isspace((unsigned char)*cd)) { ++cd; } diff --git a/main/snprintf.c b/main/snprintf.c index 44c70dd86479..73c981a1cee7 100644 --- a/main/snprintf.c +++ b/main/snprintf.c @@ -284,7 +284,7 @@ PHPAPI char * php_conv_fp(char format, double num, /* * Check for Infinity and NaN */ - if (isalpha((int)*p)) { + if (isalpha((unsigned char)*p)) { *len = strlen(p); memcpy(buf, p, *len + 1); *is_negative = false; @@ -431,11 +431,11 @@ typedef struct buf_area buffy; #define NUM( c ) ( c - '0' ) #define STR_TO_DEC( str, num ) \ - num = NUM( *str++ ) ; \ - while ( isdigit((int)*str ) ) \ + num = NUM( *(str)++ ) ; \ + while ( isdigit((unsigned char)*(str) ) ) \ { \ num *= 10 ; \ - num += NUM( *str++ ) ; \ + num += NUM( *(str)++ ) ; \ } /* @@ -529,7 +529,7 @@ static size_t format_converter(buffy * odp, const char *fmt, va_list ap) /* {{{ /* * Try to avoid checking for flags, width or precision */ - if (isascii((int)*fmt) && !islower((int)*fmt)) { + if (isascii((unsigned char)*fmt) && !islower((unsigned char)*fmt)) { /* * Recognize flags: -, #, BLANK, + */ @@ -551,7 +551,7 @@ static size_t format_converter(buffy * odp, const char *fmt, va_list ap) /* {{{ /* * Check if a width was specified */ - if (isdigit((int)*fmt)) { + if (isdigit((unsigned char)*fmt)) { STR_TO_DEC(fmt, min_width); adjust_width = true; } else if (*fmt == '*') { @@ -571,7 +571,7 @@ static size_t format_converter(buffy * odp, const char *fmt, va_list ap) /* {{{ if (*fmt == '.') { adjust_precision = true; fmt++; - if (isdigit((int)*fmt)) { + if (isdigit((unsigned char)*fmt)) { STR_TO_DEC(fmt, precision); } else if (*fmt == '*') { precision = va_arg(ap, int); diff --git a/main/spprintf.c b/main/spprintf.c index 7249c17cdda0..6553853d8104 100644 --- a/main/spprintf.c +++ b/main/spprintf.c @@ -146,12 +146,12 @@ #define NUM(c) (c - '0') #define STR_TO_DEC(str, num) do { \ - num = NUM(*str++); \ - while (isdigit((int)*str)) { \ + num = NUM(*(str)++); \ + while (isdigit((unsigned char)*(str))) {\ num *= 10; \ - num += NUM(*str++); \ + num += NUM(*(str)++); \ if (num >= INT_MAX / 10) { \ - while (isdigit((int)*str++)); \ + while (isdigit((unsigned char)*(str)++)); \ break; \ } \ } \ @@ -231,7 +231,7 @@ static void xbuf_format_converter(void *xbuf, bool is_char, const char *fmt, va_ /* * Try to avoid checking for flags, width or precision */ - if (isascii((int)*fmt) && !islower((int)*fmt)) { + if (isascii((unsigned char)*fmt) && !islower((unsigned char)*fmt)) { /* * Recognize flags: -, #, BLANK, + */ @@ -253,7 +253,7 @@ static void xbuf_format_converter(void *xbuf, bool is_char, const char *fmt, va_ /* * Check if a width was specified */ - if (isdigit((int)*fmt)) { + if (isdigit((unsigned char)*fmt)) { STR_TO_DEC(fmt, min_width); adjust_width = true; } else if (*fmt == '*') { @@ -273,7 +273,7 @@ static void xbuf_format_converter(void *xbuf, bool is_char, const char *fmt, va_ if (*fmt == '.') { adjust_precision = true; fmt++; - if (isdigit((int)*fmt)) { + if (isdigit((unsigned char)*fmt)) { STR_TO_DEC(fmt, precision); } else if (*fmt == '*') { precision = va_arg(ap, int); diff --git a/main/streams/streams.c b/main/streams/streams.c index 31d1eda16790..d7cff6cf8de6 100644 --- a/main/streams/streams.c +++ b/main/streams/streams.c @@ -1973,7 +1973,7 @@ void php_shutdown_stream_wrappers(int module_number) static inline zend_result php_stream_wrapper_scheme_validate(const char *protocol, size_t protocol_len) { for (size_t i = 0; i < protocol_len; i++) { - if (!isalnum((int)protocol[i]) && + if (!isalnum((unsigned char)protocol[i]) && protocol[i] != '+' && protocol[i] != '-' && protocol[i] != '.') { @@ -2053,7 +2053,7 @@ PHPAPI php_stream_wrapper *php_stream_locate_url_wrapper(const char *path, const return (php_stream_wrapper*)((options & STREAM_LOCATE_WRAPPERS_ONLY) ? NULL : &php_plain_files_wrapper); } - for (p = path; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) { + for (p = path; isalnum((unsigned char)*p) || *p == '+' || *p == '-' || *p == '.'; p++) { n++; } diff --git a/main/streams/transports.c b/main/streams/transports.c index 0a18d10f7433..014e435cfb05 100644 --- a/main/streams/transports.c +++ b/main/streams/transports.c @@ -93,7 +93,7 @@ PHPAPI php_stream *_php_stream_xport_create(const char *name, size_t namelen, in } orig_path = name; - for (p = name; isalnum((int)*p) || *p == '+' || *p == '-' || *p == '.'; p++) { + for (p = name; isalnum((unsigned char)*p) || *p == '+' || *p == '-' || *p == '.'; p++) { n++; } diff --git a/sapi/cli/php_cli_server.c b/sapi/cli/php_cli_server.c index 0dfbe2d3db2a..797979b67305 100644 --- a/sapi/cli/php_cli_server.c +++ b/sapi/cli/php_cli_server.c @@ -651,7 +651,7 @@ static int sapi_cli_server_register_entry_cb(zval *entry, int num_args, va_list if (key[i] == '-') { key[i] = '_'; } else { - key[i] = toupper(key[i]); + key[i] = toupper((unsigned char)key[i]); } } spprintf(&real_key, 0, "%s_%s", "HTTP", key); diff --git a/sapi/fpm/fpm/fpm_conf.c b/sapi/fpm/fpm/fpm_conf.c index 3979d875a18e..a77f7776bdbe 100644 --- a/sapi/fpm/fpm/fpm_conf.c +++ b/sapi/fpm/fpm/fpm_conf.c @@ -973,7 +973,7 @@ static int fpm_conf_process_all_pools(void) } for (i = 0; i < strlen(status); i++) { - if (!isalnum(status[i]) && status[i] != '/' && status[i] != '-' && status[i] != '_' && status[i] != '.' && status[i] != '~') { + if (!isalnum((unsigned char)status[i]) && status[i] != '/' && status[i] != '-' && status[i] != '_' && status[i] != '.' && status[i] != '~') { zlog(ZLOG_ERROR, "[pool %s] the status path '%s' must contain only the following characters '[alphanum]/_-.~'", wp->config->name, status); return -1; } @@ -996,7 +996,7 @@ static int fpm_conf_process_all_pools(void) } for (i = 0; i < strlen(ping); i++) { - if (!isalnum(ping[i]) && ping[i] != '/' && ping[i] != '-' && ping[i] != '_' && ping[i] != '.' && ping[i] != '~') { + if (!isalnum((unsigned char)ping[i]) && ping[i] != '/' && ping[i] != '-' && ping[i] != '_' && ping[i] != '.' && ping[i] != '~') { zlog(ZLOG_ERROR, "[pool %s] the ping path '%s' must contain only the following characters '[alphanum]/_-.~'", wp->config->name, ping); return -1; } diff --git a/sapi/fpm/fpm/fpm_status.c b/sapi/fpm/fpm/fpm_status.c index 9cb8731363fa..ff6ef68f3949 100644 --- a/sapi/fpm/fpm/fpm_status.c +++ b/sapi/fpm/fpm/fpm_status.c @@ -522,8 +522,8 @@ int fpm_status_handle_request(void) /* {{{ */ if (full_syntax) { unsigned int i; int first; - zend_string *tmp_query_string; - char *query_string; + zend_string *tmp_query_string, *tmp_request_uri_string; + char *query_string, *request_uri_string; struct timeval duration, now; float cpu; @@ -548,13 +548,36 @@ int fpm_status_handle_request(void) /* {{{ */ } } + request_uri_string = NULL; + tmp_request_uri_string = NULL; + if (proc->request_uri[0] != '\0') { + if (encode_html) { + tmp_request_uri_string = php_escape_html_entities_ex( + (const unsigned char *) proc->request_uri, + strlen(proc->request_uri), 1, ENT_DISALLOWED | ENT_HTML_DOC_XML1 | ENT_COMPAT, + NULL, /* double_encode */ 1, /* quiet */ 0); + request_uri_string = ZSTR_VAL(tmp_request_uri_string); + } else if (encode_json) { + tmp_request_uri_string = php_json_encode_string(proc->request_uri, + strlen(proc->request_uri), PHP_JSON_INVALID_UTF8_IGNORE); + request_uri_string = ZSTR_VAL(tmp_request_uri_string); + /* remove quotes around the string */ + if (ZSTR_LEN(tmp_request_uri_string) >= 2) { + request_uri_string[ZSTR_LEN(tmp_request_uri_string) - 1] = '\0'; + ++request_uri_string; + } + } else { + request_uri_string = proc->request_uri; + } + } + query_string = NULL; tmp_query_string = NULL; if (proc->query_string[0] != '\0') { if (encode_html) { tmp_query_string = php_escape_html_entities_ex( (const unsigned char *) proc->query_string, - strlen(proc->query_string), 1, ENT_HTML_IGNORE_ERRORS & ENT_COMPAT, + strlen(proc->query_string), 1, ENT_DISALLOWED | ENT_HTML_DOC_XML1 | ENT_COMPAT, NULL, /* double_encode */ 1, /* quiet */ 0); } else if (encode_json) { tmp_query_string = php_json_encode_string(proc->query_string, @@ -593,7 +616,7 @@ int fpm_status_handle_request(void) /* {{{ */ proc->requests, (unsigned long) (duration.tv_sec * 1000000UL + duration.tv_usec), proc->request_method[0] != '\0' ? proc->request_method : "-", - proc->request_uri[0] != '\0' ? proc->request_uri : "-", + request_uri_string ? request_uri_string : "-", query_string ? "?" : "", query_string ? query_string : "", proc->content_length, @@ -604,6 +627,9 @@ int fpm_status_handle_request(void) /* {{{ */ PUTS(buffer); efree(buffer); + if (tmp_request_uri_string) { + zend_string_free(tmp_request_uri_string); + } if (tmp_query_string) { zend_string_free(tmp_query_string); } diff --git a/sapi/fpm/tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt b/sapi/fpm/tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt new file mode 100644 index 000000000000..475bc130a42b --- /dev/null +++ b/sapi/fpm/tests/ghsa-7qg2-v9fj-4mwv-status-xss.phpt @@ -0,0 +1,48 @@ +--TEST-- +FPM: GHSA-7qg2-v9fj-4mwv - status xss +--SKIPIF-- + +--FILE-- +start(); +$tester->expectLogStartNotices(); +$responses = $tester + ->multiRequest([ + ['uri' => '/', 'query' => ''], + ['uri' => '/status', 'query' => 'full&html', 'delay' => 100000], + ]); +var_dump(strpos($responses[1]->getBody(), '