tech-userlevel archive

[Date Prev][Date Next][Thread Prev][Thread Next][Date Index][Thread Index][Old Index]

__hldtoa broken for ld128



this was found by fuzzing the LLVM __cxa_demangle on an ld128 Android
system using hwasan, but it turns out no to simply be a buffer
overflow --- the results are just wrong. (which shows how much anyone
uses ld128 in conjunction with %a!)

this was the minimized test case:

free(__cxa_demangle("1\006ILeeeEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEEE", 0, 0, 0));

which, going down a level to snprintf was working out to something
like this gtest:

  char buf[BUFSIZ];
  union {
    uint64_t a[2];
    long double v;
  } u;
  u.a[0] = UINT64_C(0xcececececececece);
  u.a[1] = UINT64_C(0xcececececececece);
  EXPECT_EQ(41, snprintf(buf, sizeof(buf), "<%La>", u.v));
  EXPECT_STREQ("<-0x1.cecececececececececececececep+3791>", buf);


here's a fix, the diff being relative to the copy of the OpenBSD
source that's used in Android (but the NetBSD source looks the same,
with minor cosmetic differences, and this should fix both):


--- a/libc/upstream-openbsd/lib/libc/gdtoa/hdtoa.c
+++ b/libc/upstream-openbsd/lib/libc/gdtoa/hdtoa.c
@@ -278,18 +278,18 @@ __hldtoa(long double e, const char *xdigs, int
ndigits, int *decpt, int *sign,
                p->ext_fracl >>= 4;
        }
 #ifdef EXT_FRACHMBITS
-       for (; s > s0; s--) {
+       for (; s > s0 + sigfigs - ((EXT_FRACLBITS + EXT_FRACHMBITS) /
4) - 1; s--) {
                *s = p->ext_frachm & 0xf;
                p->ext_frachm >>= 4;
        }
 #endif
 #ifdef EXT_FRACLMBITS
-       for (; s > s0; s--) {
+       for (; s > s0 + sigfigs - ((EXT_FRACLBITS + EXT_FRACHMBITS +
EXT_FRACLMBITS) / 4) - 1; s--) {
                *s = p->ext_fraclm & 0xf;
                p->ext_fraclm >>= 4;
        }
 #endif
-       for (; s > s0; s--) {
+       for (; s > s0 + sigfigs - ((EXT_FRACLBITS + EXT_FRACHMBITS +
EXT_FRACLMBITS + EXT_FRACHBITS) / 4) - 1; s--) {
                *s = p->ext_frach & 0xf;
                p->ext_frach >>= 4;
        }
@@ -300,7 +300,7 @@ __hldtoa(long double e, const char *xdigs, int
ndigits, int *decpt, int *sign,
         * (partial) nibble, which is dealt with by the next
         * statement.  We also tack on the implicit normalization bit.
         */
-       *s = p->ext_frach | (1U << ((LDBL_MANT_DIG - 1) % 4));
+       *s = (p->ext_frach | (1U << ((LDBL_MANT_DIG - 1) % 4))) & 0xf;

        /* If ndigits < 0, we are expected to auto-size the precision. */
        if (ndigits < 0) {


if you want to watch it going wrong live in a way that makes the bug
quite plain, i recommend adding `fprintf(stderr, "%02x // %x\n", *s,
p->ext_<whatever>);` between each `*s =` line and the `>>= 4` line
that follows. that was where i understood what was going wrong.

thanks!


Home | Main Index | Thread Index | Old Index