On Fri, Sep 16, 2022 at 07:38:37AM -0700, Kees Cook wrote: > On Fri, Sep 16, 2022 at 02:41:30PM +0100, Josh Triplett wrote: > > Currently, execve allocates an mm and parses argv and envp before > > checking if the path exists. However, the common case of a $PATH search > > may have several failed calls to exec before a single success. Do a > > filename lookup for the purposes of returning ENOENT before doing more > > expensive operations. > > At first I didn't understand how you were seeing this, since I'm so used > to watching shell scripts under tracing, which correctly use stat(): > > $ strace bash -c foo > stat("/home/keescook/bin/foo", 0x7ffe1f9ddea0) = -1 ENOENT (No such file or directory) > stat("/usr/local/sbin/foo", 0x7ffe1f9ddea0) = -1 ENOENT (No such file or directory) > stat("/usr/local/bin/foo", 0x7ffe1f9ddea0) = -1 ENOENT (No such file or directory) > stat("/usr/sbin/foo", 0x7ffe1f9ddea0) = -1 ENOENT (No such file or directory) > stat("/usr/bin/foo", 0x7ffe1f9ddea0) = -1 ENOENT (No such file or directory) > stat("/sbin/foo", 0x7ffe1f9ddea0) = -1 ENOENT (No such file or directory) > stat("/bin/foo", 0x7ffe1f9ddea0) = -1 ENOENT (No such file or directory) > > But I see, yes, glibc tries to actually call execve(), which, as you > say, is extremely heavy: > > $ strace ./execvpe > ... > execve("/home/kees/bin/foo", ["./execvpe"], 0x7ffc542bff38 /* 33 vars */) = -1 ENOENT (No such file or directory) > execve("/usr/local/sbin/foo", ["./execvpe"], 0x7ffc542bff38 /* 33 vars */) = -1 ENOENT (No such file or directory) > execve("/usr/local/bin/foo", ["./execvpe"], 0x7ffc542bff38 /* 33 vars */) = -1 ENOENT (No such file or directory) > execve("/usr/sbin/foo", ["./execvpe"], 0x7ffc542bff38 /* 33 vars */) = -1 ENOENT (No such file or directory) > execve("/usr/bin/foo", ["./execvpe"], 0x7ffc542bff38 /* 33 vars */) = -1 ENOENT (No such file or directory) > execve("/sbin/foo", ["./execvpe"], 0x7ffc542bff38 /* 33 vars */) = -1 ENOENT (No such file or directory) > execve("/bin/foo", ["./execvpe"], 0x7ffc542bff38 /* 33 vars */) = -1 ENOENT (No such file or directory) > > This really seems much more like a glibc bug. The shell does it correctly... musl does the same thing, as do python and perl (likely via execvp or posix_spawnp). As does gcc when it executes `as`. And I've seen more than a few programs hand-implement a PATH search the same way. Seems worth optimizing for. And with io_uring_spawn, it'll be the substantially easier approach, since it'll just require one pass (series of execs) rather than two (stats then exec). - Josh Triplett