commit 6e12e7dcc81f80a405fa5018b7ff572141cc4c61
parent 466964478506739de7084a870c38321c6bd9efee
Author: Conrad Hoffmann <ch@bitfehler.net>
Date: Thu, 15 Jun 2023 21:07:47 +0200
os::exec: only open() regular files
Currently, os::exec::open() will happily return success when e.g.
opening a directory (as long as the user has execute permission on it).
This can become a problem, as os::exec::lookup() uses a successful call
to open() as indicator that it found the right executable.
In the situation where a directory that is also early in PATH contains
e.g. another directory with the same name as some executable, that
directory will be returned instead of the executable. Making open()
check its target more diligently - and hence fail early - solves that.
Simple reproduction example:
mkdir -p test/hare
export PATH="$(pwd)/test/:$PATH"
haredoc -h
Signed-off-by: Conrad Hoffmann <ch@bitfehler.net>
Diffstat:
1 file changed, 18 insertions(+), 2 deletions(-)
diff --git a/os/exec/exec+linux.ha b/os/exec/exec+linux.ha
@@ -69,12 +69,28 @@ fn open(path: str) (platform_cmd | error) = {
};
// O_PATH is used because it allows us to use an executable for which we
// have execute permissions, but not read permissions.
- match (rt::open(path, rt::O_PATH, 0u)) {
+ let fd = match (rt::open(path, rt::O_PATH, 0u)) {
case let fd: int =>
- return fd;
+ yield fd;
case let err: rt::errno =>
return errors::errno(err);
};
+ let success = false;
+ defer if (!success) rt::close(fd)!;
+ // Make sure we are not trying to execute anything weird. fstat()
+ // already dereferences symlinks, so if this is anything other than a
+ // regular file it cannot be executed.
+ let s = rt::st { ... };
+ match (rt::fstat(fd, &s)) {
+ case let err: rt::errno =>
+ return errors::errno(err);
+ case void =>
+ if (s.mode & rt::S_IFREG == 0) {
+ return errors::noaccess;
+ };
+ };
+ success = true;
+ return fd;
};
fn platform_finish(cmd: *command) void = rt::close(cmd.platform)!;