Fix heap buffer underflow in PDC_mbstowcs() - #384
serhiy-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
|
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 |
…encodeable wide character or runs out of buffer space. Fixes issue #384.
|
Returning to this after quite a while... My initial thought was that However, it's internal to PDCursesMod, called in three places, and in each of them, we ought to have a guarantee of null termination if we get to the end of the buffer or hit an un-encodeable character. I considered trying to return a partial string in such cases ("we got this far and then either hit an un-encodeable character or ran out of buffer space"). The problem is that Re "callers expect an n+1 buffer NUL-terminated" : sort of true, but callers really should check the return value. If it's ERR, it'll only be null-terminated as of this patch. With an older library, it may not be null-terminated. (Much as anyone calling |
mbstowcs() returns (size_t)-1 on an invalid multibyte sequence, and PDC_mbstowcs() then stored the terminator at dest[(size_t)-1], one element before the buffer. Return (size_t)-1 with an empty dest instead, as PDC_wcstombs() does since ea9baa4. Fixes Bill-Gray#383.
daf5e76 to
8d94d4a
Compare
|
The The null-termination guarantee does not need that branch: every caller gives I have rebased this PR onto master and reduced it to the |
Fixes #383.
mbstowcs()returns(size_t)-1on an invalid multibyte sequence, without writing a terminator, andPDC_mbstowcs()then stored the terminator atdest[(size_t)-1], one element before the buffer. Return(size_t)-1with an emptydestinstead, asPDC_wcstombs()does since ea9baa4.With the
vtport and a guard element placed before the buffer,PDC_mbstowcs(buf, "AB\xff", 8)in a UTF-8 locale overwrote the guard with 0 and leftbufunterminated; it now leaves the guard alone and setsbuf[0] = 0.The
PDC_wcstombs()half of the original PR is superseded by ea9baa4 (see #390 / #391 for the exact-fit case).