authorgravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2024-11-30 10:20:20+01:00
committergravatar for alex@alexrp.comAlex Rønne Petersen <alex@alexrp.com> 2025-01-22 03:01:02+01:00
loge800ea0fdd4ecbe2e28867550a8c36715f3c5ea9
treecc70cb1dc7b483e7fb49510c520452fb49bda8ee
parent97b97ae6202811e207196920018c3b9610957f90
signaturebadge-check Signed by SSH key SHA256:7B/LJ7bpR1eX8aCXSr4mtd5M45VMPKcx9zY8e95b5QM

wasm: Add a check for zero length around uses of memory.copy/memory.fill.

Apparently the WebAssembly spec requires these instructions to trap if the computed memory access could be out of bounds, even if the length is zero. Really a rather bizarre design choice.

1 files changed, 35 insertions(+), 0 deletions(-)

src/arch/wasm/CodeGen.zig+35
...@@ -1591,10 +1591,28 @@ fn memcpy(cg: *CodeGen, dst: WValue, src: WValue, len: WValue) !void {...@@ -1591,10 +1591,28 @@ fn memcpy(cg: *CodeGen, dst: WValue, src: WValue, len: WValue) !void {
1591 // When bulk_memory is enabled, we lower it to wasm's memcpy instruction.1591 // When bulk_memory is enabled, we lower it to wasm's memcpy instruction.
1592 // If not, we lower it ourselves manually1592 // If not, we lower it ourselves manually
1593 if (std.Target.wasm.featureSetHas(cg.target.cpu.features, .bulk_memory)) {1593 if (std.Target.wasm.featureSetHas(cg.target.cpu.features, .bulk_memory)) {
1594 try cg.startBlock(.block, .empty);
1595
1596 // Even if `len` is zero, the spec requires an implementation to trap if `src + len` or
1597 // `dst + len` are out of memory bounds. This can easily happen in Zig in a case such as:
1598 //
1599 // const dst: [*]u8 = undefined;
1600 // const src: [*]u8 = undefined;
1601 // var len: usize = runtime_zero();
1602 // @memcpy(dst[0..len], src[0..len]);
1603 //
1604 // So explicitly avoid using `memory.copy` in the `len == 0` case. Lovely design.
1605 try cg.emitWValue(len);
1606 try cg.addTag(.i32_eqz);
1607 try cg.addLabel(.br_if, 0);
1608
1594 try cg.lowerToStack(dst);1609 try cg.lowerToStack(dst);
1595 try cg.lowerToStack(src);1610 try cg.lowerToStack(src);
1596 try cg.emitWValue(len);1611 try cg.emitWValue(len);
1597 try cg.addExtended(.memory_copy);1612 try cg.addExtended(.memory_copy);
1613
1614 try cg.endBlock();
1615
1598 return;1616 return;
1599 }1617 }
16001618
...@@ -4782,10 +4800,27 @@ fn memset(cg: *CodeGen, elem_ty: Type, ptr: WValue, len: WValue, value: WValue)...@@ -4782,10 +4800,27 @@ fn memset(cg: *CodeGen, elem_ty: Type, ptr: WValue, len: WValue, value: WValue)
4782 // When bulk_memory is enabled, we lower it to wasm's memset instruction.4800 // When bulk_memory is enabled, we lower it to wasm's memset instruction.
4783 // If not, we lower it ourselves.4801 // If not, we lower it ourselves.
4784 if (std.Target.wasm.featureSetHas(cg.target.cpu.features, .bulk_memory) and abi_size == 1) {4802 if (std.Target.wasm.featureSetHas(cg.target.cpu.features, .bulk_memory) and abi_size == 1) {
4803 try cg.startBlock(.block, .empty);
4804
4805 // Even if `len` is zero, the spec requires an implementation to trap if `ptr + len` is
4806 // out of memory bounds. This can easily happen in Zig in a case such as:
4807 //
4808 // const ptr: [*]u8 = undefined;
4809 // var len: usize = runtime_zero();
4810 // @memset(ptr[0..len], 42);
4811 //
4812 // So explicitly avoid using `memory.fill` in the `len == 0` case. Lovely design.
4813 try cg.emitWValue(len);
4814 try cg.addTag(.i32_eqz);
4815 try cg.addLabel(.br_if, 0);
4816
4785 try cg.lowerToStack(ptr);4817 try cg.lowerToStack(ptr);
4786 try cg.emitWValue(value);4818 try cg.emitWValue(value);
4787 try cg.emitWValue(len);4819 try cg.emitWValue(len);
4788 try cg.addExtended(.memory_fill);4820 try cg.addExtended(.memory_fill);
4821
4822 try cg.endBlock();
4823
4789 return;4824 return;
4790 }4825 }
47914826