Open
Description
The issue
The Red Hat customer reported an interesting issue with $PROGRAM_NAME
($0
).
There is $PROGRAM_NAME
with result like /dev/fd/6
when the script is running under sudo
with checksum verification.
We have /usr/local/bin/script.pl
#!/usr/bin/perl
use strict;
use warnings;
use Cwd qw( abs_path );
print "\$0: $0\n";
print "abs_path(\$0): " . abs_path($0) . "\n";
Configuration of sudo:
$ echo "user ALL=NOPASSWD: sha256:$(sha256sum /usr/local/bin/script.pl)" > /etc/sudoers.d/user
Example user:
useradd user
Running as user.
root> su user
user> sudo /usr/local/bin/script.pl
Result like (on Fedora Rawhide):
$0: /dev/fd/6
abs_path($0): /proc/1961/fd/6
I investigated the issue, and there are some consequences:
- Perl doesn't user prctl API for get of
$PROGRAM_NAME
- C code with prctl works fine in this case.
C code in ex1.c:
#define _GNU_SOURCE
#include <stdio.h>
#include <string.h>
#include <sys/prctl.h>
int main(void) {
char name[17] = {0};
if (prctl(PR_GET_NAME, (unsigned long)name, 0, 0, 0) == -1) {
perror("prctl(PR_GET_NAME)");
return 1;
}
printf("Name of program: %s\n", name);
return 0;
}
Compilation:
gcc -o ex1 ex1.c
The result is:
Name of program: ex1
- When I looked at the code, it is in perl.c:
...
/* if find_script() returns, it returns a malloc()-ed value */
scriptname = PL_origfilename = find_script(scriptname, dosearch, NULL, 1);
s = scriptname + strlen(scriptname);
if (strBEGINs(scriptname, "/dev/fd/")
&& isDIGIT(scriptname[8])
&& grok_atoUV(scriptname + 8, &uv, &s)
&& uv <= PERL_INT_MAX
) {
fdscript = (int)uv;
if (*s) {
/* PSz 18 Feb 04
* Tell apart "normal" usage of fdscript, e.g.
* with bash on FreeBSD:
* perl <( echo '#!perl -DA'; echo 'print "$0\n"')
* from usage in suidperl.
* Does any "normal" usage leave garbage after the number???
* Is it a mistake to use a similar /dev/fd/ construct for
* suidperl?
*/
*suidscript = TRUE;
/* PSz 20 Feb 04
* Be supersafe and do some sanity-checks.
* Still, can we be sure we got the right thing?
*/
if (*s != '/') {
Perl_croak(aTHX_ "Wrong syntax (suid) fd script name \"%s\"\n", s);
}
if (! *(s+1)) {
Perl_croak(aTHX_ "Missing (suid) fd script name\n");
}
scriptname = savepv(s + 1);
Safefree(PL_origfilename);
PL_origfilename = (char *)scriptname;
}
}
}
...
The condition:
if (strBEGINs(scriptname, "/dev/fd/")
&& isDIGIT(scriptname[8])
&& grok_atoUV(scriptname + 8, &uv, &s)
&& uv <= PERL_INT_MAX
) {
hit the code, but there is no implementation for this situation.
- I found another situation with similar output
perl <( echo '#!perl -DA'; echo 'print "$0\n"');
The resolution
I tried to implement the resolving of /dev/fd/ to program name via readlink
like:
...
PL_origfilename = (char *)scriptname;
+ } else {
+ char proc_fd_path[64];
+ snprintf(proc_fd_path, sizeof(proc_fd_path), "/proc/self/fd/%d", fdscript);
+ char target_path[PATH_MAX];
+ ssize_t len = readlink(proc_fd_path, target_path, sizeof(target_path) - 1);
+ if (len != -1) {
+ target_path[len] = '\0';
+ PL_origfilename = savepv(target_path);
+ }
}
...
The result in 3) is /usr/local/bin/script.pl
The result in 4) is pipe:number
Questions
What do you think about this?