Page MenuHomeFreeBSD

sh(1): initialize smark to zero in main()
ClosedPublic

Authored by bapt on Jun 3 2023, 8:19 PM.
Tags
None
Referenced Files
Unknown Object (File)
Sun, Oct 6, 6:49 AM
Unknown Object (File)
Sun, Oct 6, 3:45 AM
Unknown Object (File)
Thu, Oct 3, 6:04 AM
Unknown Object (File)
Wed, Sep 25, 6:37 PM
Unknown Object (File)
Mon, Sep 9, 3:28 AM
Unknown Object (File)
Sun, Sep 8, 2:26 PM
Unknown Object (File)
Sun, Sep 8, 9:01 AM
Unknown Object (File)
Sun, Sep 8, 8:28 AM

Details

Summary

As popstackmark may be called on this without pushstackmark having
been called, we need to initialize it so that we don't get a bogus
comparison inside popstackmark, which would have resulted in a
NULL pointer dereference.

Diff Detail

Repository
rG FreeBSD src repository
Lint
Lint Not Applicable
Unit
Tests Not Applicable

Event Timeline

bapt requested review of this revision.Jun 3 2023, 8:19 PM
This revision is now accepted and ready to land.Jun 3 2023, 8:49 PM

To me, it looks like that situation can't actually happen. If there is an error or SIGINT during early initialization, state will still be 0 and the if (setjmp(main_handler.loc)) block will immediately call exitshell(exitstatus) without touching smark. If I'm wrong, please provide a reproduction scenario.

We might still want this change to make static analyzers happier.

the issue was caught by the author of chimera linux when compiling it with lto and clang16

<@q66> bapt: anyway, to reproduce: i added this to memalloc.c https://gist.github.com/q66/978ca271db9ea32c8641209eb90f2f85
<@q66> then all you have to do is exec the sh binary, and press ctrl-c
<@q66> in plain prompt
<@q66> https://gist.github.com/q66/99295f340eeaffae740d53e0cc603ad0
<@q66> actually, printf("POPSTACKMARK %p %p\n", mark->stackp, stackp); is more explanatory
<@q66> you will see a POPSTACKMARK 0 0 upon ctrl-c
<@q66> e.g. like https://gist.github.com/q66/61b70ecabb510bbdf86a11e330f89893
<@q66> if i compile without the zeroing
<@q66> i will get a bogus address
<@q66> in mark->stackp

This revision was automatically updated to reflect the committed changes.

@jilles I looked into this more and it seems that this is actually a miscompilation, possibly caused by the LLVM optimizer getting confused because of the long jumps.

If I LTO-compile the shell (with -O2) with LLVM 16 as is, I get a crash when i spawn it and ctrl-c. If I add printfs in setstackmark/popstackmark that don't touch any of the addresses, I can see that setstackmark is being called for smark as the first thing (so it should in fact be initialized) but then I still get a crash because in popstackmark, reading the mark->stackp gets me a bogus address (like 0x30103). However, if I add a printf of the passed mark's address in the set/popstackmark, this stops the crashes.

Initializing smark in main() to zero will apparently prevent that assumption in the optimizer, which results in a working binary. What likewise prevents the crash is calling setstackmark for smark in main() before the if (setjmp(...)), as I guess that also breaks the assumption, as LLVM can no longer assume that popstackmark may be called on a dirty struct.

I can reproduce this on multiple architectures, so it does not appear to be codegen-related.

and indeed, further testing reveals that the issue does not get fixed by this change (nor by moving setstackmark), only hidden; any changes to smark contents are *not* visible after the jump, which in practice means we are most likely leaking memory, or worse

This patch actually works around the problem:

From f1ddc1f587e89bab41263ad9b71207da8e16b6ff Mon Sep 17 00:00:00 2001
From: q66 <q66@chimera-linux.org>
Date: Sun, 4 Jun 2023 00:21:55 +0200
Subject: [PATCH] sh(1): make smark/smark2 static

This prevents the compiler from not considering changes done to
the members from within the setjmp conditional.
---
 src.freebsd/sh/main.c | 3 ++-
 1 file changed, 2 insertions(+), 1 deletion(-)

diff --git a/src.freebsd/sh/main.c b/src.freebsd/sh/main.c
index 4d1a0e5..955b2bb 100644
--- a/src.freebsd/sh/main.c
+++ b/src.freebsd/sh/main.c
@@ -97,10 +97,11 @@ static char *find_dot_file(char *);
  * is used to figure out how far we had gotten.
  */
 
+static struct stackmark smark, smark2;
+
 int
 main(int argc, char *argv[])
 {
-	struct stackmark smark = {0}, smark2;
 	volatile int state;
 	char *shinit;
 
-- 
2.41.0

Now that I am thinking about it, this may not be a compiler issue - the C standard specifies that the value of non-volatile local variables modified between the setjmp call and the longjmp is unspecified. So making these static is actually the right way to go.