https://bugs.freebsd.org/bugzilla/show_bug.cgi?id=297394
--- Comment #18 from Jeremy Chadwick <[email protected]> --- (In reply to Benjamin Jacobs from comment #17) Contents are below, including full deep dive. But again, /root/.shrc and /root/.profile are whatever FreeBSD 15.1 put in place. No modifications. These files have never been modified by me and 15.1 upgrade overwrote them anyway. It takes a few minutes to launch an EC2 or GCP VM running 15.1 to reproduce this problem, and that doesn't need to involve me. $ cat /root/.shrc # # .shrc - bourne shell startup file # # This file will be used if the shell is invoked for interactive use and # the environment variable ENV is set to this file. # # see also sh(1), environ(7). # # file permissions: rwxr-xr-x # # umask 022 # Uncomment this to enable the builtin vi(1) command line editor in sh(1), # e.g. ESC to go into visual mode. # set -o vi # # some useful aliases # alias h='fc -l' # alias j=jobs # alias m="$PAGER" # alias ll='ls -laFo' # alias l='ls -l' # alias g='egrep -i' # # be paranoid # alias cp='cp -ip' # alias mv='mv -i' # alias rm='rm -i' # # csh like history on arrow up and down bind ^[[A ed-search-prev-history bind ^[[B ed-search-next-history # # Ctrl+right arrow: go to the next word # # Ctrl+left arrow: go to the previous word bind "\\e[1;5C" em-next-word bind "\\e[1;5D" ed-prev-word alias history='fc -l' # Fix home/del for mobaxterm bind ^[[5~ ed-move-to-beg bind ^[[6~ ed-move-to-end # read(2) of directories may not be desirable by default, as this will provoke # EISDIR errors from each directory encountered. # alias grep='grep -d skip' # set prompt: ``username@hostname:directory $ '' PS1="\u@\h:\w \\$ " # search path for cd(1) # CDPATH=:$HOME Next: Linux distros do not all use bash for /bin/sh. If you think that, you need to use more Linux :-) Debian/Ubuntu for example use dash, which is identical to FreeBSD sh, as their /bin/sh. dash also honours $ENV in whatever way it does (man page is the same!). But this problem does not happen on those distros, because they do not set $ENV anywhere. Only FreeBSD does this. I cover Linux later on though, where their stuff works fine for multiple reasons (albeit poor Linux mindsets/habits, sigh). Next: please read the below slowly, as in this PR I've already seen people "gloss over" info because I'm verbose. If you aren't familiar with \command then I'm doing it here to ensure there are no aliases/etc. used, just to rule out someone barking up *that* tree. $ sudo chsh -s /bin/sh chsh: user information updated $ \sudo su - root@mambo:~ # rm -f junk root@mambo:~ # truss -e -f -o junk sa-compile Can't exec "re2c": No such file or directory at /usr/local/bin/sa-compile line 114. root@mambo:~ # egrep 're2c|env' junk 43742: read(4," environment variable will be se"...,8192) = 8192 (0x2000) 43743: execve("/bin/re2c",0x25868f2b7ee0,[ "PAGER=less", "LANG=C.UTF-8", "MAIL=/var/mail/root", "PATH=/bin:/usr/bin", "PWD=/root", "TERM=xterm", "USER=root", "HOME=/root", "SHELL=/bin/sh", "MM_CHARSET=UTF-8", "BLOCKSIZE=K" ]) ERR#2 'No such file or directory' 43743: execve("/usr/bin/re2c",0x25868f2b7ee0,[ "PAGER=less", "LANG=C.UTF-8", "MAIL=/var/mail/root", "PATH=/bin:/usr/bin", "PWD=/root", "TERM=xterm", "USER=root", "HOME=/root", "SHELL=/bin/sh", "MM_CHARSET=UTF-8", "BLOCKSIZE=K" ]) ERR#2 'No such file or directory' 43743: write(2,"Can't exec "re2c": No such file "...,84) = 84 (0x54) Look closely at what $PATH is in execve() calls. This isn't doing /bin/sh -c "re2c" either, as my grep would have caught that. Now let's unset ENV (like with csh environment) and see what happens: root@mambo:~ # unset ENV root@mambo:~ # rm -f junk root@mambo:~ # truss -e -f -o junk sa-compile Aug 11 10:51:15.933 [43766] info: generic: base extraction starting. this can take a while... Aug 11 10:51:15.934 [43766] info: generic: extracting from rules of type body_neg2000 100% [=========================================================================================================================================================================] 29.02 rules/sec 0m 0s DONE 0% [ ^C root@mambo:~ # egrep 're2c|env' junk 43766: read(4," environment variable will be se"...,8192) = 8192 (0x2000) 43768: execve("/sbin/re2c",0x2d956c9ce740,[ "PAGER=less", "LANG=C.UTF-8", "MAIL=/var/mail/root", "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin", "PWD=/root", "TERM=xterm", "USER=root", "HOME=/root", "SHELL=/bin/sh", "MM_CHARSET=UTF-8", "BLOCKSIZE=K" ]) ERR#2 'No such file or directory' 43768: execve("/bin/re2c",0x2d956c9ce740,[ "PAGER=less", "LANG=C.UTF-8", "MAIL=/var/mail/root", "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin", "PWD=/root", "TERM=xterm", "USER=root", "HOME=/root", "SHELL=/bin/sh", "MM_CHARSET=UTF-8", "BLOCKSIZE=K" ]) ERR#2 'No such file or directory' 43768: execve("/usr/sbin/re2c",0x2d956c9ce740,[ "PAGER=less", "LANG=C.UTF-8", "MAIL=/var/mail/root", "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin", "PWD=/root", "TERM=xterm", "USER=root", "HOME=/root", "SHELL=/bin/sh", "MM_CHARSET=UTF-8", "BLOCKSIZE=K" ]) ERR#2 'No such file or directory' 43768: execve("/usr/bin/re2c",0x2d956c9ce740,[ "PAGER=less", "LANG=C.UTF-8", "MAIL=/var/mail/root", "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin", "PWD=/root", "TERM=xterm", "USER=root", "HOME=/root", "SHELL=/bin/sh", "MM_CHARSET=UTF-8", "BLOCKSIZE=K" ]) ERR#2 'No such file or directory' 43768: execve("/usr/local/sbin/re2c",0x2d956c9ce740,[ "PAGER=less", "LANG=C.UTF-8", "MAIL=/var/mail/root", "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin", "PWD=/root", "TERM=xterm", "USER=root", "HOME=/root", "SHELL=/bin/sh", "MM_CHARSET=UTF-8", "BLOCKSIZE=K" ]) ERR#2 'No such file or directory' 43768: execve("/usr/local/bin/re2c",0x2d956c9ce740,[ "PAGER=less", "LANG=C.UTF-8", "MAIL=/var/mail/root", "PATH=/sbin:/bin:/usr/sbin:/usr/bin:/usr/local/sbin:/usr/local/bin", "PWD=/root", "TERM=xterm", "USER=root", "HOME=/root", "SHELL=/bin/sh", "MM_CHARSET=UTF-8", "BLOCKSIZE=K" ]) EJUSTRETURN So we can tell within sa-compile, $PATH changes conditionally. With $ENV set, $PATH gets set to /bin:/usr/bin. But is this Perl's taint mode or SpamAssassin? Let's test Perl's taint mode for behaviour using -T. Again, this is Perl itself, not sa-compile. We should get taint warnings by default: root@mambo:~ # exit $ \sudo su - root@mambo:~ # truss -e -f -o junk perl -Te 'print qx("re2c");' Insecure $ENV{PATH} while running with -T switch at -e line 1. Expected. Let's untaint PATH (dirty way but we're just testing Perl): root@mambo:~ # truss -e -f -o junk perl -Te 'delete @ENV{"PATH"}; print qx("re2c");' Insecure $ENV{ENV} while running with -T switch at -e line 1. This is a new one to me, but is documented in Perl's perlsec man page: https://perldoc.perl.org/perlsec#Cleaning-Up-Your-Path . So let's untaint ENV: root@mambo:~ # truss -e -f -o junk perl -Te 'delete @ENV{"PATH"}; delete @ENV{"ENV"}; print qx("re2c");' re2c: error: no source file OK, we see /usr/local/bin being honoured here, so Perl itself seems to be honouring what's in the shell $PATH (/usr/local/bin being present). Now let's unset ENV and see what happens. root@mambo:~ # unset ENV root@mambo:~ # truss -e -f -o junk perl -Te 'print qx("re2c");' Insecure $ENV{PATH} while running with -T switch at -e line 1. Again, expected. Let's untaint PATH: root@mambo:~ # truss -e -f -o junk perl -Te 'delete @ENV{"PATH"}; print qx("re2c");' re2c: error: no source file So yes, SpamAssassin is doing something with $PATH when $ENV is set, prior to any execve() calls. What about Linux? All I have on hand is Ubuntu/Debian right now. Rather than do the same analysis, we can tell one difference right off the bat: Linux chooses to install sa-compile in /usr/bin, therefore whatever SpamAssassin's hard-coded $PATH defaults would likely still work -- even if $ENV was set. It also hard-links to /bin/sa-compile, for whatever god-awful reason: root@linux:~# dpkg-query -L sa-compile ... /usr/bin /usr/bin/sa-compile ... root@linux:~# ls -li /usr/bin/sa-compile /bin/sa-compile 15102207 -rwxr-xr-x 1 root root 22375 Mar 23 2023 /bin/sa-compile 15102207 -rwxr-xr-x 1 root root 22375 Mar 23 2023 /usr/bin/sa-compile Before we get to "What have we learned?", I will remind readers: there are TWO PROBLEMS to solve: one with sa-update and one with sa-compile. The problems manifest differently, but both stem from $ENV being set. So what have we learned? 1. sa-update does not like $ENV being set: period. It does not untaint $ENV. Probably a bug/quirk in SpamAssassin. 2. sa-compile, when $ENV is set, causes $PATH to change to /bin:/usr/bin. We can tell that Perl taint mode IS NOT doing this because qx() (and probably system()) with a binary in /usr/local/bin actually is found, rather than ENOENT. We can additionally tell because execve() $PATH is seen to change. Untainting these variables is of course required, and which approach you take for untainting is your choice. SpamAssassin has apparently chosen a poor choice, probably because I doubt anyone on the SA team runs FreeBSD. Ports, as we all know, defaults to /usr/local base. So this has been a problem lurking on FreeBSD for a very long time (whenever SA started doing this). 3. This problem doesn't happen on Linux (Debian/Ubuntu) because 1) dash doesn't set $ENV like FreeBSD, but even if it did, 2) the sa-compile binary gets installed in /usr/bin and a hardlink in /bin, so it's going to work with whatever SpamAssassin's code is. Further proof SA team probably only uses Linux. Workarounds: WA1) Patch FreeBSD spamassassin port. Needs two changes: a. sa-update modification: fix $ENV tainting -- consider upstream patch to SA as well b. sa-compile modification: find where $PATH is changed to hard-coded value and add $PORTSBASE (or whatever it's called) One could also hand-modify the software on a system to fix this. Be sure to pkg lock spamassassin after though, otherwise next change will wipe out your hacks. WA2) Modify /root/.profile and comment/delete line that sets ENV. Prompt etc. will change as a result, as /root/.shrc will no longer be read. You could also "just remember to unset ENV" every time, which is ridiculous IMO. FreeBSD felt 29 years ago setting this env var and pointing it to /root/.shrc was "the right thing" due to this /bin/sh feature. WA3) Change root's shell to /bin/csh. csh dotfiles do not set $ENV, and csh does not care about $ENV, thus immune all of the above. Quickest/easiest solution and guarantees no further surprises with other software (I suspect SpamAssassin probably isn't the only one). No AI was used in the course of this analysis, just human time -- 2 hours -- of a guy who isn't responsible for any of this software. I've done as much as I'm willing to do on this PR/bug. Move it to ports category, close it for CoC violations, whatever. -- You are receiving this mail because: You are the assignee for the bug.
