diff options
author | 2023-09-05 14:25:19 -0700 | |
---|---|---|
committer | 2023-09-05 14:25:19 -0700 | |
commit | 1e998c1bf2e0c95fb182eb01806bf11eebe6fed3 (patch) | |
tree | 21ba4289dd7be6b621505ca084960ae6b7c9a5ef /src/install/padding_checker.zig | |
parent | bc2b55fdeee3001b07252d0f250671ea1876a3ed (diff) | |
download | bun-1e998c1bf2e0c95fb182eb01806bf11eebe6fed3.tar.gz bun-1e998c1bf2e0c95fb182eb01806bf11eebe6fed3.tar.zst bun-1e998c1bf2e0c95fb182eb01806bf11eebe6fed3.zip |
fix(install): ensure all lockfile structs do not have undefined padding (#4401)
* padding sucks
* this assertion is already done elsewhere
* remove test. will be covered alex's pr i believe?
* fix webkit submodule
* fix uws submodule
Diffstat (limited to 'src/install/padding_checker.zig')
-rw-r--r-- | src/install/padding_checker.zig | 93 |
1 files changed, 93 insertions, 0 deletions
diff --git a/src/install/padding_checker.zig b/src/install/padding_checker.zig new file mode 100644 index 000000000..1d9405a43 --- /dev/null +++ b/src/install/padding_checker.zig @@ -0,0 +1,93 @@ +const std = @import("std"); + +/// In some parts of lockfile serialization, Bun will use `std.mem.sliceAsBytes` to convert a struct into raw +/// bytes to write. This makes lockfile serialization/deserialization much simpler/faster, at the cost of not +/// having any pointers within these structs. +/// +/// One major caveat of this is that if any of these structs have uninitialized memory, then that can leak +/// garbage memory into the lockfile. See https://github.com/oven-sh/bun/issues/4319 +/// +/// The obvious way to introduce undefined memory into a struct is via `.field = undefined`, but a much more +/// subtle way is to have implicit padding in an extern struct. For example: +/// ```zig +/// const Demo = struct { +/// a: u8, // @sizeOf(Demo, "a") == 1, @offsetOf(Demo, "a") == 0 +/// b: u64, // @sizeOf(Demo, "b") == 8, @offsetOf(Demo, "b") == 8 +/// } +/// ``` +/// +/// `a` is only one byte long, but due to the alignment of `b`, there is 7 bytes of padding between `a` and `b`, +/// which is considered *undefined memory*. +/// +/// The solution is to have it explicitly initialized to zero bytes, like: +/// ```zig +/// const Demo = struct { +/// a: u8, +/// _padding: [7]u8 = .{0} ** 7, +/// b: u64, // same offset as before +/// } +/// ``` +/// +/// There is one other way to introduce undefined memory into a struct, which this does not check for, and that is +/// a union with unequal size fields. +pub fn assertNoUninitializedPadding(comptime T: type) void { + const info_ = @typeInfo(T); + const info = switch (info_) { + .Struct => info_.Struct, + .Union => info_.Union, + .Array => |a| { + assertNoUninitializedPadding(a.child); + return; + }, + .Optional => |a| { + assertNoUninitializedPadding(a.child); + return; + }, + .Pointer => |ptr| { + // Pointers aren't allowed, but this just makes the assertion easier to invoke. + assertNoUninitializedPadding(ptr.child); + return; + }, + else => { + return; + }, + }; + // if (info.layout != .Extern) { + // @compileError("assertNoUninitializedPadding(" ++ @typeName(T) ++ ") expects an extern struct type, got a struct of layout '" ++ @tagName(info.layout) ++ "'"); + // } + var i = 0; + for (info.fields) |field| { + const fieldInfo = @typeInfo(field.type); + switch (fieldInfo) { + .Struct => assertNoUninitializedPadding(field.type), + .Union => assertNoUninitializedPadding(field.type), + .Array => |a| assertNoUninitializedPadding(a.child), + .Optional => |a| assertNoUninitializedPadding(a.child), + .Pointer => { + @compileError("Expected no pointer types in " ++ @typeName(T) ++ ", found field '" ++ field.name ++ "' of type '" ++ @typeName(field.type) ++ "'"); + }, + else => {}, + } + } + if (info_ == .Union) { + return; + } + for (info.fields, 0..) |field, j| { + const offset = @offsetOf(T, field.name); + if (offset != i) { + @compileError(std.fmt.comptimePrint( + \\Expected no possibly uninitialized bytes of memory in '{s}', but found a {d} byte gap between fields '{s}' and '{s}' This can be fixed by adding a padding field to the struct like `padding: [{d}]u8 = .{{0}} ** {d},` between these fields. For more information, look at `padding_checker.zig` + , + .{ + @typeName(T), + offset - i, + info.fields[j - 1].name, + field.name, + offset - i, + offset - i, + }, + )); + } + i = offset + @sizeOf(field.type); + } +} |