att/ast

DEBUG trap is not reset when running a script without a shebang

petrpavlu opened this issue · 0 comments

Description of problem:

When a DEBUG trap is registered in a parent shell and then a child shell is invoked to run a script without a shebang, the DEBUG trap is not reset in the forked shell.

My understanding is that when a subshell is entered, traps should be set to the default actions (POSIX 2004 reference).

Ksh version:

  • sh (AT&T Research) 2020.0.0
  • Current Git revision: 031d73a.

How reproducible:

Always.

Steps to reproduce:

$ echo "echo test" > test.sh 
$ chmod +x test.sh
$ function log_debug { echo "DEBUG ${.sh.command}"; }
$ trap log_debug DEBUG
$ ./test.sh
DEBUG ./test.sh
./test.sh: line 1: log_debug: not found
test

Note: When invoking a script with a proper shebang, the DEBUG trap is reset:

$ cat <<EOF > test2.sh
> #!/bin/ksh
> echo test
> EOF
$ chmod +x test2.sh
$ function log_debug { echo "DEBUG ${.sh.command}"; }
$ trap log_debug DEBUG
$ ./test2.sh
DEBUG ./test2.sh
test

Actual results:

DEBUG trap remains set in a child shell.

Expected results:

DEBUG trap is not set in a child shell.

Additional info:

My understanding is that the following happens when running the above mentioned example test.sh:

  • Function path_spawn() (src/cmd/ksh93/sh/path.c) is executed to spawn ./test.sh.

  • It ends up running the execve("./test.sh", ...) syscall which returns ENOEXEC because of the missing shebang.

  • Function path_spawn() recognizes this failure and instead forks the running ksh process to execute the script: path_spawn() -> exscript().

  • Function exscript() ends up passing control via a series of long jumps to sh_main() (src/cmd/ksh93/sh/main.c) which calls sh_reinit() (src/cmd/ksh93/sh/init.c).

  • Function sh_reinit() re-initializes the shell to a clean state. It removes the user-defined function log_debug() and clears also some knowledge of trap handlers, in particular it calls sh_sigreset().

  • Function sh_sigreset() contains the following code:

    for(sig=SH_DEBUGTRAP-1;sig>=0;sig--)
    {
            if(trap=sh.st.trap[sig])
            {
                    if(mode)
                            free(trap);
                    sh.st.trap[sig] = 0;
            }
    
    }
    

    The loop starts from SH_DEBUGTRAP-1 and so the DEBUG trap is not reset.

  • The script invokes command echo test which triggers the debug handler and ksh reports that log_debug() is not available.

Naively, my initial impression was that the above loop should start from SH_DEBUGTRAP and not SH_DEBUGTRAP-1 because the trap array is declared in src/cmd/ksh93/include/defs.h as char *trap[SH_DEBUGTRAP+1]. However, a colleague pointed out to me that this might not be a simple off-by-one error because the code was consciously changed from SH_DEBUGTRAP to SH_DEBUGTRAP-1 in ksh93t. Unfortunately, it is not clear to me from its changelog why this was done.