Funny the bugs one finds when reading the source code to a program... I was looking at the usage of INPUT_REDIRECT and TRANSLATE_REDIRECT in execute_null_command() and thought “why compare against 8 values, when by re-ordering enum r_instruction in command.h, a simple range check would suffice?” Which led me down a rabbit hole. Why *only* those particular redirections?
When a null command is invoked, its redirections are performed in the current shell and then unwound. Unfortunately this unwinding can be ineffective in some cases. $ cat /tmp/foo #!/bin/bash limit=${1:-100} 255>| bar for (( fd = 10 ; fd <= limit ; ++fd )) do eval "$fd>| bar" done ls -mU /proc/$$/fd ls -log /proc/$$/fd/99 $ bash /tmp/foo 0, 1, 2, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63, 64, 65, 66, 67, 68, 69, 70, 71, 72, 73, 74, 75, 76, 77, 78, 79, 80, 81, 82, 83, 84, 85, 86, 87, 88, 89, 90, 91, 92, 93, 94, 95, 96, 97, 98, 99, 100, 101, 255, lr-x------ 1 64 2025-02-08 11:58:52 +1000 /proc/1723/fd/99 -> /tmp/foo It's important to note that these are empty commands, not ‘exec’, and that all the filedescriptors higher than 10 are open reading the script ‘foo’ (not writing ‘bar’). I know that I've constructed an intentionally pathological case, and I wouldn't be so concerned about it except that if pushed a bit harder it can cause Bash to crash: increase the iteration limit to 1000, and in all my tests it crashes (SIGSEGV or SIGABRT) somewhere between 257 and 530, sometimes preceded by messages along the lines of: > *save_bash_input: buffer already exists for new fd 275* and/or > > *malloc: subst.c:4004: assertion botchedmalloc: block on free list > clobbered* I've tested and found this can crash multiple versions of Bash ranging from 3.0-rc1 to 5.3-alpha, but they vary as to which fd number finally pushes them over the edge. I ran Bash under gdb, and it appears that when saving a reserved filedescriptor (such as the one used to read the script), it gets confused and dups twice, but afterwards only cleans up one. The first redirection 255>|bar causes the original to be duped to both 10 and 11, but only 10 is closed after the command. I was initially experimenting to see whether the kind of redirection would influence whether to set forcefork in execute_null_command(), since that *appeared* to be the case, and that's why I used the “>|” redirection operator. However some of my tests used other redirection operators and it didn't seem any less prone to failure. -Martin