${ ...; } can end up closing user fds open within

Stephane Chazelas <[email protected]>
Newsgroups gmane.comp.shells.bash.bugs
Message-ID <xwqiiqm4y4yoswrnfebgrxv33w54llbe5thfcbcledpbsantnk@auq5kxdeu6gg>
Configuration Information [Automatically generated, do not change]:
Machine: x86_64
OS: linux-gnu
Compiler: gcc
Compilation CFLAGS: -g -O2 -Werror=implicit-function-declaration -fstack-protector-strong -fstack-clash-protection -Wformat -Werror=format-security -fcf-protection -Wall
uname output: Linux samsung 6.17.13+deb14-amd64 #1 SMP PREEMPT_DYNAMIC Debian 6.17.13-1 (2025-12-20) x86_64 GNU/Linux
Machine Type: x86_64-pc-linux-gnu

Bash Version: 5.3
Patch Level: 3
Release Status: release

(same in current "devel" git head (commit
c4b56ed9ac00424bde0f9ce3adfb7edb3d19a557)

Description:

$ bash -c 'echo "${ exec 4> /dev/null && echo OK; }"; ls -dog "/proc/$$/fd/4"'
OK
l-wx------ 1 64 Jan 23 09:22 /proc/19083/fd/4 -> /dev/null

fine, but when 4 is the first free fd like after:

$ exec 3>&1
$ bash -c 'echo "${ exec 4> /dev/null && echo OK; }"; ls -dog "/proc/$$/fd/4"'

ls: cannot access '/proc/19106/fd/4': No such file or directory


Under strace, we see (trimmed):

memfd_create("anonopen", MFD_NOEXEC_SEAL) = 4
dup2(1, 255)                            = 255
close(1)                                = 0
dup2(4, 1)                              = 1
openat(AT_FDCWD, "/dev/null", O_WRONLY|O_CREAT|O_TRUNC, 0666) = 5
fcntl(4, F_GETFD)                       = 0
fcntl(4, F_DUPFD, 10)                   = 10
fcntl(4, F_GETFD)                       = 0
fcntl(10, F_SETFD, FD_CLOEXEC)          = 0
dup2(5, 4)                              = 4
close(5)                                = 0
close(10)                               = 0
write(1, "OK\n", 3)                     = 3
lseek(4, 0, SEEK_SET)                   = 0
read(4, 0x7ffcf090bc80, 4096)           = -1 EBADF (Bad file descriptor)
dup2(255, 1)                            = 1
close(255)                              = 0
close(4)                                = 0

We see the memfd open on the first free fd, here < 10, which
means it breaks if that particular fd is used within. fds 0 to 9
should be reserved for the user.

The doc has:

>    Redirections using file descriptors greater than 9 should be used
> with care, as they may conflict with file descriptors the shell uses
> internally.

Implying 0 to 9 should be safe to use.

Here, not only fd 4 as open by the user was closed in the end
causing that error by ls, but command substitution failed as
bash tried to read the output from that fd 4 which it assumed
was still open on the memfd.

That came up at
https://unix.stackexchange.com/questions/803968/double-pass-processing-of-stdin-with-bash/803973#803973
when trying to do:

${ exec 3>&1; }

to get a fd open on an anonymous memfile.

Fix:

use a fd above 9 for the anonfile or the general approach that
bash uses in other contexts to avoid its internal fds clashing
with those manipulated by the user.

-- 
Stephane
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.