From 5762b6d2183581990c168099f277b77be55addd4 Mon Sep 17 00:00:00 2001 From: John Schmidt <3405586+schmee@users.noreply.github.com> Date: Sat, 29 Jan 2022 12:25:25 +0100 Subject: [PATCH] std.fmt: fix out-of-bounds array write in float printing This commit fixes an out of bounds write that can occur when formatting certain float values. The write messes up the stack and causes incorrect results, segfaults, or nothing at all, depending on the optimization mode used. The `errol` function writes the digits of the float into `buffer` starting from index 1, leaving index 0 untouched, and returns `buffer[1..]` and the exponent. This is because `roundToPrecision` relies on index 0 being unused in case the rounding adds a digit (e.g rounding 999.99 to 1000.00). When this happens, pointer arithmetic is used [here](https://github.com/ziglang/zig/blob/0e6d2184cacf2dd1fad7508b2f9ae99d78763148/lib/std/fmt/errol.zig#L61-L65) to access index 0 and put the ones digit in the right place. However, `errol3u` contains two special cases: `errolInt` and `errolFixed`, which return from the function early. For these two special cases index 0 was never reserved, and the return value contains `buffer` instead of `buffer[1..]`. This causes the pointer arithmetic in `roundToPrecision` to write out of bounds, which in the case of `std.fmt.formatFloatDecimal` messes up the stack and causes undefined behavior. The fix is to move the slicing of `buffer` to `buffer[1..]` from `errol3u` to `errol` so that both the default and the special cases operate on the sliced buffer. --- lib/std/fmt.zig | 1 + lib/std/fmt/errol.zig | 13 ++++++------- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/lib/std/fmt.zig b/lib/std/fmt.zig index df520940fd..31171e2028 100644 --- a/lib/std/fmt.zig +++ b/lib/std/fmt.zig @@ -2259,6 +2259,7 @@ test "float.decimal" { try expectFmt("f64: 0.00030000", "f64: {d:.8}", .{@as(f64, 0.0003)}); try expectFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 1.40130e-45)}); try expectFmt("f64: 0.00000", "f64: {d:.5}", .{@as(f64, 9.999960e-40)}); + try expectFmt("f64: 10000000000000.00", "f64: {d:.2}", .{@as(f64, 9999999999999.999)}); } test "float.libc.sanity" { diff --git a/lib/std/fmt/errol.zig b/lib/std/fmt/errol.zig index e697b7d42f..e98c23f6ec 100644 --- a/lib/std/fmt/errol.zig +++ b/lib/std/fmt/errol.zig @@ -92,7 +92,10 @@ pub fn errol3(value: f64, buffer: []u8) FloatDecimal { }; } - return errol3u(value, buffer); + // We generate digits starting at index 1. If rounding a buffer later then it may be + // required to generate a preceding digit in some cases (9.999) in which case we use + // the 0-index for this extra digit. + return errol3u(value, buffer[1..]); } /// Uncorrected Errol3 double to ASCII conversion. @@ -162,11 +165,7 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal { } // digit generation - - // We generate digits starting at index 1. If rounding a buffer later then it may be - // required to generate a preceding digit in some cases (9.999) in which case we use - // the 0-index for this extra digit. - var buf_index: usize = 1; + var buf_index: usize = 0; while (true) { var hdig = @floatToInt(u8, math.floor(high.val)); if ((high.val == @intToFloat(f64, hdig)) and (high.off < 0)) hdig -= 1; @@ -192,7 +191,7 @@ fn errol3u(val: f64, buffer: []u8) FloatDecimal { buf_index += 1; return FloatDecimal{ - .digits = buffer[1..buf_index], + .digits = buffer[0..buf_index], .exp = exp, }; }