Fix heap buffer underflow in PDC_mbstowcs() and PDC_wcstombs()#384
Fix heap buffer underflow in PDC_mbstowcs() and PDC_wcstombs()#384serhiy-storchaka wants to merge 1 commit into
Conversation
GitMensch
left a comment
There was a problem hiding this comment.
@Bill-Gray we need to define how those functions behave when returning -1 (should the dest buffer be zero-terminated or not) and do it identical in both functions and w/wo PDC_FORCE_UTF8
| if (i == (size_t)-1) /* an invalid multibyte sequence */ | ||
| i = 0; |
There was a problem hiding this comment.
per code above that is wrong - instead -1 should be returned
There was a problem hiding this comment.
@Bill-Gray we should possibly return -1 here instead of asserting (then do the same for the -1 case below)
Without checking the code I'm not sure where we copy the trailing zero - and if we do that on each memcpy (which would be a waste)
Side note: While the compiler will likely optimize that, I'd suggest to const size_t m = m - 1.
|
You're both right, and this code is wrong no matter how you slice it. Pinging @wmbrine because the same issue is in "upstream" PDCurses and should be fixed there as well. Quoting from the documentation at the top of the file, The man page for So. In the non- Strictly speaking, we aren't quite done yet. The diff --git a/pdcurses/util.c b/pdcurses/util.c
index 3c059f38..89fd014f 100644
--- a/pdcurses/util.c
+++ b/pdcurses/util.c
@@ -473,30 +473,33 @@ size_t PDC_mbstowcs(wchar_t *dest, const char *src, size_t n)
size_t PDC_wcstombs(char *dest, const wchar_t *src, size_t n)
{
# ifdef PDC_FORCE_UTF8
- size_t i = 0;
+ size_t i = 0, count = 1;
assert( src);
assert( dest);
if (!src || !dest)
return 0;
- while( i + 4 < n && *src)
- i += PDC_wc_to_utf8( dest + i, *src++);
- while( i < n && *src)
+ while( count && i + 4 < n && *src)
+ i += (count = PDC_wc_to_utf8( dest + i, *src++));
+ while( count && i < n && *src)
{
char tbuff[4];
- size_t count = (size_t)PDC_wc_to_utf8( tbuff, *src++);
- assert( count <= n - i); /* partial UTF-8 decoding indicates error */
+ count = (size_t)PDC_wc_to_utf8( tbuff, *src++);
+ assert( count <= n - i); /* don't go past end of buffer */
if( count > n - i)
count = n - i;
memcpy( dest + i, tbuff, count);
i += count;
}
+ if( !count) /* invalid UTF-8 sequence encountered */
+ return (size_t)-1;
+ if( i < n)
+ dest[i] = '\0';
# else
size_t i = wcstombs(dest, src, n);
# endif
- dest[i] = '\0';
return i;
}
#endif
|
mbstowcs()/wcstombs() return (size_t)-1, without appending a terminator, when they meet a character they cannot convert. Both PDC_ wrappers then wrote dest[i] = 0 unconditionally, so on failure dest[(size_t)-1] stored one element before the buffer and corrupted adjacent memory. Now they return (size_t)-1 right away in that case, leaving dest unterminated, and write the terminator only on success -- where callers such as winnstr() rely on it (dest is sized for it). The PDC_FORCE_UTF8 branch of PDC_wcstombs() is made consistent: it stops and returns -1 on an invalid sequence rather than asserting.
838a3d8 to
daf5e76
Compare
|
Thanks @Bill-Gray — agreed the original is wrong. But dropping the terminator entirely in the non- I hit this via the CPython So I kept the error contract only for the failure path — return |
Fixes #383.
mbstowcs()/wcstombs()return(size_t)-1on an invalid multibyte sequence or a wide character the current locale cannot encode.PDC_mbstowcs()andPDC_wcstombs()stored the terminating NUL atdest[i]without checking, sodest[(size_t)-1]wrote one element before the start of the buffer — an out-of-bounds write corrupting adjacent memory. This is reachable fromwinnstr()/winnwstr()on a window that holds a character the current locale cannot encode (details and a reproducer in #383).Guard the
(size_t)-1error return before writing the terminator. On failure the functions now yield an empty, properly terminated string — the same result as "nothing was convertible".Only the non-
PDC_FORCE_UTF8branches were affected; the forced-UTF-8 paths already return early on error and never reach the terminator write.