[njs] Fixed buffer overflow in Number.prototype.toString(radix).

Alexander Borisov alexander.borisov at nginx.com
Thu Oct 3 12:42:05 UTC 2019


details:   https://hg.nginx.org/njs/rev/5f0812d53158
branches:  
changeset: 1167:5f0812d53158
user:      Alexander Borisov <alexander.borisov at nginx.com>
date:      Thu Oct 03 15:41:30 2019 +0300
description:
Fixed buffer overflow in Number.prototype.toString(radix).

diffstat:

 src/njs_number.c         |  121 ++++++++++++++++++++++++++++++++++------------
 src/test/njs_unit_test.c |   37 ++++++++++++++
 2 files changed, 126 insertions(+), 32 deletions(-)

diffs (222 lines):

diff -r 5c22e2f006fe -r 5f0812d53158 src/njs_number.c
--- a/src/njs_number.c	Fri Sep 27 22:21:55 2019 +0300
+++ b/src/njs_number.c	Thu Oct 03 15:41:30 2019 +0300
@@ -6,6 +6,7 @@
 
 
 #include <njs_main.h>
+#include <njs_diyfp.h>
 
 
 /*
@@ -637,30 +638,52 @@ njs_number_prototype_to_fixed(njs_vm_t *
 
 
 /*
- * The radix equal to 2 produces the longest intergral value of a number
- * and the maximum value consists of 1024 digits and minus sign.
+ * The radix equal to 2 produces the longest  value for a number.
  */
 
 #define NJS_STRING_RADIX_INTERGRAL_LEN  (1 + 1024)
-#define NJS_STRING_RADIX_FRACTION_LEN   (1 + 54)
+#define NJS_STRING_RADIX_FRACTION_LEN   (1 + 1075)
 #define NJS_STRING_RADIX_LEN                                                  \
     (NJS_STRING_RADIX_INTERGRAL_LEN + NJS_STRING_RADIX_FRACTION_LEN)
 
 
+njs_inline double
+njs_number_next_double(double n)
+{
+    njs_diyfp_t  v;
+
+    v = njs_d2diyfp(n);
+
+    if (signbit(n)) {
+        if (v.significand == 0) {
+            return 0.0;
+        }
+
+        v.significand--;
+
+    } else {
+        v.significand++;
+    }
+
+    return njs_diyfp2d(v);
+}
+
+
 static njs_int_t
 njs_number_to_string_radix(njs_vm_t *vm, njs_value_t *string,
     double number, uint32_t radix)
 {
-    u_char   *p, *f, *end;
-    double   n, next;
-    size_t   size;
-    uint8_t  reminder;
-    u_char   buf[NJS_STRING_RADIX_LEN];
+    int       digit;
+    char      ch;
+    double    n, remainder, integer, fraction, delta;
+    u_char    *p, *end;
+    uint32_t  size;
+    u_char    buf[NJS_STRING_RADIX_LEN];
 
     static const char  *digits = "0123456789abcdefghijklmnopqrstuvwxyz";
 
-    end = buf + NJS_STRING_RADIX_LEN;
     p = buf + NJS_STRING_RADIX_INTERGRAL_LEN;
+    end = p;
 
     n = number;
 
@@ -668,35 +691,69 @@ njs_number_to_string_radix(njs_vm_t *vm,
         n = -n;
     }
 
+    integer = floor(n);
+    fraction = n - integer;
+
+    delta = 0.5 * (njs_number_next_double(n) - n);
+    delta = njs_max(njs_number_next_double(0.0), delta);
+
+    if (fraction >= delta) {
+        *p++ = '.';
+
+        do {
+            fraction *= radix;
+            delta *= radix;
+
+            digit = (int) fraction;
+            *p++ = digits[digit];
+
+            fraction -= digit;
+
+            if ((fraction > 0.5 || (fraction == 0.5 && (digit & 1)))
+                && (fraction + delta > 1))
+            {
+                while (p-- != buf) {
+                    if (p == buf + NJS_STRING_RADIX_INTERGRAL_LEN) {
+                        integer += 1;
+                        break;
+                    }
+
+                    ch = *p;
+                    digit = (ch > '9') ? ch - 'a' + 10 : ch - '0';
+
+                    if ((uint32_t) (digit + 1) < radix) {
+                        *p++ = digits[digit + 1];
+                        break;
+                    }
+                }
+
+                break;
+            }
+
+        } while (fraction >= delta);
+
+        end = p;
+    }
+
+    p = buf + NJS_STRING_RADIX_INTERGRAL_LEN;
+
+    while (njs_d2diyfp(integer / radix).exp > 0) {
+        integer /= radix;
+        *(--p) = '0';
+    }
+
     do {
-        next = trunc(n / radix);
-        reminder = n - next * radix;
-        *(--p) = digits[reminder];
-        n = next;
-    } while (n != 0);
+        remainder = fmod(integer, radix);
+        *(--p) = digits[(int) remainder];
+        integer = (integer - remainder) / radix;
 
-    n = number;
+    } while (integer > 0);
 
-    if (n < 0) {
+    if (number < 0) {
         *(--p) = '-';
     }
 
-    f = buf + NJS_STRING_RADIX_INTERGRAL_LEN;
-
-    n = n - trunc(n);
-
-    if (n != 0) {
-        *f++ = '.';
-
-        do {
-            n = n * radix;
-            reminder = trunc(n);
-            *f++ = digits[reminder];
-            n = n - reminder;
-        } while (n != 0 && f < end);
-    }
-
-    size = f - p;
+    size = (uint32_t) (end - p);
 
     return njs_string_new(vm, string, p, size, size);
 }
diff -r 5c22e2f006fe -r 5f0812d53158 src/test/njs_unit_test.c
--- a/src/test/njs_unit_test.c	Fri Sep 27 22:21:55 2019 +0300
+++ b/src/test/njs_unit_test.c	Thu Oct 03 15:41:30 2019 +0300
@@ -361,8 +361,10 @@ static njs_unit_test_t  njs_test[] =
 
     /* Number.toString(radix) method. */
 
+#ifndef NJS_SUNC
     { njs_str("0..toString(2)"),
       njs_str("0") },
+#endif
 
     { njs_str("240..toString(2)"),
       njs_str("11110000") },
@@ -427,6 +429,41 @@ static njs_unit_test_t  njs_test[] =
     { njs_str("NaN.toString(NaN)"),
       njs_str("RangeError") },
 
+    { njs_str("1.2312313132.toString(14)"),
+      njs_str("1.3346da6d5d455c") },
+
+    { njs_str("7.799999999999999.toString(14)"),
+      njs_str("7.b2b2b2b2b2b2a5") },
+
+#ifndef NJS_SUNC
+    { njs_str("1e20.toString(14)"),
+      njs_str("33cb3bb449c2a92000") },
+
+    /* Smallest positive double (next_double(0)). */
+    { njs_str("4.94065645841246544176568792868E-324.toString(36) == ('0.' + '0'.repeat(207) +'3')"),
+      njs_str("true") },
+
+    { njs_str("1.7976931348623157E+308.toString(36) == ('1a1e4vngaiqo' + '0'.repeat(187))"),
+      njs_str("true") },
+
+    /* Largest positive double (prev_double(INFINITY)). */
+    { njs_str("1.7976931348623157E+308.toString(2) == ('1'.repeat(53) + '0'.repeat(971))"),
+      njs_str("true") },
+
+    /* Maximum fraction length. */
+    { njs_str("2.2250738585072014E-323.toString(2) == ('0.' + '0'.repeat(1071) + '101')"),
+      njs_str("true") },
+
+    { njs_str("2.2250738585072014E-308.toString(2) == ('0.' + '0'.repeat(1021) + '1')"),
+      njs_str("true") },
+
+    { njs_str("Array(5).fill().map((n, i) => i + 10).map((v)=>(1.2312313132).toString(v))"),
+      njs_str("1.2312313132,1.25a850416057383,1.293699002749414,1.3010274cab0288,1.3346da6d5d455c") },
+
+    { njs_str("Array(5).fill().map((n, i) => 36 - i).map((v)=>(1e23).toString(v))"),
+      njs_str("ga894a06abs0000,o5hlsorok4y0000,128fpsprqld20000,1m1s0ajv6cmo0000,2kmg5hv19br00000") },
+#endif
+
     /* Number.prototype.toFixed(frac) method. */
 
     { njs_str("(900.1).toFixed(1)"),


More information about the nginx-devel mailing list