binaryen icon indicating copy to clipboard operation
binaryen copied to clipboard

[wasm-ctor-eval] Any way to zero the stack after calling wasm-ctor-eval?

Open nuchi opened this issue 3 years ago • 8 comments

I'm using wasm-ctor-eval as a great "extended compile-time evaluation" for when constexpr doesn't suffice. I did notice however that as the memory state gets saved after running, that includes the stack. That clutters (a bit) the resulting binary since now the dirty stack is dutifully recorded, even though nothing should ever depend on its exact contents. (i.e. it saves the contents of the stack after it has been returned from; in the case of partial evaluation of course it should keep the contents of whichever part of the stack hasn't been returned from yet.)

Is there any (easy?) way to have it not do that? I can manually zero out individual stack variables before returning (cast them to a volatile char array, iterate over each one) but this gets quickly tiresome.

EDIT: I realized I can do the following to just zero out the entire stack. Good enough for me!

volatile unsigned char* _start = (volatile unsigned char*)&__data_end;
volatile unsigned char* _end = (volatile unsigned char*)&__heap_base;
for (size_t i = 0; _start + i < _end; ++i) {
    _start[i] = 0;
}

nuchi avatar Mar 14 '22 23:03 nuchi

Yes, the edit is basically what I'd recommend. wasm-ctor-eval isn't aware of the stack, really (it's a userspace matter) so you need to zero it out manually. Making a function that does so, and calling it, is the way to go.

kripken avatar Mar 16 '22 14:03 kripken

But code should not be relying on the contents of the stack being zero on startup should it? As far as I know there is no guarantee in the C language that the stack is zero'd when main starts.

For example, the _start function (which calls main) will normally have written to the stack before calling main.. so I don't think it a valid to assume that the stack is zero'd on entry to main.

sbc100 avatar Mar 16 '22 19:03 sbc100

wasm-ctor-eval isn't aware of the stack, really (it's a userspace matter)

Ahh, that makes sense — of course since the stack can go in different places according to preference, there couldn't be a universal solution here.

But code should not be relying on the contents of the stack being zero on startup should it?

No, but my concern is rather binary size. My code doesn't care what's on the stack, but if it's all zero then I don't have to record it in the binary.

nuchi avatar Mar 16 '22 19:03 nuchi

But the code above runs inside the module, once it starts doesn't it? So it doesn't help with module size. (for (size_t i = 0; _start + i < _end; ++i) { thing .. or is that code somehow being run inside binaryen?)

sbc100 avatar Mar 16 '22 19:03 sbc100

My edit reflects the solution I found: I put that code block at the end of the function which I'm going to call with wasm-ctor-eval. Then just as wasm-ctor-eval finishes running my constructor, and just before it saves the new binary, the stack gets zeroed out.

So if I don't include that code block, then the dirty stack gets saved in the binary; if I do include that block, then the dirty stack gets zeroed and hence not saved. Success!

nuchi avatar Mar 16 '22 19:03 nuchi

@sbc100

After ctor-eval evaluates code it applies the results to memory. So it can replace a long sequence of zeros for the stack (which takes 0 binary size almost) with a bunch of "random" data. Logically the contents of the stack don't matter after unwinding, so it's fine to zero that out manually (either in code that wasm-ctor-eval runs, or I guess one could modify the wasm manually).

kripken avatar Mar 16 '22 20:03 kripken

In case it benefits anyone reading in the future, here's an example to illustrate what I mean. clang in both cases below is from the wasi sdk.


With no explicit stack zeroing:

clang example.c -o example.wasm -mexec-model=reactor -O0
wasm-ctor-eval example.wasm -c _initialize,init -o example2.wasm
example.c
int triangular(int n) {
  if (n == 1) {
    return 1;
  }
  return n + triangular(n - 1);
}

int savedTriangular;

void __attribute__((export_name("init"))) init() {
  savedTriangular = triangular(200);
}
wasm-dis example2.wasm
(module
 (memory $0 2)
 (data (i32.const 1024) "\84N")
 (data (i32.const 63384) "\01\00\00\00\01")
 (data (i32.const 63400) "\02\00\00\00\03")
 (data (i32.const 63416) "\03\00\00\00\06")
 (data (i32.const 63432) "\04\00\00\00\n")
 (data (i32.const 63448) "\05\00\00\00\0f")
 (data (i32.const 63464) "\06\00\00\00\15")
 (data (i32.const 63480) "\07\00\00\00\1c")
 (data (i32.const 63496) "\08\00\00\00$")
 (data (i32.const 63512) "\t\00\00\00-")
 (data (i32.const 63528) "\n\00\00\007")
 (data (i32.const 63544) "\0b\00\00\00B")
 (data (i32.const 63560) "\0c\00\00\00N")
 (data (i32.const 63576) "\0d\00\00\00[")
 (data (i32.const 63592) "\0e\00\00\00i")
 (data (i32.const 63608) "\0f\00\00\00x")
 (data (i32.const 63624) "\10\00\00\00\88")
 (data (i32.const 63640) "\11\00\00\00\99")
 (data (i32.const 63656) "\12\00\00\00\ab")
 (data (i32.const 63672) "\13\00\00\00\be")
 (data (i32.const 63688) "\14\00\00\00\d2")
 (data (i32.const 63704) "\15\00\00\00\e7")
 (data (i32.const 63720) "\16\00\00\00\fd")
 (data (i32.const 63736) "\17\00\00\00\14\01")
 (data (i32.const 63752) "\18\00\00\00,\01")
 (data (i32.const 63768) "\19\00\00\00E\01")
 (data (i32.const 63784) "\1a\00\00\00_\01")
 (data (i32.const 63800) "\1b\00\00\00z\01")
 (data (i32.const 63816) "\1c\00\00\00\96\01")
 (data (i32.const 63832) "\1d\00\00\00\b3\01")
 (data (i32.const 63848) "\1e\00\00\00\d1\01")
 (data (i32.const 63864) "\1f\00\00\00\f0\01")
 (data (i32.const 63880) " \00\00\00\10\02")
 (data (i32.const 63896) "!\00\00\001\02")
 (data (i32.const 63912) "\"\00\00\00S\02")
 (data (i32.const 63928) "#\00\00\00v\02")
 (data (i32.const 63944) "$\00\00\00\9a\02")
 (data (i32.const 63960) "%\00\00\00\bf\02")
 (data (i32.const 63976) "&\00\00\00\e5\02")
 (data (i32.const 63992) "\'\00\00\00\0c\03")
 (data (i32.const 64008) "(\00\00\004\03")
 (data (i32.const 64024) ")\00\00\00]\03")
 (data (i32.const 64040) "*\00\00\00\87\03")
 (data (i32.const 64056) "+\00\00\00\b2\03")
 (data (i32.const 64072) ",\00\00\00\de\03")
 (data (i32.const 64088) "-\00\00\00\0b\04")
 (data (i32.const 64104) ".\00\00\009\04")
 (data (i32.const 64120) "/\00\00\00h\04")
 (data (i32.const 64136) "0\00\00\00\98\04")
 (data (i32.const 64152) "1\00\00\00\c9\04")
 (data (i32.const 64168) "2\00\00\00\fb\04")
 (data (i32.const 64184) "3\00\00\00.\05")
 (data (i32.const 64200) "4\00\00\00b\05")
 (data (i32.const 64216) "5\00\00\00\97\05")
 (data (i32.const 64232) "6\00\00\00\cd\05")
 (data (i32.const 64248) "7\00\00\00\04\06")
 (data (i32.const 64264) "8\00\00\00<\06")
 (data (i32.const 64280) "9\00\00\00u\06")
 (data (i32.const 64296) ":\00\00\00\af\06")
 (data (i32.const 64312) ";\00\00\00\ea\06")
 (data (i32.const 64328) "<\00\00\00&\07")
 (data (i32.const 64344) "=\00\00\00c\07")
 (data (i32.const 64360) ">\00\00\00\a1\07")
 (data (i32.const 64376) "?\00\00\00\e0\07")
 (data (i32.const 64392) "@\00\00\00 \08")
 (data (i32.const 64408) "A\00\00\00a\08")
 (data (i32.const 64424) "B\00\00\00\a3\08")
 (data (i32.const 64440) "C\00\00\00\e6\08")
 (data (i32.const 64456) "D\00\00\00*\t")
 (data (i32.const 64472) "E\00\00\00o\t")
 (data (i32.const 64488) "F\00\00\00\b5\t")
 (data (i32.const 64504) "G\00\00\00\fc\t")
 (data (i32.const 64520) "H\00\00\00D\n")
 (data (i32.const 64536) "I\00\00\00\8d\n")
 (data (i32.const 64552) "J\00\00\00\d7\n")
 (data (i32.const 64568) "K\00\00\00\"\0b")
 (data (i32.const 64584) "L\00\00\00n\0b")
 (data (i32.const 64600) "M\00\00\00\bb\0b")
 (data (i32.const 64616) "N\00\00\00\t\0c")
 (data (i32.const 64632) "O\00\00\00X\0c")
 (data (i32.const 64648) "P\00\00\00\a8\0c")
 (data (i32.const 64664) "Q\00\00\00\f9\0c")
 (data (i32.const 64680) "R\00\00\00K\0d")
 (data (i32.const 64696) "S\00\00\00\9e\0d")
 (data (i32.const 64712) "T\00\00\00\f2\0d")
 (data (i32.const 64728) "U\00\00\00G\0e")
 (data (i32.const 64744) "V\00\00\00\9d\0e")
 (data (i32.const 64760) "W\00\00\00\f4\0e")
 (data (i32.const 64776) "X\00\00\00L\0f")
 (data (i32.const 64792) "Y\00\00\00\a5\0f")
 (data (i32.const 64808) "Z\00\00\00\ff\0f")
 (data (i32.const 64824) "[\00\00\00Z\10")
 (data (i32.const 64840) "\\\00\00\00\b6\10")
 (data (i32.const 64856) "]\00\00\00\13\11")
 (data (i32.const 64872) "^\00\00\00q\11")
 (data (i32.const 64888) "_\00\00\00\d0\11")
 (data (i32.const 64904) "`\00\00\000\12")
 (data (i32.const 64920) "a\00\00\00\91\12")
 (data (i32.const 64936) "b\00\00\00\f3\12")
 (data (i32.const 64952) "c\00\00\00V\13")
 (data (i32.const 64968) "d\00\00\00\ba\13")
 (data (i32.const 64984) "e\00\00\00\1f\14")
 (data (i32.const 65000) "f\00\00\00\85\14")
 (data (i32.const 65016) "g\00\00\00\ec\14")
 (data (i32.const 65032) "h\00\00\00T\15")
 (data (i32.const 65048) "i\00\00\00\bd\15")
 (data (i32.const 65064) "j\00\00\00\'\16")
 (data (i32.const 65080) "k\00\00\00\92\16")
 (data (i32.const 65096) "l\00\00\00\fe\16")
 (data (i32.const 65112) "m\00\00\00k\17")
 (data (i32.const 65128) "n\00\00\00\d9\17")
 (data (i32.const 65144) "o\00\00\00H\18")
 (data (i32.const 65160) "p\00\00\00\b8\18")
 (data (i32.const 65176) "q\00\00\00)\19")
 (data (i32.const 65192) "r\00\00\00\9b\19")
 (data (i32.const 65208) "s\00\00\00\0e\1a")
 (data (i32.const 65224) "t\00\00\00\82\1a")
 (data (i32.const 65240) "u\00\00\00\f7\1a")
 (data (i32.const 65256) "v\00\00\00m\1b")
 (data (i32.const 65272) "w\00\00\00\e4\1b")
 (data (i32.const 65288) "x\00\00\00\\\1c")
 (data (i32.const 65304) "y\00\00\00\d5\1c")
 (data (i32.const 65320) "z\00\00\00O\1d")
 (data (i32.const 65336) "{\00\00\00\ca\1d")
 (data (i32.const 65352) "|\00\00\00F\1e")
 (data (i32.const 65368) "}\00\00\00\c3\1e")
 (data (i32.const 65384) "~\00\00\00A\1f")
 (data (i32.const 65400) "\7f\00\00\00\c0\1f")
 (data (i32.const 65416) "\80\00\00\00@ ")
 (data (i32.const 65432) "\81\00\00\00\c1 ")
 (data (i32.const 65448) "\82\00\00\00C!")
 (data (i32.const 65464) "\83\00\00\00\c6!")
 (data (i32.const 65480) "\84\00\00\00J\"")
 (data (i32.const 65496) "\85\00\00\00\cf\"")
 (data (i32.const 65512) "\86\00\00\00U#")
 (data (i32.const 65528) "\87\00\00\00\dc#")
 (data (i32.const 65544) "\88\00\00\00d$")
 (data (i32.const 65560) "\89\00\00\00\ed$")
 (data (i32.const 65576) "\8a\00\00\00w%")
 (data (i32.const 65592) "\8b\00\00\00\02&")
 (data (i32.const 65608) "\8c\00\00\00\8e&")
 (data (i32.const 65624) "\8d\00\00\00\1b\'")
 (data (i32.const 65640) "\8e\00\00\00\a9\'")
 (data (i32.const 65656) "\8f\00\00\008(")
 (data (i32.const 65672) "\90\00\00\00\c8(")
 (data (i32.const 65688) "\91\00\00\00Y)")
 (data (i32.const 65704) "\92\00\00\00\eb)")
 (data (i32.const 65720) "\93\00\00\00~*")
 (data (i32.const 65736) "\94\00\00\00\12+")
 (data (i32.const 65752) "\95\00\00\00\a7+")
 (data (i32.const 65768) "\96\00\00\00=,")
 (data (i32.const 65784) "\97\00\00\00\d4,")
 (data (i32.const 65800) "\98\00\00\00l-")
 (data (i32.const 65816) "\99\00\00\00\05.")
 (data (i32.const 65832) "\9a\00\00\00\9f.")
 (data (i32.const 65848) "\9b\00\00\00:/")
 (data (i32.const 65864) "\9c\00\00\00\d6/")
 (data (i32.const 65880) "\9d\00\00\00s0")
 (data (i32.const 65896) "\9e\00\00\00\111")
 (data (i32.const 65912) "\9f\00\00\00\b01")
 (data (i32.const 65928) "\a0\00\00\00P2")
 (data (i32.const 65944) "\a1\00\00\00\f12")
 (data (i32.const 65960) "\a2\00\00\00\933")
 (data (i32.const 65976) "\a3\00\00\0064")
 (data (i32.const 65992) "\a4\00\00\00\da4")
 (data (i32.const 66008) "\a5\00\00\00\7f5")
 (data (i32.const 66024) "\a6\00\00\00%6")
 (data (i32.const 66040) "\a7\00\00\00\cc6")
 (data (i32.const 66056) "\a8\00\00\00t7")
 (data (i32.const 66072) "\a9\00\00\00\1d8")
 (data (i32.const 66088) "\aa\00\00\00\c78")
 (data (i32.const 66104) "\ab\00\00\00r9")
 (data (i32.const 66120) "\ac\00\00\00\1e:")
 (data (i32.const 66136) "\ad\00\00\00\cb:")
 (data (i32.const 66152) "\ae\00\00\00y;")
 (data (i32.const 66168) "\af\00\00\00(<")
 (data (i32.const 66184) "\b0\00\00\00\d8<")
 (data (i32.const 66200) "\b1\00\00\00\89=")
 (data (i32.const 66216) "\b2\00\00\00;>")
 (data (i32.const 66232) "\b3\00\00\00\ee>")
 (data (i32.const 66248) "\b4\00\00\00\a2?")
 (data (i32.const 66264) "\b5\00\00\00W@")
 (data (i32.const 66280) "\b6\00\00\00\0dA")
 (data (i32.const 66296) "\b7\00\00\00\c4A")
 (data (i32.const 66312) "\b8\00\00\00|B")
 (data (i32.const 66328) "\b9\00\00\005C")
 (data (i32.const 66344) "\ba\00\00\00\efC")
 (data (i32.const 66360) "\bb\00\00\00\aaD")
 (data (i32.const 66376) "\bc\00\00\00fE")
 (data (i32.const 66392) "\bd\00\00\00#F")
 (data (i32.const 66408) "\be\00\00\00\e1F")
 (data (i32.const 66424) "\bf\00\00\00\a0G")
 (data (i32.const 66440) "\c0\00\00\00`H")
 (data (i32.const 66456) "\c1\00\00\00!I")
 (data (i32.const 66472) "\c2\00\00\00\e3I")
 (data (i32.const 66488) "\c3\00\00\00\a6J")
 (data (i32.const 66504) "\c4\00\00\00jK")
 (data (i32.const 66520) "\c5\00\00\00/L")
 (data (i32.const 66536) "\c6\00\00\00\f5L")
 (data (i32.const 66552) "\c7\00\00\00\bcM")
 (data (i32.const 66568) "\c8\00\00\00\84N")
 (export "memory" (memory $0))
 ;; custom section "producers", size 108
)

And this time with the stack-zeroing trick:

clang example-with-stack-zero.c -o example-with-stack-zero.wasm -mexec-model=reactor -O0
wasm-ctor-eval example-with-stack-zero.wasm -c _initialize,init -o example-with-stack-zero-2.wasm
example-with-stack-zero.c
int triangular(int n) {
  if (n == 1) {
    return 1;
  }
  return n + triangular(n - 1);
}

int savedTriangular;

extern unsigned char __heap_base;
extern unsigned char __data_end;

void __attribute__((export_name("init"))) init() {
  savedTriangular = triangular(200);

  volatile unsigned char* _end = (volatile unsigned char*)&__heap_base;
  volatile unsigned char* _start = (volatile unsigned char*)&__data_end;
  // + 12: don't overwrite this frame!
  for (int i = 0; _start + i + 12 < _end; ++i) {
    _start[i] = 0;
  }
}
wasm-dis example-with-stack-zero-2.wasm
(module
 (memory $0 2)
 (data (i32.const 1024) "\84N")
 (data (i32.const 66566) "\01\00\04\04\00\00\10\04\01")
 (export "memory" (memory $0))
 ;; custom section "producers", size 108
)

nuchi avatar Mar 16 '22 22:03 nuchi