-
Notifications
You must be signed in to change notification settings - Fork 8k
Fix GH-17399: iconv memory leak with large line-length #21541
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
4afdcf5
12b48b9
5dd9815
21c3a24
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -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,68 @@ 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); | ||
| 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) { | ||
| case E2BIG: | ||
| break; | ||
| } | ||
| if (err == PHP_ICONV_ERR_SUCCESS) { | ||
| if (cd2 != (iconv_t)NULL) { | ||
| _php_iconv_appendl(pretval, NULL, 0, cd2); | ||
| } | ||
| smart_str_0(pretval); | ||
| } | ||
| } zend_catch { | ||
| if (cd1 != (iconv_t)NULL) { | ||
| iconv_close(cd1); | ||
| } | ||
| if (cd2 != (iconv_t)NULL) { | ||
| _php_iconv_appendl(pretval, NULL, 0, cd2); | ||
| iconv_close(cd2); | ||
| } | ||
| smart_str_0(pretval); | ||
| } | ||
| zend_bailout(); | ||
| } zend_end_try(); | ||
|
|
||
| if (cd1 != (iconv_t)NULL) { | ||
| iconv_close(cd1); | ||
|
|
@@ -904,6 +920,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,10 +979,10 @@ 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); | ||
|
|
||
| char_cnt = max_line_len; | ||
|
|
||
| zend_try { | ||
| buf = safe_emalloc(1, max_line_len, 5); | ||
| _php_iconv_appendl(pretval, fname, fname_nbytes, cd_pl); | ||
| char_cnt -= fname_nbytes; | ||
| smart_str_appendl(pretval, ": ", sizeof(": ") - 1); | ||
|
|
@@ -1171,6 +1188,9 @@ static php_iconv_err_t _php_iconv_mime_encode(smart_str *pretval, const char *fn | |
| } while (in_left > 0); | ||
|
|
||
| smart_str_0(pretval); | ||
| } zend_catch { | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. The newline above should be removed.
Contributor
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Done |
||
| bailout = true; | ||
| } zend_end_try(); | ||
|
|
||
| out: | ||
| if (cd != (iconv_t)(-1)) { | ||
|
|
@@ -1185,6 +1205,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 +1216,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,6 +1248,7 @@ static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *st | |
| } | ||
|
|
||
| p1 = str; | ||
| zend_try { | ||
| for (str_left = str_nbytes; str_left > 0; str_left--, p1++) { | ||
| int eos = 0; | ||
|
|
||
|
|
@@ -1729,13 +1754,21 @@ static php_iconv_err_t _php_iconv_mime_decode(smart_str *pretval, const char *st | |
| } | ||
|
|
||
| smart_str_0(pretval); | ||
|
|
||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. Same here. That's what the majority of the uses of zend_try/catch do. |
||
| } zend_catch { | ||
| bailout = true; | ||
| } zend_end_try(); | ||
|
|
||
| out: | ||
| if (cd != (iconv_t)(-1)) { | ||
| iconv_close(cd); | ||
| } | ||
| if (cd_pl != (iconv_t)(-1)) { | ||
| iconv_close(cd_pl); | ||
| } | ||
| if (bailout) { | ||
| zend_bailout(); | ||
| } | ||
| return err; | ||
| } | ||
| /* }}} */ | ||
|
|
||
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| --TEST-- | ||
| GH-17399 (iconv memory leak with large line-length in iconv_mime_encode) | ||
| --EXTENSIONS-- | ||
| iconv | ||
| --FILE-- | ||
| <?php | ||
| $options = [ | ||
| 'line-length' => 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 |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| --TEST-- | ||
| GH-17399 (iconv() leak on bailout) | ||
| --EXTENSIONS-- | ||
| iconv | ||
| --INI-- | ||
| memory_limit=2M | ||
| --FILE-- | ||
| <?php | ||
| $str = str_repeat('a', 1024 * 1024); | ||
| iconv('UTF-8', 'ISO-8859-1', $str); | ||
| ?> | ||
| --EXPECTF-- | ||
| Fatal error: Allowed memory size of %d bytes exhausted %s in %s on line %d |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| --TEST-- | ||
| GH-17399 (iconv_mime_decode() leak on bailout) | ||
| --EXTENSIONS-- | ||
| iconv | ||
| --INI-- | ||
| memory_limit=2M | ||
| --FILE-- | ||
| <?php | ||
| $str = str_repeat('a', 1024 * 1024); | ||
| iconv_mime_decode($str, 0, 'UTF-8'); | ||
| ?> | ||
| --EXPECTF-- | ||
| Fatal error: Allowed memory size of %d bytes exhausted %s in %s on line %d |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,13 @@ | ||
| --TEST-- | ||
| GH-17399 (iconv_substr() leak on bailout) | ||
| --EXTENSIONS-- | ||
| iconv | ||
| --INI-- | ||
| memory_limit=2M | ||
| --FILE-- | ||
| <?php | ||
| $str = str_repeat('a', 1024 * 1024); | ||
| iconv_substr($str, 0, 1024 * 1024, 'UTF-8'); | ||
| ?> | ||
| --EXPECTF-- | ||
| Fatal error: Allowed memory size of %d bytes exhausted %s in %s on line %d |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'd prefer to place this directly above
zend_try.There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I left that one out, it triggered
-Wsometimes-uninitializedon clang before, so while it could be moved a bit lower, this seem ok to me.