Perl / perl5

🐪 The Perl programming language
https://dev.perl.org/perl5/
Other
1.85k stars 524 forks source link

$? == -1 after qx returns defined scalar #22127

Closed ikegami closed 3 weeks ago

ikegami commented 3 weeks ago

Perl 5.36.0. Linux. A use of qx in scalar context is returning a defined value, sets $? to -1, and sets $! to 0.


qx, like system, sets $?.

A positive value indicates the child ran, and it was killed by a signal or it exited with a non-zero exit code.

A zero value indicates indicates the child ran, it wasn't killed by a signal, and it exited with exit code zero.

A negative value indicates the child couldn't be executed. The error could be the execution of the child itself, or some other system call performed in preparation (e.g. fork).

This means it's impossible for $? to be -1 when qx returns a defined value. ...Except that is what I am experiencing. The child executed, qx returned its output, but $? is set to -1. Worse, $! should be set to an error code when $? is -1, but it's 0 (no error).

While 100% reproducible, I can't replicate this using a one-liner. The following is nested in a larger program, which I haven't been able to reduce to a runnable demonstration.

my $out = qx{$cmd 2>&1};
if ( !defined( $out ) ) {
  my $e = $? == -1 ? "\$?=-1:\$!=".( 0+$! )."=$!" : "\$?=$?";
  die join "\n\t", "Failed: '$cmd' ($e)" => $out;
}

die "**************** \$?=-1:\$!=".( 0+$! )."=$! ****************\n" if $? == -1;

Output:

**************** $?=-1:$!=0= ****************

The odd thing is that this block is executed twice, and it only dies on the second pass.

The command is very simple: /bin/tiffdump foo 2>&1 where foo is a file name with no quotes or other special characters. This would qualify it for the shell-bypass optimization.

I did not find any local $?, and I confirmed the magic is still present on $?.

Summary of my perl5 (revision 5 version 36 subversion 0) configuration:

  Platform:
    osname=linux
    osvers=3.10.0-1160.76.1.el7.x86_64
    archname=x86_64-linux
    uname='linux XXX.YYY.net 3.10.0-1160.76.1.el7.x86_64 #1 smp tue jul 26 14:15:37 utc 2022 x86_64 x86_64 x86_64 gnulinux '
    config_args='-des -Dprefix=/opt/freeware'
    hint=recommended
    useposix=true
    d_sigaction=define
    useithreads=undef
    usemultiplicity=undef
    use64bitint=define
    use64bitall=define
    uselongdouble=undef
    usemymalloc=n
    default_inc_excludes_dot=define
  Compiler:
    cc='cc'
    ccflags ='-std=gnu99 -fwrapv -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include -D_LARGEFILE_SOURCE -D_FILE_OFFSET_BITS=64 -D_FORTIFY_SOURCE=2'
    optimize='-O2'
    cppflags='-std=gnu99 -fwrapv -fno-strict-aliasing -pipe -fstack-protector-strong -I/usr/local/include'
    ccversion=''
    gccversion='4.8.5 20150623 (Red Hat 4.8.5-44)'
    gccosandvers=''
    intsize=4
    longsize=8
    ptrsize=8
    doublesize=8
    byteorder=12345678
    doublekind=3
    d_longlong=define
    longlongsize=8
    d_longdbl=define
    longdblsize=16
    longdblkind=3
    ivtype='long'
    ivsize=8
    nvtype='double'
    nvsize=8
    Off_t='off_t'
    lseeksize=8
    alignbytes=8
    prototype=define
  Linker and Libraries:
    ld='cc'
    ldflags =' -fstack-protector-strong -L/usr/local/lib'
    libpth=/usr/local/lib /usr/lib /usr/lib64 /usr/local/lib64
    libs=-lpthread -lnsl -ldl -lm -lcrypt -lutil -lc
    perllibs=-lpthread -lnsl -ldl -lm -lcrypt -lutil -lc
    libc=libc-2.17.so
    so=so
    useshrplib=false
    libperl=libperl.a
    gnulibc_version='2.17'
  Dynamic Linking:
    dlsrc=dl_dlopen.xs
    dlext=so
    d_dlsymun=undef
    ccdlflags='-Wl,-E'
    cccdlflags='-fPIC'
    lddlflags='-shared -O2 -L/usr/local/lib -fstack-protector-strong'

