From d28d59a8f32fb3b9b1732db990277262578342f4 Mon Sep 17 00:00:00 2001 From: Gina Peter Banyard Date: Sat, 28 Mar 2026 14:48:35 +0000 Subject: [PATCH 1/6] Zend/Optimizer/dfa_pass.c: refactor zend_dfa_optimize_calls() (#21549) This function is only called within dfa_pass.c, thus make it static Return a uint32_t as the value can never be negative Add some const qualifiers --- Zend/Optimizer/dfa_pass.c | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/Zend/Optimizer/dfa_pass.c b/Zend/Optimizer/dfa_pass.c index a30367c343f4..cfc6b27b3d21 100644 --- a/Zend/Optimizer/dfa_pass.c +++ b/Zend/Optimizer/dfa_pass.c @@ -398,13 +398,13 @@ static bool variable_defined_or_used_in_range(zend_ssa *ssa, int var, int start, return false; } -int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa) +static uint32_t zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa) { - zend_func_info *func_info = ZEND_FUNC_INFO(op_array); - int removed_ops = 0; + const zend_func_info *func_info = ZEND_FUNC_INFO(op_array); + uint32_t removed_ops = 0; if (func_info->callee_info) { - zend_call_info *call_info = func_info->callee_info; + const zend_call_info *call_info = func_info->callee_info; do { zend_op *op = call_info->caller_init_opline; @@ -413,7 +413,6 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa) || (op->opcode == ZEND_FRAMELESS_ICALL_3 && (op + 1)->op1_type == IS_CONST)) && call_info->callee_func && zend_string_equals_literal_ci(call_info->callee_func->common.function_name, "in_array")) { - bool strict = false; bool has_opdata = op->opcode == ZEND_FRAMELESS_ICALL_3; ZEND_ASSERT(!call_info->is_prototype); @@ -428,7 +427,7 @@ int zend_dfa_optimize_calls(zend_op_array *op_array, zend_ssa *ssa) && Z_TYPE_P(CT_CONSTANT_EX(op_array, op->op2.constant)) == IS_ARRAY) { bool ok = true; - HashTable *src = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, op->op2.constant)); + const HashTable *src = Z_ARRVAL_P(CT_CONSTANT_EX(op_array, op->op2.constant)); HashTable *dst; zval *val, tmp; zend_ulong idx; From 465ecaabf1582c8e92b5a2e8af3d7d7ae3ec4bc8 Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 28 Mar 2026 21:01:43 +0530 Subject: [PATCH 2/6] ext/standard: zend_string_concat2() instead of manual memcpy (#21567) --- ext/standard/string.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/ext/standard/string.c b/ext/standard/string.c index 7d609a032dd1..e479a9e7612e 100644 --- a/ext/standard/string.c +++ b/ext/standard/string.c @@ -2203,10 +2203,10 @@ PHP_FUNCTION(chunk_split) if ((size_t)chunklen > ZSTR_LEN(str)) { /* to maintain BC, we must return original string + ending */ - result = zend_string_safe_alloc(ZSTR_LEN(str), 1, endlen, 0); - memcpy(ZSTR_VAL(result), ZSTR_VAL(str), ZSTR_LEN(str)); - memcpy(ZSTR_VAL(result) + ZSTR_LEN(str), end, endlen); - ZSTR_VAL(result)[ZSTR_LEN(result)] = '\0'; + result = zend_string_concat2( + ZSTR_VAL(str), ZSTR_LEN(str), + end, endlen + ); RETURN_NEW_STR(result); } From 78f2d4409005a2805b1caafe80c4ec15a456d0d7 Mon Sep 17 00:00:00 2001 From: Weilin Du <108666168+LamentXU123@users.noreply.github.com> Date: Sat, 28 Mar 2026 23:35:34 +0800 Subject: [PATCH 3/6] uri: Preinitialize `errors` array with the correct size in `fill_errors()` (#21560) --- ext/uri/uri_parser_whatwg.c | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/ext/uri/uri_parser_whatwg.c b/ext/uri/uri_parser_whatwg.c index 055f130af7c6..9d5e87fd4cf4 100644 --- a/ext/uri/uri_parser_whatwg.c +++ b/ext/uri/uri_parser_whatwg.c @@ -62,12 +62,13 @@ static zend_always_inline void zval_long_or_null_to_lexbor_str(zval *value, lexb */ static const char *fill_errors(zval *errors) { - if (lexbor_parser.log == NULL || lexbor_plog_length(lexbor_parser.log) == 0) { + size_t log_len; + if (lexbor_parser.log == NULL || (log_len = lexbor_plog_length(lexbor_parser.log)) == 0) { ZVAL_EMPTY_ARRAY(errors); return NULL; } - array_init(errors); + array_init_size(errors, log_len); const char *result = NULL; lexbor_plog_entry_t *lxb_error; From 02614a25992f642aa040a97d388f292e4122d77f Mon Sep 17 00:00:00 2001 From: Arshid Date: Sat, 28 Mar 2026 22:17:05 +0530 Subject: [PATCH 4/6] ext/spl: zend_string_concat3() instead of manual memcpy for sub_path construction (#21564) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ext/spl: zend_string_concat3() instead of manual memcpy for sub_path construction * spl: Adjust `zend_string_concat3()` formatting --------- Co-authored-by: Tim Düsterhus --- ext/spl/spl_directory.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/ext/spl/spl_directory.c b/ext/spl/spl_directory.c index 86e4b11334c8..daaba27cbfc6 100644 --- a/ext/spl/spl_directory.c +++ b/ext/spl/spl_directory.c @@ -1470,11 +1470,11 @@ PHP_METHOD(RecursiveDirectoryIterator, getChildren) if (subdir) { size_t name_len = strlen(intern->u.dir.entry.d_name); if (intern->u.dir.sub_path && ZSTR_LEN(intern->u.dir.sub_path)) { - zend_string *sub_path = zend_string_alloc(ZSTR_LEN(intern->u.dir.sub_path) + 1 + name_len, 0); - memcpy(ZSTR_VAL(sub_path), ZSTR_VAL(intern->u.dir.sub_path), ZSTR_LEN(intern->u.dir.sub_path)); - ZSTR_VAL(sub_path)[ZSTR_LEN(intern->u.dir.sub_path)] = slash; - memcpy(ZSTR_VAL(sub_path) + ZSTR_LEN(intern->u.dir.sub_path) + 1, intern->u.dir.entry.d_name, name_len); - ZSTR_VAL(sub_path)[ZSTR_LEN(intern->u.dir.sub_path) + 1 + name_len] = 0; + zend_string *sub_path = zend_string_concat3( + ZSTR_VAL(intern->u.dir.sub_path), ZSTR_LEN(intern->u.dir.sub_path), + &slash, 1, + intern->u.dir.entry.d_name, name_len + ); subdir->u.dir.sub_path = sub_path; } else { subdir->u.dir.sub_path = zend_string_init(intern->u.dir.entry.d_name, name_len, 0); From 38628e89a545c06e1425aa639b1d624baacf938a Mon Sep 17 00:00:00 2001 From: Ilia Alshanetsky Date: Thu, 26 Mar 2026 18:05:28 -0400 Subject: [PATCH 5/6] Fix GH-17399: iconv memory leak on bailout Wrap bailable sections in php_iconv_string(), _php_iconv_substr(), _php_iconv_mime_encode(), and _php_iconv_mime_decode() with zend_try/zend_catch to ensure iconv handles allocated via system malloc are closed if a Zend OOM bailout fires during smart_str or zend_string operations. Fixes GH-17399 Closes GH-21541 --- NEWS | 3 + ext/iconv/iconv.c | 1295 +++++++++++----------- ext/iconv/tests/gh17399.phpt | 13 + ext/iconv/tests/gh17399_iconv.phpt | 13 + ext/iconv/tests/gh17399_mime_decode.phpt | 13 + ext/iconv/tests/gh17399_substr.phpt | 13 + 6 files changed, 718 insertions(+), 632 deletions(-) create mode 100644 ext/iconv/tests/gh17399.phpt create mode 100644 ext/iconv/tests/gh17399_iconv.phpt create mode 100644 ext/iconv/tests/gh17399_mime_decode.phpt create mode 100644 ext/iconv/tests/gh17399_substr.phpt diff --git a/NEWS b/NEWS index dc0e430775f1..0e042bb864cd 100644 --- a/NEWS +++ b/NEWS @@ -6,6 +6,9 @@ PHP NEWS . Fixed bug GH-19983 (GC assertion failure with fibers, generators and destructors). (iliaal) +- Iconv: + . Fixed bug GH-17399 (iconv memory leak on bailout). (iliaal) + - SPL: . Fixed bug GH-21499 (RecursiveArrayIterator getChildren UAF after parent free). (Girgias) diff --git a/ext/iconv/iconv.c b/ext/iconv/iconv.c index ba74eddd012f..117a9e948f64 100644 --- a/ext/iconv/iconv.c +++ b/ext/iconv/iconv.c @@ -459,59 +459,65 @@ PHP_ICONV_API php_iconv_err_t php_iconv_string(const char *in_p, size_t in_len, out_left = in_len + 32; /* Avoid realloc() most cases */ out_size = 0; bsz = out_left; - out_buf = zend_string_alloc(bsz, 0); - out_p = ZSTR_VAL(out_buf); - - while (in_left > 0) { - result = iconv(cd, (ICONV_CONST char **) &in_p, &in_left, (char **) &out_p, &out_left); - out_size = bsz - out_left; - if (result == (size_t)(-1)) { - if (ignore_ilseq && errno == EILSEQ) { - if (in_left <= 1) { - result = 0; - } else { - errno = 0; - in_p++; - in_left--; - continue; + + zend_try { + out_buf = zend_string_alloc(bsz, 0); + out_p = ZSTR_VAL(out_buf); + + while (in_left > 0) { + result = iconv(cd, (ICONV_CONST char **) &in_p, &in_left, (char **) &out_p, &out_left); + out_size = bsz - out_left; + if (result == (size_t)(-1)) { + if (ignore_ilseq && errno == EILSEQ) { + if (in_left <= 1) { + result = 0; + } else { + errno = 0; + in_p++; + in_left--; + continue; + } } - } - if (errno == E2BIG && in_left > 0) { - /* converted string is longer than out buffer */ - bsz += in_len; + if (errno == E2BIG && in_left > 0) { + /* converted string is longer than out buffer */ + bsz += in_len; - out_buf = zend_string_extend(out_buf, bsz, 0); - out_p = ZSTR_VAL(out_buf); - out_p += out_size; - out_left = bsz - out_size; - continue; + out_buf = zend_string_extend(out_buf, bsz, 0); + out_p = ZSTR_VAL(out_buf); + out_p += out_size; + out_left = bsz - out_size; + continue; + } } + break; } - break; - } - if (result != (size_t)(-1)) { - /* flush the shift-out sequences */ - for (;;) { - result = iconv(cd, NULL, NULL, (char **) &out_p, &out_left); - out_size = bsz - out_left; + if (result != (size_t)(-1)) { + /* flush the shift-out sequences */ + for (;;) { + result = iconv(cd, NULL, NULL, (char **) &out_p, &out_left); + out_size = bsz - out_left; - if (result != (size_t)(-1)) { - break; - } + if (result != (size_t)(-1)) { + break; + } - if (errno == E2BIG) { - bsz += 16; - out_buf = zend_string_extend(out_buf, bsz, 0); - out_p = ZSTR_VAL(out_buf); - out_p += out_size; - out_left = bsz - out_size; - } else { - break; + if (errno == E2BIG) { + bsz += 16; + out_buf = zend_string_extend(out_buf, bsz, 0); + out_p = ZSTR_VAL(out_buf); + out_p += out_size; + out_left = bsz - out_size; + } else { + break; + } } } - } + } zend_catch { + iconv_close(cd); + zend_bailout(); + } zend_end_try(); iconv_close(cd); @@ -684,58 +690,63 @@ static php_iconv_err_t _php_iconv_substr(smart_str *pretval, errno = 0; more = nbytes > 0 && len > 0; - for (in_p = str, in_left = nbytes, cnt = 0; more; ++cnt) { - out_p = buf; - out_left = sizeof(buf); + bool bailout = false; + zend_try { + for (in_p = str, in_left = nbytes, cnt = 0; more; ++cnt) { + out_p = buf; + out_left = sizeof(buf); - more = in_left > 0 && len > 0; + more = in_left > 0 && len > 0; - iconv(cd1, more ? (ICONV_CONST char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left); - if (out_left == sizeof(buf)) { - break; - } + iconv(cd1, more ? (ICONV_CONST char **)&in_p : NULL, more ? &in_left : NULL, (char **) &out_p, &out_left); + if (out_left == sizeof(buf)) { + break; + } - if ((zend_long)cnt >= offset) { - if (cd2 == (iconv_t)NULL) { - cd2 = iconv_open(enc, GENERIC_SUPERSET_NAME); + if ((zend_long)cnt >= offset) { + if (cd2 == (iconv_t)NULL) { + cd2 = iconv_open(enc, GENERIC_SUPERSET_NAME); - if (cd2 == (iconv_t)(-1)) { - cd2 = (iconv_t)NULL; - if (errno == EINVAL) { - err = PHP_ICONV_ERR_WRONG_CHARSET; - } else { - err = PHP_ICONV_ERR_CONVERTER; + if (cd2 == (iconv_t)(-1)) { + cd2 = (iconv_t)NULL; + if (errno == EINVAL) { + err = PHP_ICONV_ERR_WRONG_CHARSET; + } else { + err = PHP_ICONV_ERR_CONVERTER; + } + break; } + } + + if (_php_iconv_appendl(pretval, buf, sizeof(buf), cd2) != PHP_ICONV_ERR_SUCCESS) { break; } + --len; } - if (_php_iconv_appendl(pretval, buf, sizeof(buf), cd2) != PHP_ICONV_ERR_SUCCESS) { - break; - } - --len; } - } - - switch (errno) { - case EINVAL: - err = PHP_ICONV_ERR_ILLEGAL_CHAR; - break; + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + break; - case EILSEQ: - err = PHP_ICONV_ERR_ILLEGAL_SEQ; - break; + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + break; - case E2BIG: - break; - } - if (err == PHP_ICONV_ERR_SUCCESS) { - if (cd2 != (iconv_t)NULL) { - _php_iconv_appendl(pretval, NULL, 0, cd2); + case E2BIG: + break; } - smart_str_0(pretval); - } + if (err == PHP_ICONV_ERR_SUCCESS) { + if (cd2 != (iconv_t)NULL) { + _php_iconv_appendl(pretval, NULL, 0, cd2); + } + smart_str_0(pretval); + } + } zend_catch { + bailout = true; + } zend_end_try(); if (cd1 != (iconv_t)NULL) { iconv_close(cd1); @@ -744,6 +755,9 @@ static php_iconv_err_t _php_iconv_substr(smart_str *pretval, if (cd2 != (iconv_t)NULL) { iconv_close(cd2); } + if (bailout) { + zend_bailout(); + } return err; } @@ -904,6 +918,7 @@ static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fn { php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; iconv_t cd = (iconv_t)(-1), cd_pl = (iconv_t)(-1); + bool bailout = false; size_t char_cnt = 0; size_t out_charset_len; size_t lfchars_len; @@ -962,215 +977,219 @@ static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fn goto out; } - buf = safe_emalloc(1, max_line_len, 5); + zend_try { + buf = safe_emalloc(1, max_line_len, 5); - char_cnt = max_line_len; + char_cnt = max_line_len; - _php_iconv_appendl(pretval, fname, fname_nbytes, cd_pl); - char_cnt -= fname_nbytes; - smart_str_appendl(pretval, ": ", sizeof(": ") - 1); - char_cnt -= 2; + _php_iconv_appendl(pretval, fname, fname_nbytes, cd_pl); + char_cnt -= fname_nbytes; + smart_str_appendl(pretval, ": ", sizeof(": ") - 1); + char_cnt -= 2; - in_p = fval; - in_left = fval_nbytes; + in_p = fval; + in_left = fval_nbytes; - do { - size_t prev_in_left; - size_t out_size; - size_t encoded_word_min_len = sizeof("=\?\?X\?\?=")-1 + out_charset_len + (enc_scheme == PHP_ICONV_ENC_SCHEME_BASE64 ? 4 : 3); + do { + size_t prev_in_left; + size_t out_size; + size_t encoded_word_min_len = sizeof("=\?\?X\?\?=")-1 + out_charset_len + (enc_scheme == PHP_ICONV_ENC_SCHEME_BASE64 ? 4 : 3); - if (char_cnt < encoded_word_min_len + lfchars_len + 1) { - /* lfchars must be encoded in ASCII here*/ - smart_str_appendl(pretval, lfchars, lfchars_len); - smart_str_appendc(pretval, ' '); - char_cnt = max_line_len - 1; - } + if (char_cnt < encoded_word_min_len + lfchars_len + 1) { + /* lfchars must be encoded in ASCII here*/ + smart_str_appendl(pretval, lfchars, lfchars_len); + smart_str_appendc(pretval, ' '); + char_cnt = max_line_len - 1; + } - smart_str_appendl(pretval, "=?", sizeof("=?") - 1); - char_cnt -= 2; - smart_str_appendl(pretval, out_charset, out_charset_len); - char_cnt -= out_charset_len; - smart_str_appendc(pretval, '?'); - char_cnt --; + smart_str_appendl(pretval, "=?", sizeof("=?") - 1); + char_cnt -= 2; + smart_str_appendl(pretval, out_charset, out_charset_len); + char_cnt -= out_charset_len; + smart_str_appendc(pretval, '?'); + char_cnt --; - switch (enc_scheme) { - case PHP_ICONV_ENC_SCHEME_BASE64: { - size_t ini_in_left; - const char *ini_in_p; - size_t out_reserved = 4; + switch (enc_scheme) { + case PHP_ICONV_ENC_SCHEME_BASE64: { + size_t ini_in_left; + const char *ini_in_p; + size_t out_reserved = 4; - smart_str_appendc(pretval, 'B'); - char_cnt--; - smart_str_appendc(pretval, '?'); - char_cnt--; + smart_str_appendc(pretval, 'B'); + char_cnt--; + smart_str_appendc(pretval, '?'); + char_cnt--; - prev_in_left = ini_in_left = in_left; - ini_in_p = in_p; + prev_in_left = ini_in_left = in_left; + ini_in_p = in_p; - out_size = (char_cnt - 2) / 4 * 3; + out_size = (char_cnt - 2) / 4 * 3; - for (;;) { - out_p = buf; + for (;;) { + out_p = buf; - if (out_size <= out_reserved) { - err = PHP_ICONV_ERR_TOO_BIG; - goto out; - } + if (out_size <= out_reserved) { + err = PHP_ICONV_ERR_TOO_BIG; + goto out; + } - out_left = out_size - out_reserved; + out_left = out_size - out_reserved; - if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { - switch (errno) { - case EINVAL: - err = PHP_ICONV_ERR_ILLEGAL_CHAR; - goto out; + if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + goto out; - case EILSEQ: - err = PHP_ICONV_ERR_ILLEGAL_SEQ; - goto out; + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + goto out; + + case E2BIG: + if (prev_in_left == in_left) { + err = PHP_ICONV_ERR_TOO_BIG; + goto out; + } + break; - case E2BIG: - if (prev_in_left == in_left) { - err = PHP_ICONV_ERR_TOO_BIG; + default: + err = PHP_ICONV_ERR_UNKNOWN; goto out; - } - break; + } + } - default: + out_left += out_reserved; + + if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) { + if (errno != E2BIG) { err = PHP_ICONV_ERR_UNKNOWN; goto out; + } + } else { + break; } - } - - out_left += out_reserved; - if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) { - if (errno != E2BIG) { + if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) { err = PHP_ICONV_ERR_UNKNOWN; goto out; } - } else { - break; - } - if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) { - err = PHP_ICONV_ERR_UNKNOWN; - goto out; + out_reserved += 4; + in_left = ini_in_left; + in_p = ini_in_p; } - out_reserved += 4; - in_left = ini_in_left; - in_p = ini_in_p; - } + prev_in_left = in_left; - prev_in_left = in_left; + encoded = php_base64_encode((unsigned char *) buf, (out_size - out_left)); - encoded = php_base64_encode((unsigned char *) buf, (out_size - out_left)); + if (char_cnt < ZSTR_LEN(encoded)) { + /* something went wrong! */ + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } - if (char_cnt < ZSTR_LEN(encoded)) { - /* something went wrong! */ - err = PHP_ICONV_ERR_UNKNOWN; - goto out; - } + smart_str_appendl(pretval, ZSTR_VAL(encoded), ZSTR_LEN(encoded)); + char_cnt -= ZSTR_LEN(encoded); + smart_str_appendl(pretval, "?=", sizeof("?=") - 1); + char_cnt -= 2; - smart_str_appendl(pretval, ZSTR_VAL(encoded), ZSTR_LEN(encoded)); - char_cnt -= ZSTR_LEN(encoded); - smart_str_appendl(pretval, "?=", sizeof("?=") - 1); - char_cnt -= 2; + zend_string_release_ex(encoded, 0); + encoded = NULL; + } break; /* case PHP_ICONV_ENC_SCHEME_BASE64: */ - zend_string_release_ex(encoded, 0); - encoded = NULL; - } break; /* case PHP_ICONV_ENC_SCHEME_BASE64: */ + case PHP_ICONV_ENC_SCHEME_QPRINT: { + size_t ini_in_left; + const char *ini_in_p; + const unsigned char *p; + size_t nbytes_required; - case PHP_ICONV_ENC_SCHEME_QPRINT: { - size_t ini_in_left; - const char *ini_in_p; - const unsigned char *p; - size_t nbytes_required; + smart_str_appendc(pretval, 'Q'); + char_cnt--; + smart_str_appendc(pretval, '?'); + char_cnt--; - smart_str_appendc(pretval, 'Q'); - char_cnt--; - smart_str_appendc(pretval, '?'); - char_cnt--; + prev_in_left = ini_in_left = in_left; + ini_in_p = in_p; - prev_in_left = ini_in_left = in_left; - ini_in_p = in_p; + for (out_size = (char_cnt - 2); out_size > 0;) { - for (out_size = (char_cnt - 2); out_size > 0;) { + nbytes_required = 0; - nbytes_required = 0; + out_p = buf; + out_left = out_size; - out_p = buf; - out_left = out_size; + if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { + switch (errno) { + case EINVAL: + err = PHP_ICONV_ERR_ILLEGAL_CHAR; + goto out; - if (iconv(cd, (ICONV_CONST char **)&in_p, &in_left, (char **) &out_p, &out_left) == (size_t)-1) { - switch (errno) { - case EINVAL: - err = PHP_ICONV_ERR_ILLEGAL_CHAR; - goto out; + case EILSEQ: + err = PHP_ICONV_ERR_ILLEGAL_SEQ; + goto out; - case EILSEQ: - err = PHP_ICONV_ERR_ILLEGAL_SEQ; - goto out; + case E2BIG: + if (prev_in_left == in_left) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } + break; - case E2BIG: - if (prev_in_left == in_left) { + default: err = PHP_ICONV_ERR_UNKNOWN; goto out; - } - break; - - default: + } + } + if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) { + if (errno != E2BIG) { err = PHP_ICONV_ERR_UNKNOWN; goto out; + } } - } - if (iconv(cd, NULL, NULL, (char **) &out_p, &out_left) == (size_t)-1) { - if (errno != E2BIG) { - err = PHP_ICONV_ERR_UNKNOWN; - goto out; + + for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) { + nbytes_required += qp_table[*p]; } - } - for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) { - nbytes_required += qp_table[*p]; - } + if (nbytes_required <= char_cnt - 2) { + break; + } - if (nbytes_required <= char_cnt - 2) { - break; + out_size -= ((nbytes_required - (char_cnt - 2)) + 2) / 3; + in_left = ini_in_left; + in_p = ini_in_p; } - out_size -= ((nbytes_required - (char_cnt - 2)) + 2) / 3; - in_left = ini_in_left; - in_p = ini_in_p; - } - - for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) { - if (qp_table[*p] == 1) { - smart_str_appendc(pretval, *(char *)p); - char_cnt--; - } else { - static const char qp_digits[] = "0123456789ABCDEF"; - smart_str_appendc(pretval, '='); - smart_str_appendc(pretval, qp_digits[(*p >> 4) & 0x0f]); - smart_str_appendc(pretval, qp_digits[(*p & 0x0f)]); - char_cnt -= 3; + for (p = (unsigned char *)buf; p < (unsigned char *)out_p; p++) { + if (qp_table[*p] == 1) { + smart_str_appendc(pretval, *(char *)p); + char_cnt--; + } else { + static const char qp_digits[] = "0123456789ABCDEF"; + smart_str_appendc(pretval, '='); + smart_str_appendc(pretval, qp_digits[(*p >> 4) & 0x0f]); + smart_str_appendc(pretval, qp_digits[(*p & 0x0f)]); + char_cnt -= 3; + } } - } - smart_str_appendl(pretval, "?=", sizeof("?=") - 1); - char_cnt -= 2; + smart_str_appendl(pretval, "?=", sizeof("?=") - 1); + char_cnt -= 2; - if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) { - err = PHP_ICONV_ERR_UNKNOWN; - goto out; - } + if (iconv(cd, NULL, NULL, NULL, NULL) == (size_t)-1) { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; + } - } break; /* case PHP_ICONV_ENC_SCHEME_QPRINT: */ - } - } while (in_left > 0); + } break; /* case PHP_ICONV_ENC_SCHEME_QPRINT: */ + } + } while (in_left > 0); - smart_str_0(pretval); + smart_str_0(pretval); + } zend_catch { + bailout = true; + } zend_end_try(); out: if (cd != (iconv_t)(-1)) { @@ -1185,6 +1204,9 @@ static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fn if (buf != NULL) { efree(buf); } + if (bailout) { + zend_bailout(); + } return err; } /* }}} */ @@ -1193,6 +1215,7 @@ static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fn static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *str, size_t str_nbytes, const char *enc, const char **next_pos, int mode) { php_iconv_err_t err = PHP_ICONV_ERR_SUCCESS; + bool bailout = false; iconv_t cd = (iconv_t)(-1), cd_pl = (iconv_t)(-1); @@ -1224,203 +1247,226 @@ static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *st } p1 = str; - for (str_left = str_nbytes; str_left > 0; str_left--, p1++) { - int eos = 0; - - switch (scan_stat) { - case 0: /* expecting any character */ - switch (*p1) { - case '\r': /* part of an EOL sequence? */ - scan_stat = 7; - break; + zend_try { + for (str_left = str_nbytes; str_left > 0; str_left--, p1++) { + int eos = 0; + + switch (scan_stat) { + case 0: /* expecting any character */ + switch (*p1) { + case '\r': /* part of an EOL sequence? */ + scan_stat = 7; + break; - case '\n': - scan_stat = 8; - break; + case '\n': + scan_stat = 8; + break; - case '=': /* first letter of an encoded chunk */ - encoded_word = p1; - scan_stat = 1; - break; + case '=': /* first letter of an encoded chunk */ + encoded_word = p1; + scan_stat = 1; + break; - case ' ': case '\t': /* a chunk of whitespaces */ - spaces = p1; - scan_stat = 11; - break; + case ' ': case '\t': /* a chunk of whitespaces */ + spaces = p1; + scan_stat = 11; + break; - default: /* first letter of a non-encoded word */ - err = _php_iconv_appendc(pretval, *p1, cd_pl); - if (err != PHP_ICONV_ERR_SUCCESS) { - if (mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR) { - err = PHP_ICONV_ERR_SUCCESS; - } else { - goto out; + default: /* first letter of a non-encoded word */ + err = _php_iconv_appendc(pretval, *p1, cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + if (mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR) { + err = PHP_ICONV_ERR_SUCCESS; + } else { + goto out; + } } - } - encoded_word = NULL; - if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { - scan_stat = 12; - } - break; - } - break; - - case 1: /* expecting a delimiter */ - if (*p1 != '?') { - if (*p1 == '\r' || *p1 == '\n') { - --p1; - } - err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); - if (err != PHP_ICONV_ERR_SUCCESS) { - goto out; - } - encoded_word = NULL; - if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { - scan_stat = 12; - } else { - scan_stat = 0; + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } + break; } break; - } - csname = p1 + 1; - scan_stat = 2; - break; - - case 2: /* expecting a charset name */ - switch (*p1) { - case '?': /* normal delimiter: encoding scheme follows */ - scan_stat = 3; - break; - - case '*': /* new style delimiter: locale id follows */ - scan_stat = 10; - break; - case '\r': case '\n': /* not an encoded-word */ - --p1; - _php_iconv_appendc(pretval, '=', cd_pl); - _php_iconv_appendc(pretval, '?', cd_pl); - err = _php_iconv_appendl(pretval, csname, (size_t)((p1 + 1) - csname), cd_pl); + case 1: /* expecting a delimiter */ + if (*p1 != '?') { + if (*p1 == '\r' || *p1 == '\n') { + --p1; + } + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); if (err != PHP_ICONV_ERR_SUCCESS) { goto out; } - csname = NULL; + encoded_word = NULL; if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { scan_stat = 12; - } - else { + } else { scan_stat = 0; } - continue; - } - if (scan_stat != 2) { - char tmpbuf[80]; - - if (csname == NULL) { - err = PHP_ICONV_ERR_MALFORMED; - goto out; + break; } + csname = p1 + 1; + scan_stat = 2; + break; - csname_len = (size_t)(p1 - csname); + case 2: /* expecting a charset name */ + switch (*p1) { + case '?': /* normal delimiter: encoding scheme follows */ + scan_stat = 3; + break; - if (csname_len > sizeof(tmpbuf) - 1) { - if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { - err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + case '*': /* new style delimiter: locale id follows */ + scan_stat = 10; + break; + + case '\r': case '\n': /* not an encoded-word */ + --p1; + _php_iconv_appendc(pretval, '=', cd_pl); + _php_iconv_appendc(pretval, '?', cd_pl); + err = _php_iconv_appendl(pretval, csname, (size_t)((p1 + 1) - csname), cd_pl); if (err != PHP_ICONV_ERR_SUCCESS) { goto out; } - encoded_word = NULL; + csname = NULL; if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { scan_stat = 12; - } else { + } + else { scan_stat = 0; } - break; - } else { + continue; + } + if (scan_stat != 2) { + char tmpbuf[80]; + + if (csname == NULL) { err = PHP_ICONV_ERR_MALFORMED; goto out; } - } - - memcpy(tmpbuf, csname, csname_len); - tmpbuf[csname_len] = '\0'; - - if (cd != (iconv_t)(-1)) { - iconv_close(cd); - } - cd = iconv_open(enc, tmpbuf); + csname_len = (size_t)(p1 - csname); - if (cd == (iconv_t)(-1)) { - if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { - /* Bad character set, but the user wants us to - * press on. In this case, we'll just insert the - * undecoded encoded word, since there isn't really - * a more sensible behaviour available; the only - * other options are to swallow the encoded word - * entirely or decode it with an arbitrarily chosen - * single byte encoding, both of which seem to have - * a higher WTF factor than leaving it undecoded. - * - * Given this approach, we need to skip ahead to - * the end of the encoded word. */ - int qmarks = 2; - while (qmarks > 0 && str_left > 1) { - if (*(++p1) == '?') { - --qmarks; + if (csname_len > sizeof(tmpbuf) - 1) { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; } - --str_left; + break; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; } + } + + memcpy(tmpbuf, csname, csname_len); + tmpbuf[csname_len] = '\0'; + + if (cd != (iconv_t)(-1)) { + iconv_close(cd); + } + + cd = iconv_open(enc, tmpbuf); - /* Look ahead to check for the terminating = that - * should be there as well; if it's there, we'll - * also include that. If it's not, there isn't much - * we can do at this point. */ - if (*(p1 + 1) == '=') { - ++p1; - if (str_left > 1) { + if (cd == (iconv_t)(-1)) { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* Bad character set, but the user wants us to + * press on. In this case, we'll just insert the + * undecoded encoded word, since there isn't really + * a more sensible behaviour available; the only + * other options are to swallow the encoded word + * entirely or decode it with an arbitrarily chosen + * single byte encoding, both of which seem to have + * a higher WTF factor than leaving it undecoded. + * + * Given this approach, we need to skip ahead to + * the end of the encoded word. */ + int qmarks = 2; + while (qmarks > 0 && str_left > 1) { + if (*(++p1) == '?') { + --qmarks; + } --str_left; } - } - err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); - if (err != PHP_ICONV_ERR_SUCCESS) { - goto out; - } + /* Look ahead to check for the terminating = that + * should be there as well; if it's there, we'll + * also include that. If it's not, there isn't much + * we can do at this point. */ + if (*(p1 + 1) == '=') { + ++p1; + if (str_left > 1) { + --str_left; + } + } - /* Let's go back and see if there are further - * encoded words or bare content, and hope they - * might actually have a valid character set. */ - scan_stat = 12; - break; - } else { - if (errno == EINVAL) { - err = PHP_ICONV_ERR_WRONG_CHARSET; + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + + /* Let's go back and see if there are further + * encoded words or bare content, and hope they + * might actually have a valid character set. */ + scan_stat = 12; + break; } else { - err = PHP_ICONV_ERR_CONVERTER; + if (errno == EINVAL) { + err = PHP_ICONV_ERR_WRONG_CHARSET; + } else { + err = PHP_ICONV_ERR_CONVERTER; + } + goto out; } - goto out; } } - } - break; + break; - case 3: /* expecting a encoding scheme specifier */ - switch (*p1) { - case 'b': - case 'B': - enc_scheme = PHP_ICONV_ENC_SCHEME_BASE64; - scan_stat = 4; - break; + case 3: /* expecting a encoding scheme specifier */ + switch (*p1) { + case 'b': + case 'B': + enc_scheme = PHP_ICONV_ENC_SCHEME_BASE64; + scan_stat = 4; + break; - case 'q': - case 'Q': - enc_scheme = PHP_ICONV_ENC_SCHEME_QPRINT; - scan_stat = 4; - break; + case 'q': + case 'Q': + enc_scheme = PHP_ICONV_ENC_SCHEME_QPRINT; + scan_stat = 4; + break; - default: + default: + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; + } + } + break; + + case 4: /* expecting a delimiter */ + if (*p1 != '?') { if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* pass the entire chunk through the converter */ err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); if (err != PHP_ICONV_ERR_SUCCESS) { goto out; @@ -1436,299 +1482,281 @@ static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *st err = PHP_ICONV_ERR_MALFORMED; goto out; } - } - break; - - case 4: /* expecting a delimiter */ - if (*p1 != '?') { - if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { - /* pass the entire chunk through the converter */ - err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); - if (err != PHP_ICONV_ERR_SUCCESS) { - goto out; - } - encoded_word = NULL; - if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { - scan_stat = 12; - } else { - scan_stat = 0; - } - break; - } else { - err = PHP_ICONV_ERR_MALFORMED; - goto out; } - } - encoded_text = p1 + 1; - scan_stat = 5; - break; - - case 5: /* expecting an encoded portion */ - if (*p1 == '?') { - encoded_text_len = (size_t)(p1 - encoded_text); - scan_stat = 6; - } - break; + encoded_text = p1 + 1; + scan_stat = 5; + break; - case 7: /* expecting a "\n" character */ - if (*p1 == '\n') { - scan_stat = 8; - } else { - /* bare CR */ - _php_iconv_appendc(pretval, '\r', cd_pl); - _php_iconv_appendc(pretval, *p1, cd_pl); - scan_stat = 0; - } - break; + case 5: /* expecting an encoded portion */ + if (*p1 == '?') { + encoded_text_len = (size_t)(p1 - encoded_text); + scan_stat = 6; + } + break; - case 8: /* checking whether the following line is part of a - folded header */ - if (*p1 != ' ' && *p1 != '\t') { - --p1; - str_left = 1; /* quit_loop */ + case 7: /* expecting a "\n" character */ + if (*p1 == '\n') { + scan_stat = 8; + } else { + /* bare CR */ + _php_iconv_appendc(pretval, '\r', cd_pl); + _php_iconv_appendc(pretval, *p1, cd_pl); + scan_stat = 0; + } break; - } - if (encoded_word == NULL) { - _php_iconv_appendc(pretval, ' ', cd_pl); - } - spaces = NULL; - scan_stat = 11; - break; - case 6: /* expecting a End-Of-Chunk character "=" */ - if (*p1 != '=') { - if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { - /* pass the entire chunk through the converter */ - err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); - if (err != PHP_ICONV_ERR_SUCCESS) { - goto out; - } - encoded_word = NULL; - if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { - scan_stat = 12; - } else { - scan_stat = 0; - } + case 8: /* checking whether the following line is part of a + folded header */ + if (*p1 != ' ' && *p1 != '\t') { + --p1; + str_left = 1; /* quit_loop */ break; - } else { - err = PHP_ICONV_ERR_MALFORMED; - goto out; } - } - scan_stat = 9; - if (str_left == 1) { - eos = 1; - } else { + if (encoded_word == NULL) { + _php_iconv_appendc(pretval, ' ', cd_pl); + } + spaces = NULL; + scan_stat = 11; break; - } - /* TODO might want to rearrange logic so this is more obvious */ - ZEND_FALLTHROUGH; - case 9: /* choice point, seeing what to do next.*/ - switch (*p1) { - default: - /* Handle non-RFC-compliant formats - * - * RFC2047 requires the character that comes right - * after an encoded word (chunk) to be a whitespace, - * while there are lots of broken implementations that - * generate such malformed headers that don't fulfill - * that requirement. - */ - if (!eos) { + case 6: /* expecting a End-Of-Chunk character "=" */ + if (*p1 != '=') { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { - /* pass the entire chunk through the converter */ - err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); - if (err != PHP_ICONV_ERR_SUCCESS) { - goto out; - } scan_stat = 12; - break; + } else { + scan_stat = 0; } + break; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; } - ZEND_FALLTHROUGH; - - case '\r': case '\n': case ' ': case '\t': { - zend_string *decoded_text; - - switch (enc_scheme) { - case PHP_ICONV_ENC_SCHEME_BASE64: - decoded_text = php_base64_decode((unsigned char*)encoded_text, encoded_text_len); - break; - - case PHP_ICONV_ENC_SCHEME_QPRINT: - decoded_text = php_quot_print_decode((unsigned char*)encoded_text, encoded_text_len, 1); - break; - default: - decoded_text = NULL; - break; - } + } + scan_stat = 9; + if (str_left == 1) { + eos = 1; + } else { + break; + } + /* TODO might want to rearrange logic so this is more obvious */ + ZEND_FALLTHROUGH; - if (decoded_text == NULL) { - if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { - /* pass the entire chunk through the converter */ - err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); - if (err != PHP_ICONV_ERR_SUCCESS) { - goto out; - } - encoded_word = NULL; + case 9: /* choice point, seeing what to do next.*/ + switch (*p1) { + default: + /* Handle non-RFC-compliant formats + * + * RFC2047 requires the character that comes right + * after an encoded word (chunk) to be a whitespace, + * while there are lots of broken implementations that + * generate such malformed headers that don't fulfill + * that requirement. + */ + if (!eos) { if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } scan_stat = 12; - } else { - scan_stat = 0; + break; } - break; - } else { - err = PHP_ICONV_ERR_UNKNOWN; - goto out; } - } + ZEND_FALLTHROUGH; - err = _php_iconv_appendl(pretval, ZSTR_VAL(decoded_text), ZSTR_LEN(decoded_text), cd); - if (err == PHP_ICONV_ERR_SUCCESS) { - err = _php_iconv_appendl(pretval, NULL, 0, cd); - } - zend_string_release_ex(decoded_text, 0); + case '\r': case '\n': case ' ': case '\t': { + zend_string *decoded_text; - if (err != PHP_ICONV_ERR_SUCCESS) { - if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { - /* pass the entire chunk through the converter */ - err = _php_iconv_appendl(pretval, encoded_word, (size_t)(p1 - encoded_word), cd_pl); - encoded_word = NULL; - if (err != PHP_ICONV_ERR_SUCCESS) { + switch (enc_scheme) { + case PHP_ICONV_ENC_SCHEME_BASE64: + decoded_text = php_base64_decode((unsigned char*)encoded_text, encoded_text_len); + break; + + case PHP_ICONV_ENC_SCHEME_QPRINT: + decoded_text = php_quot_print_decode((unsigned char*)encoded_text, encoded_text_len, 1); + break; + default: + decoded_text = NULL; + break; + } + + if (decoded_text == NULL) { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)((p1 + 1) - encoded_word), cd_pl); + if (err != PHP_ICONV_ERR_SUCCESS) { + goto out; + } + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } break; + } else { + err = PHP_ICONV_ERR_UNKNOWN; + goto out; } - } else { - goto out; } - } - if (eos) { /* reached end-of-string. done. */ - scan_stat = 0; - break; - } + err = _php_iconv_appendl(pretval, ZSTR_VAL(decoded_text), ZSTR_LEN(decoded_text), cd); + if (err == PHP_ICONV_ERR_SUCCESS) { + err = _php_iconv_appendl(pretval, NULL, 0, cd); + } + zend_string_release_ex(decoded_text, 0); - switch (*p1) { - case '\r': /* part of an EOL sequence? */ - scan_stat = 7; - break; + if (err != PHP_ICONV_ERR_SUCCESS) { + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + /* pass the entire chunk through the converter */ + err = _php_iconv_appendl(pretval, encoded_word, (size_t)(p1 - encoded_word), cd_pl); + encoded_word = NULL; + if (err != PHP_ICONV_ERR_SUCCESS) { + break; + } + } else { + goto out; + } + } - case '\n': - scan_stat = 8; + if (eos) { /* reached end-of-string. done. */ + scan_stat = 0; break; + } - case '=': /* first letter of an encoded chunk */ - scan_stat = 1; - break; + switch (*p1) { + case '\r': /* part of an EOL sequence? */ + scan_stat = 7; + break; - case ' ': case '\t': /* medial whitespaces */ - spaces = p1; - scan_stat = 11; - break; + case '\n': + scan_stat = 8; + break; - default: /* first letter of a non-encoded word */ - _php_iconv_appendc(pretval, *p1, cd_pl); - scan_stat = 12; - break; - } - } break; - } - break; + case '=': /* first letter of an encoded chunk */ + scan_stat = 1; + break; - case 10: /* expects a language specifier. dismiss it for now */ - if (*p1 == '?') { - scan_stat = 3; - } - break; + case ' ': case '\t': /* medial whitespaces */ + spaces = p1; + scan_stat = 11; + break; - case 11: /* expecting a chunk of whitespaces */ - switch (*p1) { - case '\r': /* part of an EOL sequence? */ - scan_stat = 7; - break; + default: /* first letter of a non-encoded word */ + _php_iconv_appendc(pretval, *p1, cd_pl); + scan_stat = 12; + break; + } + } break; + } + break; - case '\n': - scan_stat = 8; - break; + case 10: /* expects a language specifier. dismiss it for now */ + if (*p1 == '?') { + scan_stat = 3; + } + break; - case '=': /* first letter of an encoded chunk */ - if (spaces != NULL && encoded_word == NULL) { - _php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl); - spaces = NULL; - } - encoded_word = p1; - scan_stat = 1; - break; + case 11: /* expecting a chunk of whitespaces */ + switch (*p1) { + case '\r': /* part of an EOL sequence? */ + scan_stat = 7; + break; - case ' ': case '\t': - break; + case '\n': + scan_stat = 8; + break; - default: /* first letter of a non-encoded word */ - if (spaces != NULL) { - _php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl); - spaces = NULL; - } - _php_iconv_appendc(pretval, *p1, cd_pl); - encoded_word = NULL; - if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { - scan_stat = 12; - } else { - scan_stat = 0; - } - break; - } - break; + case '=': /* first letter of an encoded chunk */ + if (spaces != NULL && encoded_word == NULL) { + _php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl); + spaces = NULL; + } + encoded_word = p1; + scan_stat = 1; + break; - case 12: /* expecting a non-encoded word */ - switch (*p1) { - case '\r': /* part of an EOL sequence? */ - scan_stat = 7; - break; + case ' ': case '\t': + break; - case '\n': - scan_stat = 8; - break; + default: /* first letter of a non-encoded word */ + if (spaces != NULL) { + _php_iconv_appendl(pretval, spaces, (size_t)(p1 - spaces), cd_pl); + spaces = NULL; + } + _php_iconv_appendc(pretval, *p1, cd_pl); + encoded_word = NULL; + if ((mode & PHP_ICONV_MIME_DECODE_STRICT)) { + scan_stat = 12; + } else { + scan_stat = 0; + } + break; + } + break; - case ' ': case '\t': - spaces = p1; - scan_stat = 11; - break; + case 12: /* expecting a non-encoded word */ + switch (*p1) { + case '\r': /* part of an EOL sequence? */ + scan_stat = 7; + break; - case '=': /* first letter of an encoded chunk */ - if (!(mode & PHP_ICONV_MIME_DECODE_STRICT)) { - encoded_word = p1; - scan_stat = 1; + case '\n': + scan_stat = 8; break; - } - ZEND_FALLTHROUGH; - default: - _php_iconv_appendc(pretval, *p1, cd_pl); - break; - } - break; + case ' ': case '\t': + spaces = p1; + scan_stat = 11; + break; + + case '=': /* first letter of an encoded chunk */ + if (!(mode & PHP_ICONV_MIME_DECODE_STRICT)) { + encoded_word = p1; + scan_stat = 1; + break; + } + ZEND_FALLTHROUGH; + + default: + _php_iconv_appendc(pretval, *p1, cd_pl); + break; + } + break; + } } - } - switch (scan_stat) { - case 0: case 8: case 11: case 12: - break; - default: - if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { - if (scan_stat == 1) { - _php_iconv_appendc(pretval, '=', cd_pl); + switch (scan_stat) { + case 0: case 8: case 11: case 12: + break; + default: + if ((mode & PHP_ICONV_MIME_DECODE_CONTINUE_ON_ERROR)) { + if (scan_stat == 1) { + _php_iconv_appendc(pretval, '=', cd_pl); + } + err = PHP_ICONV_ERR_SUCCESS; + } else { + err = PHP_ICONV_ERR_MALFORMED; + goto out; } - err = PHP_ICONV_ERR_SUCCESS; - } else { - err = PHP_ICONV_ERR_MALFORMED; - goto out; - } - } + } - if (next_pos != NULL) { - *next_pos = p1; - } + if (next_pos != NULL) { + *next_pos = p1; + } + + smart_str_0(pretval); + } zend_catch { + bailout = true; + } zend_end_try(); - smart_str_0(pretval); out: if (cd != (iconv_t)(-1)) { iconv_close(cd); @@ -1736,6 +1764,9 @@ static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *st if (cd_pl != (iconv_t)(-1)) { iconv_close(cd_pl); } + if (bailout) { + zend_bailout(); + } return err; } /* }}} */ diff --git a/ext/iconv/tests/gh17399.phpt b/ext/iconv/tests/gh17399.phpt new file mode 100644 index 000000000000..b93fc638e84c --- /dev/null +++ b/ext/iconv/tests/gh17399.phpt @@ -0,0 +1,13 @@ +--TEST-- +GH-17399 (iconv memory leak with large line-length in iconv_mime_encode) +--EXTENSIONS-- +iconv +--FILE-- + PHP_INT_MAX, +]; +iconv_mime_encode('Subject', 'test', $options); +?> +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted %s in %s on line %d diff --git a/ext/iconv/tests/gh17399_iconv.phpt b/ext/iconv/tests/gh17399_iconv.phpt new file mode 100644 index 000000000000..2cdd0e176b20 --- /dev/null +++ b/ext/iconv/tests/gh17399_iconv.phpt @@ -0,0 +1,13 @@ +--TEST-- +GH-17399 (iconv() leak on bailout) +--EXTENSIONS-- +iconv +--INI-- +memory_limit=2M +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted %s in %s on line %d diff --git a/ext/iconv/tests/gh17399_mime_decode.phpt b/ext/iconv/tests/gh17399_mime_decode.phpt new file mode 100644 index 000000000000..95a37d364f33 --- /dev/null +++ b/ext/iconv/tests/gh17399_mime_decode.phpt @@ -0,0 +1,13 @@ +--TEST-- +GH-17399 (iconv_mime_decode() leak on bailout) +--EXTENSIONS-- +iconv +--INI-- +memory_limit=2M +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted %s in %s on line %d diff --git a/ext/iconv/tests/gh17399_substr.phpt b/ext/iconv/tests/gh17399_substr.phpt new file mode 100644 index 000000000000..35d791db59f0 --- /dev/null +++ b/ext/iconv/tests/gh17399_substr.phpt @@ -0,0 +1,13 @@ +--TEST-- +GH-17399 (iconv_substr() leak on bailout) +--EXTENSIONS-- +iconv +--INI-- +memory_limit=2M +--FILE-- + +--EXPECTF-- +Fatal error: Allowed memory size of %d bytes exhausted %s in %s on line %d From 5cd877781a16f26e9d4e77051589af218772d716 Mon Sep 17 00:00:00 2001 From: Ilija Tovilo Date: Sat, 28 Mar 2026 19:19:20 +0100 Subject: [PATCH 6/6] [skip ci] Mark gh17399_substr.phpt as slow --- ext/iconv/tests/gh17399_substr.phpt | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/ext/iconv/tests/gh17399_substr.phpt b/ext/iconv/tests/gh17399_substr.phpt index 35d791db59f0..12d4cff28eac 100644 --- a/ext/iconv/tests/gh17399_substr.phpt +++ b/ext/iconv/tests/gh17399_substr.phpt @@ -4,6 +4,10 @@ GH-17399 (iconv_substr() leak on bailout) iconv --INI-- memory_limit=2M +--SKIPIF-- + --FILE--