From: buzz@toxicavenger.bear.com (Buzz Moschetti)
Newsgroups: alt.security
Subject: Re: Secure setuid programs: A model
Date: 19 Aug 92 20:19:05 GMT
Reply-To: buzz@fsrg.bear.com
Distribution: alt
Organization: Bear, Stearns & Co. Inc.
In-reply-to: buzz@toxicavenger.bear.com's message of 19 Aug 92 12:39:41 GMT

In article <BUZZ.92Aug19083941@toxicavenger.bear.com> buzz@toxicavenger.bear.com (Buzz Moschetti) writes:

   We wish to make mount and umount available to two or three non-root accounts.
   The plan is create a C code fork/exec wrapper that does the following:

	   1.  Verifies that the euid is root.
	   2.  Verifies that the uid is one of the "privileged" users.
	   3.  Forces argv[0] to be /usr/etc/(u)mount.
	   4.  Does an execv(3) -- NOT execvp(3) -- to run the argv.

   [...deleted...]
   I believe this to be secure.  Can someone claim otherwise?

I forgot to mention that yes, the environment had been scrapped before calling
execv(3), but in any case I received several pieces of mail with some tips.
Here is a "model" program that incorporates those tips:

---8<------8<------8<------8<------8<------8<------8<------8<------8<---
/*
Here is a "model" safe setuid program -- thanks to all who sent
me mail with suggestions.  Features include:

1.  All sigs except SIGKILL and SIGCHLD shut down.
2.  All file descriptors except for stdin, out, & err closed prior to exec.
3.  "Trusted environment" only is passed via exeve().
4.  Static linking to be EXTRA safe; eliminates the dynamic load issue.

Edit the obvious things (trusted_user, etc.) and compile this program with:
cc mmount.c -Bstatic -o mmount

The binary should be stripped and chmod-ed to 4711 to prevent strings(1)
from being run on it.  It works just fine on SunOS 4.1.1 using the plain
ol' /bin/cc C compiler.  I left in some debugging printf()s; these would
be eliminated in a "production" version.

Can anyone add anything else?  Are we secure yet?
*/

#include <stdio.h>
#include <signal.h>

#include <pwd.h>
#include <sys/wait.h>



static char *trusted_user = "buzz";

static char *trusted_env[] = {
      "PATH=/usr/etc",
      0
};

main(argc, argv)
int argc;
char **argv;
{
      struct passwd *uid_ent;
      int uid, euid;
      int pid, i;
      int status;
      char **p;

      /*  Prevent signal tricks: */
      for(i = 0; i < NSIG; i++) {
	    if(i != SIGKILL && i != SIGCHLD) {
		  (void) signal(i, SIG_IGN);
	    }
      }
	
      /* Prevent file descriptor tricks; close all but stdin, out, & err: */
      for(i = getdtablesize(); i > 2; i--) {
	    (void) close(i);
      }
	    

      uid = getuid();
      euid = geteuid();

      (void) printf("uid: %d\n", uid);
      (void) printf("euid: %d\n", euid);


      if(euid != 0) {
	    (void) fprintf(stderr, "This program must be setuid root.\n");
	    exit(1);
      }

      uid_ent = getpwuid(uid);

      if(strcmp(uid_ent->pw_name, trusted_user)) {
	    (void) fprintf(stderr, "Only %s may run this program.\n",
			   trusted_user);
	    exit(1);
      }


      /* Force execution of the proper mount: */
      argv[0] = "/usr/etc/mount";


      (void) printf("argv: ");
      for(p = argv; *p != (char *)0; p++) {
	    (void) printf("[%s] ", *p);
      }
      (void) puts("");
      
      switch(pid = fork()) {
	  case -1:
	    return -1;

	  case 0:  /* child! */
	    (void) execve(argv[0], argv, trusted_env);

	    _exit(1);  /* Didn't execute! */
	    
	  default:  /* parent! */
	    break;
      }

      pid = wait(&status);

      if(WIFEXITED(status)) {
	    (void) fprintf(stderr, "child pid %d exited w/code %d\n",
			   pid, WEXITSTATUS(status));
      } else if(WIFSIGNALED(status)) {
	    (void) fprintf(stderr, "child pid %d recv'd signal %d\n",
			   pid, WTERMSIG(status));
      } else {
	    fprintf(stderr, "child did something else!\n");
      }
}


Newsgroups: alt.security,comp.security.misc