regcomp.c: Use Perl_form() instead of buggy sprintf
authorKarl Williamson <public@khwilliamson.com>
Wed, 11 Sep 2013 02:40:25 +0000 (20:40 -0600)
committerKarl Williamson <public@khwilliamson.com>
Wed, 11 Sep 2013 03:02:58 +0000 (21:02 -0600)
Commit 4cabb89a737018190d4e09360a6615e19160709f introduced tests
which include trying to look up a Unicode property whose name
is an upper Latin-range character.  This caused errors and a segfault
on some but not all patforms.  It also turns out that the error is in
some locales but not others.

The problem was there all along, and this commit merely exposed it; and
the bug is in the libc sprintf() for those platforms, which was used
to create the look-up property name.  This version of sprintf() turns
out to be locale-sensitive, and in a UTF-8 locale, it refuses to format
a %s that isn't valid UTF-8.  My guess is that sprintf and printf share
implementation, and such sensitivity may be warranted for printf, but
certainly not for sprintf.  It is undocumented behavior.  And it is
incorrect UTF-8 handling even if one were to output UTF-8 only.  The
reason for that is any malformed text should be turned into the
REPLACEMENT CHARACTER, not just skipped over.  This is a potential
security hole in in this sprintf() version.

The solution I ended up for Perl is to replace the sprintf() with
Perl_form().  I also looked at my_strlcpy() and friends.  Neither one is
as convenient as the buggy sprintf.  Feel free to replace this with
something better.

regcomp.c

index 10b1aa3..0034298 100644 (file)
--- a/regcomp.c
+++ b/regcomp.c
@@ -12513,6 +12513,7 @@ parseit:
                }
                if (!SIZE_ONLY) {
                     SV* invlist;
+                    char* formatted;
                     char* name;
 
                    if (UCHARAT(RExC_parse) == '^') {
@@ -12533,14 +12534,14 @@ parseit:
                      * will have its name be <__NAME_i>.  The design is
                      * discussed in commit
                      * 2f833f5208e26b208886e51e09e2c072b5eabb46 */
-                    Newx(name, n + sizeof("_i__\n"), char);
-
-                    sprintf(name, "%s%.*s%s\n",
-                                    (FOLD) ? "__" : "",
-                                    (int)n,
-                                    RExC_parse,
-                                    (FOLD) ? "_i" : ""
-                    );
+                    formatted = Perl_form(aTHX_
+                                          "%s%.*s%s\n",
+                                          (FOLD) ? "__" : "",
+                                          (int)n,
+                                          RExC_parse,
+                                          (FOLD) ? "_i" : ""
+                                );
+                    name = savepvn(formatted, strlen(formatted));
 
                     /* Look up the property name, and get its swash and
                      * inversion list, if the property is found  */