Tomas Klacko
2014-01-08 13:00:21 UTC
Hi,
the loop:
1. When ksh starts a binary, it sets its environment variable "_"
to "*number*/path/to/binary". Where "number" is the pid of the ksh process.
2. The binary forks and the child executes a suid root shell script
which begins with #!/bin/sh. On Solaris, this is Korn shell.
3. The ksh process interpreting the suid shell script leaves the "_"
variable as not set (nv_getval(L_ARGNOD) returns NULL) because
the "number" from step 1 is not the pid of its parent process.
4. Because "_" is not set and the script is suid root, the following
condition
is true in src/cmd/ksh93/sh/main.c:
296 if(((type = sh_type(cp = av[0])) & SH_TYPE_SH) && (!(name
= nv_getval(L_ARGNOD)) || !((type = sh_type(cp = name)) & SH_TYPE_SH)))
297 {
and this code gets executed:
298 av[0] = (type & SH_TYPE_LOGIN) ? cp : path_basename(cp);
299 /* exec to change $0 for ps */
300 execv(pathshell(),av);
301 /* exec fails */
302 shp->st.dolv[0] = av[0];
303 fixargs(shp->st.dolv,1);
304 }
5. When the SHELL environment variable contains "/bin/sh"
then pathshell() returns "/bin/sh". This becomes an infinite
loop of /bin/sh /dev/fd/3 executing /bin/sh /dev/fd/3.
/dev/fd/3 is the suid root script passed in via file descriptor 3.
The first /bin/sh in the loop is the #!/bin/sh from the suid script.
The second and subsequent /bin/sh in the loop is from
the SHELL environment variable.
This is the fix which avoids the infinite loop:
<fix>
--- /dev/null Thu Oct 31 04:52:33 2013
+++ new/components/ksh93/patches/17432413.patch Thu Oct 31 04:52:32 2013
@@ -0,0 +1,23 @@
--- a/src/cmd/ksh93/sh/init.c
+++ b/src/cmd/ksh93/sh/init.c
@@ -720,7 +720,7 @@ static char* get_lastarg(Namval_t* np, Namfun_t *fp)
char *cp;
int pid;
if(sh_isstate(SH_INIT) && (cp=shp->lastarg) && *cp=='*' &&
(pid=strtol(cp+1,&cp,10)) && *cp=='*')
- nv_putval(np,(pid==shp->gd->ppid?cp+1:0),0);
+ nv_putval(np,cp+1,0);
return(shp->lastarg);
}
--- a/src/cmd/ksh93/sh/main.c
+++ b/src/cmd/ksh93/sh/main.c
@@ -298,7 +298,7 @@ int sh_main(int ac, char *av[], Shinit_f userinit)
*/
if (shp->st.repl_index > 0)
av[shp->st.repl_index] = shp->st.repl_arg;
- if(((type = sh_type(cp = av[0]))
& SH_TYPE_SH) && (!(name = nv_getval(L_ARGNOD)) || !((type = sh_type(cp
= name)) & SH_TYPE_SH)))
+ if(((type = sh_type(cp = av[0]))
& SH_TYPE_SH) && (name = nv_getval(L_ARGNOD)) && (!((type = sh_type(cp =
name)) & SH_TYPE_SH)))
{
av[0] = (type &
SH_TYPE_LOGIN) ? cp : path_basename(cp);
/* exec to change $0
for ps */
</fix>
The get_lastarg() change disables the check whether the "number"
refers to the process id of the parent process.
The sh_main() change prevents infinite loop when the "_" variable
is not passed in from the environment.
This is the source code which sets all the necessary conditions
and triggers the infinite loop:
<source>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define ERROR \
do{ \
fprintf(stderr, "[%s, %d] error\n", __FILE__, __LINE__); \
exit(EXIT_FAILURE); \
}while(0)
int main(int argc, char *argv[])
{
char devfd[]="/dev/fd/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
char *arg[5];
int fd;
if(argc!=2)
ERROR;
fd=open(argv[1], O_RDONLY);
if(fd<0)
ERROR;
sprintf(devfd, "/dev/fd/%d", fd);
arg[0]="sh";
arg[1]=devfd;
arg[2]="AAA";
arg[3]="BBB";
arg[4]=NULL;
if(argc>1)
unsetenv("_");
else
setenv("_", "*1234*/garbage", 1);
setenv("SHELL", "/bin/sh", 1);
execvp("/bin/sh", arg);
ERROR;
}
</source>
Is it ok that calling pathshell() on line 300 can actually
lead to a Korn script being executed by Bash for instance?
(when SHELL is set to /bin/bash)
Tomas Klacko
the loop:
1. When ksh starts a binary, it sets its environment variable "_"
to "*number*/path/to/binary". Where "number" is the pid of the ksh process.
2. The binary forks and the child executes a suid root shell script
which begins with #!/bin/sh. On Solaris, this is Korn shell.
3. The ksh process interpreting the suid shell script leaves the "_"
variable as not set (nv_getval(L_ARGNOD) returns NULL) because
the "number" from step 1 is not the pid of its parent process.
4. Because "_" is not set and the script is suid root, the following
condition
is true in src/cmd/ksh93/sh/main.c:
296 if(((type = sh_type(cp = av[0])) & SH_TYPE_SH) && (!(name
= nv_getval(L_ARGNOD)) || !((type = sh_type(cp = name)) & SH_TYPE_SH)))
297 {
and this code gets executed:
298 av[0] = (type & SH_TYPE_LOGIN) ? cp : path_basename(cp);
299 /* exec to change $0 for ps */
300 execv(pathshell(),av);
301 /* exec fails */
302 shp->st.dolv[0] = av[0];
303 fixargs(shp->st.dolv,1);
304 }
5. When the SHELL environment variable contains "/bin/sh"
then pathshell() returns "/bin/sh". This becomes an infinite
loop of /bin/sh /dev/fd/3 executing /bin/sh /dev/fd/3.
/dev/fd/3 is the suid root script passed in via file descriptor 3.
The first /bin/sh in the loop is the #!/bin/sh from the suid script.
The second and subsequent /bin/sh in the loop is from
the SHELL environment variable.
This is the fix which avoids the infinite loop:
<fix>
--- /dev/null Thu Oct 31 04:52:33 2013
+++ new/components/ksh93/patches/17432413.patch Thu Oct 31 04:52:32 2013
@@ -0,0 +1,23 @@
--- a/src/cmd/ksh93/sh/init.c
+++ b/src/cmd/ksh93/sh/init.c
@@ -720,7 +720,7 @@ static char* get_lastarg(Namval_t* np, Namfun_t *fp)
char *cp;
int pid;
if(sh_isstate(SH_INIT) && (cp=shp->lastarg) && *cp=='*' &&
(pid=strtol(cp+1,&cp,10)) && *cp=='*')
- nv_putval(np,(pid==shp->gd->ppid?cp+1:0),0);
+ nv_putval(np,cp+1,0);
return(shp->lastarg);
}
--- a/src/cmd/ksh93/sh/main.c
+++ b/src/cmd/ksh93/sh/main.c
@@ -298,7 +298,7 @@ int sh_main(int ac, char *av[], Shinit_f userinit)
*/
if (shp->st.repl_index > 0)
av[shp->st.repl_index] = shp->st.repl_arg;
- if(((type = sh_type(cp = av[0]))
& SH_TYPE_SH) && (!(name = nv_getval(L_ARGNOD)) || !((type = sh_type(cp
= name)) & SH_TYPE_SH)))
+ if(((type = sh_type(cp = av[0]))
& SH_TYPE_SH) && (name = nv_getval(L_ARGNOD)) && (!((type = sh_type(cp =
name)) & SH_TYPE_SH)))
{
av[0] = (type &
SH_TYPE_LOGIN) ? cp : path_basename(cp);
/* exec to change $0
for ps */
</fix>
The get_lastarg() change disables the check whether the "number"
refers to the process id of the parent process.
The sh_main() change prevents infinite loop when the "_" variable
is not passed in from the environment.
This is the source code which sets all the necessary conditions
and triggers the infinite loop:
<source>
#include <fcntl.h>
#include <stdio.h>
#include <stdlib.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <unistd.h>
#define ERROR \
do{ \
fprintf(stderr, "[%s, %d] error\n", __FILE__, __LINE__); \
exit(EXIT_FAILURE); \
}while(0)
int main(int argc, char *argv[])
{
char devfd[]="/dev/fd/xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxx";
char *arg[5];
int fd;
if(argc!=2)
ERROR;
fd=open(argv[1], O_RDONLY);
if(fd<0)
ERROR;
sprintf(devfd, "/dev/fd/%d", fd);
arg[0]="sh";
arg[1]=devfd;
arg[2]="AAA";
arg[3]="BBB";
arg[4]=NULL;
if(argc>1)
unsetenv("_");
else
setenv("_", "*1234*/garbage", 1);
setenv("SHELL", "/bin/sh", 1);
execvp("/bin/sh", arg);
ERROR;
}
</source>
Is it ok that calling pathshell() on line 300 can actually
lead to a Korn script being executed by Bash for instance?
(when SHELL is set to /bin/bash)
Tomas Klacko