security: fix TOCTOU and symlink vulnerabilities in pipe-tunnel FIFO

File and Resource Handling: High

The pipe-tunnel module creates FIFOs and then adjusts their
permissions using chmod() on the path. Between mkfifo() and
chmod(), an attacker with write access to the directory (e.g.,
/tmp with the default hardcoded paths) can delete the FIFO and
replace it with a symlink to a target file. The chmod(0666) then
changes permissions on the symlink target, allowing the attacker
to make arbitrary files world-readable/writable.

Fix by:
1. Adding O_NOFOLLOW to the open() call so symlinks are rejected
   at open time rather than followed.
2. Moving the permission change from chmod() (path-based, follows
   symlinks) to fchmod() (fd-based, operates on the already-opened
   and validated file descriptor), and doing it after the fstat
   S_ISFIFO check confirms we opened an actual FIFO.

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
Wim Taymans 2026-04-23 18:06:20 +02:00
parent 56c5eaf317
commit b2bdd65338

View file

@ -618,16 +618,9 @@ static int create_fifo(struct impl *impl)
goto error; goto error;
} }
} else { } else {
/*
* Our umask is 077, so the pipe won't be created with the
* requested permissions. Let's fix the permissions with chmod().
*/
if (chmod(filename, 0666) < 0)
pw_log_warn("chmod('%s'): %s", filename, spa_strerror(-errno));
do_unlink_fifo = true; do_unlink_fifo = true;
} }
if ((fd = open(filename, O_RDWR | O_CLOEXEC | O_NONBLOCK, 0)) < 0) { if ((fd = open(filename, O_RDWR | O_CLOEXEC | O_NONBLOCK | O_NOFOLLOW, 0)) < 0) {
res = -errno; res = -errno;
pw_log_error("open('%s'): %s", filename, spa_strerror(res)); pw_log_error("open('%s'): %s", filename, spa_strerror(res));
goto error; goto error;
@ -644,6 +637,12 @@ static int create_fifo(struct impl *impl)
pw_log_error("'%s' is not a FIFO.", filename); pw_log_error("'%s' is not a FIFO.", filename);
goto error; goto error;
} }
/*
* Our umask is 077, so the pipe won't be created with the
* requested permissions. Use fchmod on the fd to avoid TOCTOU.
*/
if (do_unlink_fifo && fchmod(fd, 0666) < 0)
pw_log_warn("fchmod('%s'): %s", filename, spa_strerror(-errno));
impl->socket = pw_loop_add_io(impl->data_loop, fd, impl->socket = pw_loop_add_io(impl->data_loop, fd,
0, false, on_pipe_io, impl); 0, false, on_pipe_io, impl);
if (impl->socket == NULL) { if (impl->socket == NULL) {