Commit 398b7dabfbd for php.net
commit 398b7dabfbd2e8f4f4ed2065dbcf3e3794e8ca47
Merge: 411c0de215f a38418777f6
Author: Ilija Tovilo <ilija.tovilo@me.com>
Date: Wed May 6 13:08:17 2026 +0200
GHSA-m8rr-4c36-8gq4: Consistently pass unsigned char to ctype.h functions
Fixes GHSA-m8rr-4c36-8gq4
Fixes CVE-2026-7258
diff --cc Zend/zend_ini.c
index 2ab476e25bd,3201eec0492..6886ea702a1
--- a/Zend/zend_ini.c
+++ b/Zend/zend_ini.c
@@@ -595,10 -555,10 +595,10 @@@ static const char *zend_ini_consume_qua
++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;
+ return digits_consumed;
}
switch (digits_consumed[1]) {
diff --cc Zend/zend_virtual_cwd.c
index 6bff2ad984d,a2ea4de165f..9975278ba2c
--- a/Zend/zend_virtual_cwd.c
+++ b/Zend/zend_virtual_cwd.c
@@@ -1129,11 -1129,8 +1129,11 @@@ CWD_API int virtual_file_ex(cwd_state *
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';
+ }
start = 3;
}
#endif
diff --cc ext/standard/formatted_print.c
index 1ff0f36212b,a1927b5fe88..b8847d1801b
--- a/ext/standard/formatted_print.c
+++ b/ext/standard/formatted_print.c
@@@ -468,9 -464,9 +468,9 @@@ php_formatted_print(char *format, size_
always_sign = 0;
expprec = 0;
- PRINTF_DEBUG(("sprintf: first looking at '%c', inpos=%d\n",
- *format, format - Z_STRVAL_P(z_format)));
+ 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 {
@@@ -539,10 -535,10 +539,10 @@@
}
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 greater than zero and less than %d", INT_MAX);
+ zend_value_error("Width must be between 0 and %d", INT_MAX);
goto fail;
}
adjusting |= ADJ_WIDTH;
@@@ -584,9 -580,9 +584,9 @@@
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 greater than zero and less than %d", INT_MAX);
+ zend_value_error("Precision must be between 0 and %d", INT_MAX);
goto fail;
}
adjusting |= ADJ_PRECISION;
diff --cc ext/standard/metaphone.c
index 9bddc996690,01ae1f965c1..887c4b1702f
--- a/ext/standard/metaphone.c
+++ b/ext/standard/metaphone.c
@@@ -78,29 -78,21 +78,29 @@@ static const char _codes[26]
};
-#define ENCODE(c) (isalpha((unsigned char)(c)) ? _codes[((toupper((unsigned char)(c))) - 'A')] : 0)
+/* 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 {
+ return 0;
+ }
+}
-#define isvowel(c) (ENCODE(c) & 1) /* AEIOU */
+#define isvowel(c) (encode(c) & 1) /* AEIOU */
/* These letters are passed through unchanged */
-#define NOCHANGE(c) (ENCODE(c) & 2) /* FJMNR */
+#define NOCHANGE(c) (encode(c) & 2) /* FJMNR */
/* These form diphthongs when preceding H */
-#define AFFECTH(c) (ENCODE(c) & 4) /* CGPST */
+#define AFFECTH(c) (encode(c) & 4) /* CGPST */
/* These make C and G soft */
-#define MAKESOFT(c) (ENCODE(c) & 8) /* EIY */
+#define MAKESOFT(c) (encode(c) & 8) /* EIY */
/* These prevent GH from becoming F */
-#define NOGHTOF(c) (ENCODE(c) & 16) /* BDH */
+#define NOGHTOF(c) (encode(c) & 16) /* BDH */
/*----------------------------- */
/* end of "metachar.h" */
@@@ -109,21 -101,18 +109,21 @@@
/* I suppose I could have been using a character pointer instead of
* accesssing 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 Next_Letter (toupper((unsigned char)word[w_idx+1]))
+#define Read_Raw_Next_Letter (word[w_idx+1])
+#define Read_Next_Letter (Convert_Raw(Read_Raw_Next_Letter))
/* Look at the current letter in the word */
-#define Curr_Letter (toupper((unsigned char)word[w_idx]))
+#define Read_Raw_Curr_Letter (word[w_idx])
+#define Read_Curr_Letter (Convert_Raw(Read_Raw_Curr_Letter))
/* Go N letters back. */
-#define Look_Back_Letter(n) (w_idx >= n ? toupper((unsigned char)word[w_idx-n]) : '\0')
+#define Look_Back_Letter(n) (w_idx >= n ? Convert_Raw(word[w_idx-n]) : '\0')
/* Previous letter. I dunno, should this return null on failure? */
-#define Prev_Letter (Look_Back_Letter(1))
+#define Read_Prev_Letter (Look_Back_Letter(1))
/* Look two letters down. It makes sure you don't walk off the string. */
-#define After_Next_Letter (Next_Letter != '\0' ? toupper((unsigned char)word[w_idx+2]) \
+#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 */
@@@ -189,9 -179,9 +189,9 @@@ static void metaphone(unsigned char *wo
/*-- 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); 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') {
+ if (curr_letter == '\0') {
End_Phoned_Word();
return;
}
@@@ -277,23 -263,18 +277,23 @@@
*/
/* Ignore non-alphas */
- if (!isalpha(curr_letter))
- if (!isalpha((unsigned char)Curr_Letter))
++ if (!isalpha((unsigned char)curr_letter))
continue;
+ curr_letter = Convert_Raw(curr_letter);
+ /* Note: we can't cache curr_letter from the previous loop
+ * because of the skip_letter variable. */
+ char prev_letter = Read_Prev_Letter;
+
/* Drop duplicates, except CC */
- if (Curr_Letter == Prev_Letter &&
- Curr_Letter != 'C')
+ if (curr_letter == prev_letter &&
+ curr_letter != 'C')
continue;
- switch (Curr_Letter) {
+ switch (curr_letter) {
/* B -> B unless in MB */
case 'B':
- if (Prev_Letter != 'M')
+ if (prev_letter != 'M')
Phonize('B');
break;
/* 'sh' if -CIA- or -CH, but not SCH, except SCHW.
diff --cc main/streams/transports.c
index 38850a3b541,0db8f4d0130..44f4050fec8
--- a/main/streams/transports.c
+++ b/main/streams/transports.c
@@@ -94,8 -94,7 +94,8 @@@ PHPAPI php_stream *_php_stream_xport_cr
}
}
+ 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 --cc sapi/phpdbg/phpdbg_utils.c
index 964d82ef6c8,04bc117ffb3..fad8c9dfb76
--- a/sapi/phpdbg/phpdbg_utils.c
+++ b/sapi/phpdbg/phpdbg_utils.c
@@@ -199,7 -199,7 +199,7 @@@ PHPDBG_API char *phpdbg_trim(const cha
const char *p = str;
char *new = NULL;
- while (isspace(*p)) {
- while (p && isspace((unsigned char)*p)) {
++ while (isspace((unsigned char)*p)) {
++p;
--len;
}