From agate!usenet.ins.cwru.edu!magnus.acs.ohio-state.edu!cis.ohio-state.edu!pacific.mps.ohio-state.edu!linac!uwm.edu!ogicse!plains!tinguely@plains.NoDak.edu Thu Mar 26 03:57:55 PST 1992 Article: 452 of comp.unix.bsd Path: agate!usenet.ins.cwru.edu!magnus.acs.ohio-state.edu!cis.ohio-state.edu!pacific.mps.ohio-state.edu!linac!uwm.edu!ogicse!plains!tinguely@plains.NoDak.edu From: tinguely@plains.NoDak.edu (Mark Tinguely) Newsgroups: comp.unix.bsd Subject: patch for shells and suid Message-ID: <16255@plains.NoDak.edu> Date: 26 Mar 92 03:21:20 GMT Distribution: usa Organization: North Dakota State University, Fargo Lines: 366 Status: R Using ideas from the BSD 4.3 reno, I patched kern_execve.c to support set uid/gid and the use of executable (#!) scripts. I did not implement the standard ability of giving one argument after the executing shell, but left the hooks for the argument. ---------------------------- Cut here ------------------------------------- *** kern_execve.c Wed Mar 25 17:38:31 1992 --- kern_execve.new.c Wed Mar 25 17:39:05 1992 *************** *** 92,98 **** register struct nameidata *ndp; int rv, amt; struct nameidata nd; - struct exec hdr; char **kargbuf, **kargbufp, *kstringbuf, *kstringbufp; char **org, **vectp, *ep; u_int needsenv, limitonargs, stringlen; --- 55,60 ---- *************** *** 103,108 **** --- 65,88 ---- struct vmspace *vs; int tsize, dsize, bsize, cnt, foff; + /* + * added to allow set-[ug]id programs and #! scripts. ideas taken from BSD 4.3 + * reno. + */ + int indir = 0; + register struct pcred *pcred = p->p_cred; + register struct ucred *cred ; + struct vnode *vp; + struct vattr vattr; + uid_t uid; + gid_t gid; + char *shellname; + /* char cfarg[MAXINTERP]; /* arguments in a #! not supported */ + union { + char ex_shell[MAXINTERP]; /* #! and interpreter name */ + struct exec ex_exec; + } exdata; + /* * Step 1. Lookup filename to see if we have something to execute. */ *************** *** 115,166 **** if (rv = namei(ndp, p)) return (rv); /* is it a regular file? */ ! if (ndp->ni_vp->v_type != VREG) { ! vput(ndp->ni_vp); return(ENOEXEC); } /* is it executable? */ ! rv = VOP_ACCESS(ndp->ni_vp, VEXEC, p->p_ucred, p); if (rv) goto exec_fail; ! rv = vn_stat(ndp->ni_vp, &statb, p); if (rv) goto exec_fail; /* * Step 2. Does the file contain a format we can * understand and execute */ ! rv = vn_rdwr(UIO_READ, ndp->ni_vp, (caddr_t)&hdr, sizeof(hdr), ! 0, UIO_SYSSPACE, IO_NODELOCKED, p->p_ucred, &amt, p); ! /* big enough to hold a header? */ if (rv) goto exec_fail; ! /* that we recognize? */ rv = ENOEXEC; ! if (hdr.a_magic != ZMAGIC) ! goto exec_fail; /* sanity check "ain't not such thing as a sanity clause" -groucho */ ! if (/*hdr.a_text == 0 || */ hdr.a_text > MAXTSIZ ! || hdr.a_text % NBPG || hdr.a_text > statb.st_size) goto exec_fail; ! if (hdr.a_data == 0 || hdr.a_data > DFLDSIZ ! || hdr.a_data > statb.st_size ! || hdr.a_data + hdr.a_text > statb.st_size) goto exec_fail; ! if (hdr.a_bss > MAXDSIZ) goto exec_fail; ! if (hdr.a_text + hdr.a_data + hdr.a_bss > MAXTSIZ + MAXDSIZ) goto exec_fail; /* * Step 3. File and header are valid. Now, dig out the strings --- 95,231 ---- if (rv = namei(ndp, p)) return (rv); + /* + * added to allow set-[ug]id programs. Ideas are taken from BSD 4.3 reno + * code, I do not pretend these are all my own ideas. + */ + vp = ndp->ni_vp; + cred = pcred->pc_ucred; + uid = pcred->p_ruid; /* get orginal uid/gid */ + gid = pcred->p_rgid; + if (rv = VOP_GETATTR(vp, &vattr, cred, p)) + goto exec_fail; + if (vp->v_mount->mnt_flag & MNT_NOEXEC) { /* no exec on fs ? */ + rv = EACCES; + goto exec_fail; + } + if ((vp->v_mount->mnt_flag & MNT_NOSUID) == 0) { + if (vattr.va_mode & VSUID) /* check for SUID */ + uid = vattr.va_uid; + if (vattr.va_mode & VSGID) /* check for SGID */ + gid = vattr.va_gid; + } + + again: /* is it a regular file? */ ! if (vp->v_type != VREG) { ! vput(vp); return(ENOEXEC); } /* is it executable? */ ! rv = VOP_ACCESS(vp, VEXEC, cred, p); if (rv) goto exec_fail; ! rv = vn_stat(vp, &statb, p); if (rv) goto exec_fail; + /* * Step 2. Does the file contain a format we can * understand and execute */ ! /* ! * Read in first few bytes of file for segment sizes, magic number: ! * ZMAGIC = demand paged RO text ! * Also an ASCII line beginning with #! is ! * the file name of a ``shell'' and arguments may be prepended ! * to the argument list if given here. ! */ ! exdata.ex_shell[0] = '\0'; /* for zero length files */ ! rv = vn_rdwr(UIO_READ, vp, (caddr_t)&exdata, sizeof (exdata), ! (off_t)0, UIO_SYSSPACE, (IO_UNIT|IO_NODELOCKED), cred, &amt, p); if (rv) goto exec_fail; ! #ifndef lint ! if (amt > sizeof(exdata) - sizeof(exdata.ex_exec) && ! exdata.ex_shell[0] != '#') { ! rv = ENOEXEC; ! goto exec_fail; ! } ! #endif /* that we recognize? */ rv = ENOEXEC; ! if ((int)exdata.ex_exec.a_magic == ZMAGIC) { /* sanity check "ain't not such thing as a sanity clause" -groucho */ ! if (/*exdata.ex_exec.a_text == 0 || */ exdata.ex_exec.a_text > MAXTSIZ ! || exdata.ex_exec.a_text % NBPG || exdata.ex_exec.a_text > statb.st_size) goto exec_fail; ! if (exdata.ex_exec.a_data == 0 || exdata.ex_exec.a_data > DFLDSIZ ! || exdata.ex_exec.a_data > statb.st_size ! || exdata.ex_exec.a_data + exdata.ex_exec.a_text > statb.st_size) goto exec_fail; ! if (exdata.ex_exec.a_bss > MAXDSIZ) goto exec_fail; ! if (exdata.ex_exec.a_text + exdata.ex_exec.a_data + exdata.ex_exec.a_bss > MAXTSIZ + MAXDSIZ) goto exec_fail; + } + else { + if (exdata.ex_shell[0] != '#' || + exdata.ex_shell[1] != '!' || + indir) { + rv = ENOEXEC; + goto exec_fail; + } + for (cp = &exdata.ex_shell[2];; ++cp) { + if (cp >= &exdata.ex_shell[MAXINTERP]) { + rv = ENOEXEC; + goto exec_fail; + } + if (*cp == '\n') { + *cp = '\0'; + break; + } + if (*cp == '\t') + *cp = ' '; + } + cp = &exdata.ex_shell[2]; /* get shell interpreter name */ + while (*cp == ' ') + cp++; + shellname = ndp->ni_dirp = cp; + while (*cp && *cp != ' ') + cp++; + /* cfarg[0] = '\0'; /* not supported */ + if (*cp) { + *cp++ = '\0'; + /* I am ignoring any arguments, but really + this should be sent before the script file + name -- mft */ + /* while (*cp == ' ') + cp++; + if (*cp) + bcopy((caddr_t)cp, (caddr_t)cfarg, MAXINTERP); + */ } + indir = 1; /* indicate this is a script file */ + vput(vp); /* find shell interpreter */ + ndp->ni_nameiop = LOOKUP | FOLLOW | LOCKLEAF; + ndp->ni_segflg = UIO_SYSSPACE; + if (rv = namei(ndp, p)) + return (rv); + vp = ndp->ni_vp; + if (rv = VOP_GETATTR(vp, &vattr, cred, p)) + goto exec_fail; + uid = pcred->p_ruid; /* shell scripts can't be setuid */ + gid = pcred->p_rgid; + goto again; + } /* * Step 3. File and header are valid. Now, dig out the strings *************** *** 192,207 **** kstringbuf = kstringbufp = ((char *)kargbuf) + NBPG/2; kargbuf += NBPG/(4*sizeof(int)); kargbufp = kargbuf; ! /* first, do args */ needsenv = 1; ! vectp = uap->argp; do_env_as_well: - cnt = 0; /* for each envp, copy in string */ limitonargs = NCARGS; ! if(vectp == 0) goto dont_bother; do { /* did we outgrow initial argbuf, if so, die */ if (kargbufp == (char **)kstringbuf) --- 257,301 ---- kstringbuf = kstringbufp = ((char *)kargbuf) + NBPG/2; kargbuf += NBPG/(4*sizeof(int)); kargbufp = kargbuf; + cnt = 0; ! /* first, do (shell name if any then) args */ needsenv = 1; ! if (indir) { ! ep = shellname ; ! twice: ! if (ep) { ! /* did we outgrow initial argbuf, if so, die */ ! if (kargbufp == (char **)kstringbuf) ! goto exec_fail; ! ! if (rv = copyinstr(ep, kstringbufp, limitonargs, ! &stringlen)) goto exec_fail; ! /* assume that strings usually all fit in last page */ ! *kargbufp = (char *)(kstringbufp - kstringbuf ! + USRSTACK - NBPG + NBPG/2); ! kargbufp++; ! cnt++; ! kstringbufp += stringlen; ! limitonargs -= stringlen + sizeof(long); ! } + if (indir) { + indir = 0; + ep = uap->fname; /* orginal executable is 1st + argument with scripts */ + goto twice; + } + if (vectp = uap->argp) vectp++; /* manually doing the first + argument with scripts */ + } + else + vectp = uap->argp; /* normal executable */ + do_env_as_well: /* for each envp, copy in string */ limitonargs = NCARGS; ! if (vectp == 0) goto dont_bother; do { /* did we outgrow initial argbuf, if so, die */ if (kargbufp == (char **)kstringbuf) *************** *** 262,277 **** /* build a new address space */ addr = 0; ! if (hdr.a_text == 0) { /* screwball mode */ foff = tsize = 0; ! hdr.a_data += hdr.a_text; } else { ! tsize = roundup(hdr.a_text, NBPG); foff = NBPG; } ! dsize = roundup(hdr.a_data, NBPG); ! bsize = roundup(hdr.a_bss + dsize, NBPG); bsize -= dsize; /* map text & data*/ --- 356,371 ---- /* build a new address space */ addr = 0; ! if (exdata.ex_exec.a_text == 0) { /* screwball mode */ foff = tsize = 0; ! exdata.ex_exec.a_data += exdata.ex_exec.a_text; } else { ! tsize = roundup(exdata.ex_exec.a_text, NBPG); foff = NBPG; } ! dsize = roundup(exdata.ex_exec.a_data, NBPG); ! bsize = roundup(exdata.ex_exec.a_bss + dsize, NBPG); bsize -= dsize; /* map text & data*/ *************** *** 325,333 **** p->p_regs[SP] = USRSTACK - NBPG + NBPG/4 - 4; vs->vm_ssize = 1; /* stack size (pages) */ ! setregs(p, hdr.a_entry); kmem_free_wakeup(exec_map, org, (NCARGS + PAGE_SIZE)/PAGE_SIZE); vput(ndp->ni_vp); return (0); exec_fail: --- 419,435 ---- p->p_regs[SP] = USRSTACK - NBPG + NBPG/4 - 4; vs->vm_ssize = 1; /* stack size (pages) */ ! setregs(p, exdata.ex_exec.a_entry); kmem_free_wakeup(exec_map, org, (NCARGS + PAGE_SIZE)/PAGE_SIZE); vput(ndp->ni_vp); + /* + * set the [ug]id program. + */ + + if (p && p->p_cred) { + p->p_ucred->cr_uid = p->p_cred->p_svuid = uid; + p->p_ucred->cr_gid = p->p_cred->p_svgid = gid; + } return (0); exec_fail: