[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