From da8a08fd58324a87f6ac673b7774512e861fe477 Mon Sep 17 00:00:00 2001 From: Justine Tunney Date: Wed, 7 Apr 2021 21:01:57 -0700 Subject: [PATCH] Provide option to have APE not modify itself This change introduces ape-no-modify-self.o to the amalgamated release binaries, which may be used as an alternative to ape.o to make it easier to use APE in cases where the self-modifying behavior isn't acceptable. Please note that this alternative copying behavior isn't necessarily better. It introduces a whole bunch of questions of its own, which are documented in the ape.S source comment and should be considered by both the program author as well as the end-user of programs linked this way. For example, build environments that use read-only file systems and would prefer to not have a launcher wrapper (like we use in our build) can use ape-no-modify-self.o instead of ape.o and then set the $TMPDIR environment variable to point to a sane read-write-exec location. Fixes #146 See #82 --- ape/ape.S | 49 +++++++++++++++++++++++++++++++++++---- ape/ape.mk | 7 +++++- test/libc/release/test.mk | 27 +++++++++++++++++++++ 3 files changed, 77 insertions(+), 6 deletions(-) diff --git a/ape/ape.S b/ape/ape.S index 822ef848d..ad4986203 100644 --- a/ape/ape.S +++ b/ape/ape.S @@ -535,7 +535,37 @@ ape_disk: #if SupportsWindows() || SupportsMetal() || SupportsXnu() apesh: .ascii "'\n#'\"\n" # sixth edition shebang +// Until all operating systems can be updated to support APE, +// we have a beautiful, yet imperfect workaround, which is to +// modify the binary to follow the local system's convention. +// There isn't a one-size-fits-all approach for this, thus we +// present two choices. +#ifndef APE_NO_MODIFY_SELF +// The default behavior is: to overwrite the header in place. +// We prefer this because it's a tiny constant one time cost. +// We simply printf a 64-byte header and call execve() again. .ascii "o=\"$(command -v \"$0\")\"\n" +#else +// The alternative behavior is to copy to $TMPDIR and edit. +// This imposes a variety of caveats of its own that should +// be considered by the user beforehand, such as whether or +// not /tmp is considered trustworthy on a given system, or +// if the administrator chose to mount it with noexec. It's +// up to the user to decide what's best in those situations +// and also note that argv[0] and getauxval(AT_EXECFN) will +// change as a result of this, and lastly note we don't try +// to cleanup the tmp copies for the sake of efficiency. It +// should also be noted that if $0 has directory components +// then permission clashes can happen between system users, +// since only root is able to set the sticky bit, which can +// be addressed simply by overriding the TMPDIR environment + .ascii "o=\"${TMPDIR:-/tmp}/$0\"\n" + .ascii "if [ ! -e \"$o\" ]; then\n" + .ascii "d=\"$o\"\n" + .ascii "o=\"$o.$$\"\n" + .ascii "mkdir -p \"${o%/*}\" 2>/dev/null\n" + .ascii "cp -f \"$0\" \"$o\" || exit 120\n" +#endif /* APE_NO_MODIFY_SELF */ #if SupportsXnu() .ascii "if [ -d /Applications ]; then\n" .ascii "dd if=\"$o\"" @@ -547,7 +577,7 @@ apesh: .ascii "'\n#'\"\n" # sixth edition shebang .shstub ape_macho_dd_count,2 .ascii "\" conv=notrunc 2>/dev/null\n" .ascii "el" -#endif +#endif /* XNU */ .ascii "if exec 7<> \"$o\"; then\n" .ascii "printf '" .ascii "\\177ELF" # 0x0: ⌂ELF @@ -574,19 +604,28 @@ apesh: .ascii "'\n#'\"\n" # sixth edition shebang .ascii "' >&7\n" .ascii "exec 7<&-\n" .ascii "else\n" - .ascii "exit 1\n" + .ascii "exit 121\n" .ascii "fi\n" - .ascii "exec \"$0\" \"$@\"\n" # etxtbsy tail recursion - .ascii "R=$?\n" # architecture optimistic +#ifndef APE_NO_MODIFY_SELF + .ascii "exec \"$0\" \"$@\"\n" # optimistic execution +#else + .ascii "mv -f \"$o\" \"$d\" 2>/dev/null\n" + .ascii "o=\"$d\"\n" + .ascii "fi\n" + .ascii "exec \"$o\" \"$@\"\n" +#endif /* APE_NO_MODIFY_SELF */ + .ascii "R=$?\n" .ascii "\n" .ascii "if [ $R -eq 126 ] && [ \"$(uname -m)\" != x86_64 ]; then\n" .ascii "if Q=\"$(command -v qemu-x86_64)\"; then\n" - .ascii "exec \"$Q\" \"$0\" \"$@\"\n" + .ascii "exec \"$Q\" \"$o\" \"$@\"\n" .ascii "else\n" .ascii "echo error: need qemu-x86_64 >&2\n" .ascii "fi\n" +#ifndef APE_NO_MODIFY_SELF .ascii "elif [ $R -eq 127 ]; then\n" # means argv[0] was wrong .ascii " exec \"$o\" \"$@\"\n" # so do a path resolution +#endif /* APE_NO_MODIFY_SELF */ .ascii "fi\n" .ascii "exit $R\n" .endobj apesh diff --git a/ape/ape.mk b/ape/ape.mk index cc23be31b..40903d127 100644 --- a/ape/ape.mk +++ b/ape/ape.mk @@ -46,5 +46,10 @@ o/ape/idata.inc: \ $(APE_OBJS): $(BUILD_FILES) \ ape/ape.mk +o/$(MODE)/ape/ape-no-modify-self.o: ape/ape.S + @$(COMPILE) -AOBJECTIFY.S $(OBJECTIFY.S) $(OUTPUT_OPTION) -DAPE_NO_MODIFY_SELF $< + .PHONY: o/$(MODE)/ape -o/$(MODE)/ape: $(APE) $(APE_CHECKS) +o/$(MODE)/ape: $(APE) \ + $(APE_CHECKS) \ + o/$(MODE)/ape/ape-no-modify-self.o diff --git a/test/libc/release/test.mk b/test/libc/release/test.mk index 724424691..01aac0224 100644 --- a/test/libc/release/test.mk +++ b/test/libc/release/test.mk @@ -6,6 +6,7 @@ o/$(MODE)/test/libc/release/cosmopolitan.zip: \ o/$(MODE)/ape/ape.lds \ o/$(MODE)/libc/crt/crt.o \ o/$(MODE)/ape/ape.o \ + o/$(MODE)/ape/ape-no-modify-self.o \ o/$(MODE)/cosmopolitan.a @$(COMPILE) -AZIP -T$@ zip -j $@ $^ @@ -37,6 +38,30 @@ o/$(MODE)/test/libc/release/smoke.com.dbg: \ o/$(MODE)/ape/ape.o \ o/$(MODE)/cosmopolitan.a +o/$(MODE)/test/libc/release/smoke-nms.com.dbg: \ + test/libc/release/smoke.c \ + o/cosmopolitan.h \ + o/$(MODE)/ape/ape.lds \ + o/$(MODE)/libc/crt/crt.o \ + o/$(MODE)/ape/ape-no-modify-self.o \ + o/$(MODE)/cosmopolitan.a + @$(COMPILE) -ACC $(CC) \ + -o $@ \ + -Os \ + -static \ + -no-pie \ + -fno-pie \ + -nostdlib \ + -nostdinc \ + -mno-red-zone \ + -fno-omit-frame-pointer \ + -Wl,-T,o/$(MODE)/ape/ape.lds \ + -include o/cosmopolitan.h \ + test/libc/release/smoke.c \ + o/$(MODE)/libc/crt/crt.o \ + o/$(MODE)/ape/ape-no-modify-self.o \ + o/$(MODE)/cosmopolitan.a + o/$(MODE)/test/libc/release/smokecxx.com: \ o/$(MODE)/test/libc/release/smokecxx.com.dbg @$(COMPILE) -AOBJCOPY -T$< $(OBJCOPY) -S -O binary $< $@ @@ -116,6 +141,8 @@ o/$(MODE)/test/libc/release/emulate.ok: \ o/$(MODE)/test/libc/release: \ o/$(MODE)/test/libc/release/smoke.com \ o/$(MODE)/test/libc/release/smoke.com.runs \ + o/$(MODE)/test/libc/release/smoke-nms.com \ + o/$(MODE)/test/libc/release/smoke-nms.com.runs \ o/$(MODE)/test/libc/release/smokecxx.com \ o/$(MODE)/test/libc/release/smokecxx.com.runs \ o/$(MODE)/test/libc/release/smokeansi.com \