This is a live mirror of the Perl 5 development currently hosted at https://github.com/perl/perl5
Make utf8_to_uvchr() safer
authorKarl Williamson <khw@cpan.org>
Tue, 17 Jul 2018 19:57:54 +0000 (13:57 -0600)
committerKarl Williamson <khw@cpan.org>
Tue, 17 Jul 2018 20:11:51 +0000 (14:11 -0600)
This function is deprecated because the API doesn't allow it to
determine the end of the input string, so it can read off the far end.
But I just realized that since many strings are NUL-terminated, so we
can forbid it from reading past the next NUL, and hence make it safe in
many cases.

utf8.c

diff --git a/utf8.c b/utf8.c
index dec8aa1..51039ae 100644 (file)
--- a/utf8.c
+++ b/utf8.c
@@ -6345,7 +6345,26 @@ Perl_utf8_to_uvchr(pTHX_ const U8 *s, STRLEN *retlen)
 {
     PERL_ARGS_ASSERT_UTF8_TO_UVCHR;
 
-    return utf8_to_uvchr_buf(s, s + UTF8_MAXBYTES, retlen);
+    /* This function is unsafe if malformed UTF-8 input is given it, which is
+     * why the function is deprecated.  If the first byte of the input
+     * indicates that there are more bytes remaining in the sequence that forms
+     * the character than there are in the input buffer, it can read past the
+     * end.  But we can make it safe if the input string happens to be
+     * NUL-terminated, as many strings in Perl are, by refusing to read past a
+     * NUL.  A NUL indicates the start of the next character anyway.  If the
+     * input isn't NUL-terminated, the function remains unsafe, as it always
+     * has been.
+     *
+     * An initial NUL has to be handled separately, but all ASCIIs can be
+     * handled the same way, speeding up this common case */
+
+    if (UTF8_IS_INVARIANT(*s)) {  /* Assumes 's' contains at least 1 byte */
+        return (UV) *s;
+    }
+
+    return utf8_to_uvchr_buf(s,
+                             s + strnlen((char *) s, UTF8_MAXBYTES),
+                            retlen);
 }
 
 /*