Tuesday, November 11, 2014

Unix Prog: popen, pclose(2)

1. popen, pclose implementation
pipe.c:
 #include<stdio.h>  
 #include<stdlib.h>  
 #include<unistd.h>  
 #include<errno.h>  
 #include<fcntl.h>  
 #include<sys/wait.h>  
   
 // pointer to array allocated at run-time  
 static pid_t *childpid = NULL;  
   
 // From our open_max()  
 static int maxfd;  
   
 FILE *popen(const char *cmdstring, const char *type)  
 {  
  int i;  
  int pfd[2];  
  pid_t pid;  
  FILE *fp;  
   
  // only allow "r" or "w"  
  if((type[0] != 'r' && type[0] != 'w') || type[1] != 0) {  
   errno = EINVAL;  
   return NULL;  
  }  
   
  // first time to allocate childpid  
  if(childpid == NULL) {  
   maxfd = 4096;  
   if((childpid = calloc(maxfd, sizeof(pid_t))) == NULL) {  
    return NULL;  
   }  
  }  
   
  // Setup the pipe  
  if(pipe(pfd) < 0) return NULL;  
   
  // Create the child process  
  if((pid = fork()) < 0) {  
   return NULL;  
  } else if(pid == 0) { // child process  
   // Handle the pipe file descriptor  
   if(*type == 'r') {  
    close(pfd[0]);  
    if(pfd[1] != STDOUT_FILENO) {  
     dup2(pfd[1], STDOUT_FILENO);  
     close(pfd[1]);  
    }  
   } else {  
    close(pfd[1]);  
    if(pfd[0] != STDIN_FILENO) {  
     dup2(pfd[0], STDIN_FILENO);  
     close(pfd[0]);  
    }  
   }  
   
   // close all existing descriptors which is opened before  
   // this is required by POSIX.1 standard  
   for(i = 0; i < maxfd; i++)  
    if(childpid[i] > 0)  
     close(i);  
   
   // Execute the descriptors  
   execl("/bin/sh", "sh", "-c", cmdstring, (char*)0);  
   _exit(127);  
  }  
   
  // parent process open the file stream from pipe descriptor  
  if(*type == 'r') {  
   close(pfd[1]);  
   if((fp = fdopen(pfd[0], type)) == NULL) return NULL;  
  } else {  
   close(pfd[0]);  
   if((fp = fdopen(pfd[1], type)) == NULL) return NULL;  
  }  
   
  childpid[fileno(fp)] = pid;  
  return fp;  
 }  
   
 int pclose(FILE* fp)  
 {  
  int fd, stat;  
  pid_t pid;  
   
  // popen has never been called.  
  if(childpid == NULL) {  
   errno = EINVAL;  
   return -1;  
  }  
   
  fd = fileno(fp);  
   
  // fp wasn't opened by popen()  
  if((pid = childpid[fd]) == 0) {  
   errno = EINVAL;  
   return -1;  
  }  
   
  childpid[fd] = 0;  
  if(fclose(fp) == EOF)  
   return -1;  
   
  while(waitpid(pid, &stat, 0) < 0) {  
   if(errno != EINTR)  
    return -1;  
  }  
   
  return stat;  
 }  
   
 int main(int argc, char* argv[])  
 {  
  exit(0);  
 }  

Note: popen should never be called by a set-user-id or set-group-id program. when it executes the command with the environment inherited by the caller.
A malicious user can manipulate the environment so that the shell executes commands other than those intended, with the elevated permissions granted by the set-ID file mode.

If the parent process setup the signal handler for SIGCHLD, waitpid is interrupted when child process exits, at this time, errno == EINTR, then we simply retry waitpid again (while waitpid loop).

No comments:

Post a Comment