Characteristics of this binary (from libperl):
  Compile-time options:
    HAS_TIMES
    PERLIO_LAYERS
    PERL_COPY_ON_WRITE
    PERL_DONT_CREATE_GVSV
    PERL_MALLOC_WRAP
    PERL_OP_PARENT
    PERL_PRESERVE_IVUV
    USE_64_BIT_ALL
    USE_64_BIT_INT
    USE_LARGE_FILES
    USE_LOCALE
    USE_LOCALE_COLLATE
    USE_LOCALE_CTYPE
    USE_LOCALE_NUMERIC
    USE_LOCALE_TIME
    USE_PERLIO
    USE_PERL_ATOF
  Built under linux
  Compiled at Aug 30 2022 11:57:48
  @INC:
    /opt/freeware/lib/perl5/site_perl/5.36.0/x86_64-linux
    /opt/freeware/lib/perl5/site_perl/5.36.0
    /opt/freeware/lib/perl5/5.36.0/x86_64-linux
    /opt/freeware/lib/perl5/5.36.0
jkeenan commented 3 weeks ago

[snip]

The command is very simple: /bin/tiffdump foo 2>&1 where foo is a file name with no quotes or other special characters. This would qualify it for the shell-bypass optimization.

I looked for tiffdump on Ubuntu but the only packages returned by apt-cache search tiffdump, once installed, did not produce a page for man tiffdump. Suggestions? (However, on my FreeBSD machine I get such a man page.)

ikegami commented 3 weeks ago

I don't see how the specific command could matter.

ikegami commented 3 weeks ago

In fact, if I remove the die I added for debugging, a later qx in the same process exhibits the same problem, and it runs perl

mauke commented 3 weeks ago

Anything interesting from strace -f?

mauke commented 3 weeks ago

Actually, do you have a $SIG{CHLD} handler (or IGNORE) somewhere?

ikegami commented 3 weeks ago

Anything interesting from strace -f?

No.

70551 read(6,  <unfinished ...>
70583 <... exit_group resumed>)         = ?
70583 +++ exited with 0 +++
70551 <... read resumed>"", 8192)       = 0
70551 --- SIGCHLD {si_signo=SIGCHLD, si_code=CLD_EXITED, si_pid=70583, si_uid=203, si_status=0, si_utime=0, si_stime=0} ---
70551 rt_sigreturn({mask=[]})           = 0
70551 fstat(6, {st_mode=S_IFIFO|0600, st_size=0, ...}) = 0
70551 close(6)                          = 0
70551 wait4(70583, [{WIFEXITED(s) && WEXITSTATUS(s) == 0}], 0, NULL) = 70583
70551 rt_sigprocmask(SIG_BLOCK, [CHLD], [], 8) = 0
70551 wait4(-1, 0x7ffd274dcb84, WNOHANG, NULL) = -1 ECHILD (No child processes)
70551 rt_sigprocmask(SIG_UNBLOCK, [CHLD], NULL, 8) = 0
70551 write(2, "Failed tifPages: /tmp/1712586111"..., 90) = 90
70551 write(2, "\n", 1)                 = 1

(The message is slightly different than in the OP because the exception is caught and printed.)

ikegami commented 3 weeks ago

Actually, do you have a $SIG{CHLD} handler (or IGNORE) somewhere?

This just occurred to me too

...and you know what, a quick look indicates there might be one! I need to step out for an appointment, so I'll report back this evening.

ikegami commented 3 weeks ago

Actually, going back the strace, that looks more like a SIGCHLD handler than qx.

And I've confirmed it. There is a SIGCHLD handler being called.