NetBSD/sys/kern/kern_execve.c

547 lines
15 KiB
C

/*
* Copyright (c) 1989, 1990, 1991, 1992 William F. Jolitz, TeleMuse
* All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in the
* documentation and/or other materials provided with the distribution.
* 3. All advertising materials mentioning features or use of this software
* must display the following acknowledgement:
* This software is a component of "386BSD" developed by
* William F. Jolitz, TeleMuse.
* 4. Neither the name of the developer nor the name "386BSD"
* may be used to endorse or promote products derived from this software
* without specific prior written permission.
*
* THIS SOFTWARE IS A COMPONENT OF 386BSD DEVELOPED BY WILLIAM F. JOLITZ
* AND IS INTENDED FOR RESEARCH AND EDUCATIONAL PURPOSES ONLY. THIS
* SOFTWARE SHOULD NOT BE CONSIDERED TO BE A COMMERCIAL PRODUCT.
* THE DEVELOPER URGES THAT USERS WHO REQUIRE A COMMERCIAL PRODUCT
* NOT MAKE USE OF THIS WORK.
*
* FOR USERS WHO WISH TO UNDERSTAND THE 386BSD SYSTEM DEVELOPED
* BY WILLIAM F. JOLITZ, WE RECOMMEND THE USER STUDY WRITTEN
* REFERENCES SUCH AS THE "PORTING UNIX TO THE 386" SERIES
* (BEGINNING JANUARY 1991 "DR. DOBBS JOURNAL", USA AND BEGINNING
* JUNE 1991 "UNIX MAGAZIN", GERMANY) BY WILLIAM F. JOLITZ AND
* LYNNE GREER JOLITZ, AS WELL AS OTHER BOOKS ON UNIX AND THE
* ON-LINE 386BSD USER MANUAL BEFORE USE. A BOOK DISCUSSING THE INTERNALS
* OF 386BSD ENTITLED "386BSD FROM THE INSIDE OUT" WILL BE AVAILABLE LATE 1992.
*
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPER ``AS IS'' AND
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
* IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
* ARE DISCLAIMED. IN NO EVENT SHALL THE DEVELOPER BE LIABLE
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
* OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
* HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
* LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY
* OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
*
* This procedure implements a minimal program execution facility for
* 386BSD. It interfaces to the BSD kernel as the execve system call.
* Significant limitations and lack of compatiblity with POSIX are
* present with this version, to make its basic operation more clear.
*
* $Id: kern_execve.c,v 1.11 1993/05/20 02:54:21 cgd Exp $
*/
#include "param.h"
#include "systm.h"
#include "signalvar.h"
#include "resourcevar.h"
#include "proc.h"
#include "mount.h"
#include "namei.h"
#include "vnode.h"
#include "file.h"
#include "exec.h"
#include "stat.h"
#include "wait.h"
#include "mman.h"
#include "malloc.h"
#include "acct.h"
#include "vm/vm.h"
#include "vm/vm_param.h"
#include "vm/vm_map.h"
#include "vm/vm_kern.h"
#include "machine/reg.h"
extern int dostacklimits;
#define copyinoutstr copyinstr
/*
* execve() system call.
*/
/* ARGSUSED */
execve(p, uap, retval)
struct proc *p;
register struct args {
char *fname;
char **argp;
char **envp;
} *uap;
int *retval;
{
register struct nameidata *ndp;
struct nameidata nd;
char **argbuf, **argbufp, *stringbuf, *stringbufp;
char **vectp, *ep;
int needsenv, limitonargs, stringlen, addr, size, len,
rv, amt, argc, tsize, dsize, bsize, cnt, foff;
struct vattr attr;
struct vmspace *vs;
caddr_t newframe;
char shellname[MAXINTERP]; /* 05 Aug 92*/
char arguments[MAXINTERP];
long magic;
union {
char ex_shell[MAXINTERP]; /* #! and interpreter name */
struct exec ex_hdr;
} exdata;
int indir = 0;
/*
* Step 1. Lookup filename to see if we have something to execute.
*/
ndp = &nd;
ndp->ni_segflg = UIO_USERSPACE;
ndp->ni_dirp = uap->fname;
again: /* 05 Aug 92*/
ndp->ni_nameiop = LOOKUP | LOCKLEAF | FOLLOW | SAVENAME;
/* is it there? */
if (rv = namei(ndp, p))
return (rv);
/* does it have any attributes? */
rv = VOP_GETATTR(ndp->ni_vp, &attr, p->p_ucred, p);
if (rv)
goto exec_fail;
if (ndp->ni_vp->v_mount->mnt_flag & MNT_NOEXEC) { /* no exec on fs ?*/
rv = EACCES;
goto exec_fail;
}
/* is it executable, and a regular file? */
if ((ndp->ni_vp->v_mount->mnt_flag & MNT_NOEXEC) || /* 29 Jul 92*/
(VOP_ACCESS(ndp->ni_vp, VEXEC, p->p_ucred, p)) ||
((attr.va_mode & 0111) == 0) ||
(attr.va_type != VREG)) {
rv = EACCES;
goto exec_fail;
}
/*
* Step 2. Does the file contain a format we can
* understand and execute
*
* XXX 05 Aug 92
* Read in first few bytes of file for segment sizes, magic number:
* ZMAGIC = demand paged RO text
* QMAGIC = "compact" demand paged RO text
* (first page unmapped, a.out header in text section)
* 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, ndp->ni_vp, (caddr_t)&exdata, sizeof(exdata),
0, UIO_SYSSPACE, IO_NODELOCKED, p->p_ucred, &amt, p);
/* big enough to hold a header? */
if (rv)
goto exec_fail;
if (exdata.ex_hdr.a_text != 0 && (ndp->ni_vp->v_flag & VTEXT) == 0 &&
ndp->ni_vp->v_writecount != 0) {
rv = ETXTBSY;
goto exec_fail;
}
/* ... that we recognize? */
rv = ENOEXEC;
magic = exdata.ex_hdr.a_magic;
/*
* XXX We should call a machine-dependent function
* to determine what to do with an executable.
*/
if (magic != ZMAGIC && magic != QMAGIC) {
char *cp, *sp;
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++;
sp = shellname;
while (*cp && *cp != ' ')
*sp++ = *cp++;
*sp = '\0';
while (*cp == ' ')
cp++;
if (*cp) { /* extra argument */
sp = arguments;
while (*cp)
*sp++ = *cp++;
*sp = '\0';
indir = 2;
}
else
indir = 1; /* indicate this is a script file */
vput(ndp->ni_vp);
FREE(ndp->ni_pnbuf, M_NAMEI);
ndp->ni_dirp = shellname; /* find shell interpreter */
ndp->ni_segflg = UIO_SYSSPACE;
goto again;
}
/* sanity check "ain't not such thing as a sanity clause" -groucho */
rv = ENOMEM;
if (/*exdata.ex_hdr.a_text == 0 || */ exdata.ex_hdr.a_text > MAXTSIZ ||
exdata.ex_hdr.a_text % NBPG || exdata.ex_hdr.a_text > attr.va_size)
goto exec_fail;
if (exdata.ex_hdr.a_data == 0 || exdata.ex_hdr.a_data > DFLDSIZ
|| exdata.ex_hdr.a_data > attr.va_size
|| exdata.ex_hdr.a_data + exdata.ex_hdr.a_text > attr.va_size)
goto exec_fail;
if (exdata.ex_hdr.a_bss > MAXDSIZ)
goto exec_fail;
if (exdata.ex_hdr.a_text + exdata.ex_hdr.a_data + exdata.ex_hdr.a_bss > MAXTSIZ + MAXDSIZ)
goto exec_fail;
if (exdata.ex_hdr.a_data + exdata.ex_hdr.a_bss > p->p_rlimit[RLIMIT_DATA].rlim_cur)
goto exec_fail;
if (exdata.ex_hdr.a_entry > exdata.ex_hdr.a_text + exdata.ex_hdr.a_data)
goto exec_fail;
/*
* Step 3. File and header are valid. Now, dig out the strings
* out of the old process image.
*/
/*
* We implement a single-pass algorithm that builds a new stack
* frame within the address space of the "old" process image,
* avoiding the second pass entirely. Thus, the new frame is
* in position to be run. This consumes much virtual address space,
* and two pages more of 'real' memory, such are the costs.
* [Also, note the cache wipe that's avoided!]
*/
/* create anonymous memory region for new stack */
vs = p->p_vmspace;
if ((unsigned)vs->vm_maxsaddr + MAXSSIZ < USRSTACK)
newframe = (caddr_t) USRSTACK - MAXSSIZ;
else
vs->vm_maxsaddr = newframe = (caddr_t) USRSTACK - 2*MAXSSIZ;
/* don't do stack limit checking on traps temporarily XXX*/
dostacklimits = 0;
rv = vm_allocate(&vs->vm_map, &newframe, MAXSSIZ, FALSE);
if (rv) goto exec_fail;
/* allocate string buffer and arg buffer */
argbuf = (char **) (newframe + MAXSSIZ - 3*ARG_MAX);
stringbuf = stringbufp = ((char *)argbuf) + 2*ARG_MAX;
argbufp = argbuf;
/* first, do args */
vectp = uap->argp;
needsenv = 1;
limitonargs = ARG_MAX;
cnt = 0;
/* first, do (shell name if any then) args */
if (indir) {
ep = shellname;
thrice:
if (ep) {
/* did we outgrow initial argbuf, if so, die */
if (argbufp >= (char **)stringbuf) {
rv = E2BIG;
goto exec_dealloc;
}
if (rv = copyoutstr(ep, stringbufp,
(u_int)limitonargs, (u_int *)&stringlen)) {
if (rv == ENAMETOOLONG)
rv = E2BIG;
goto exec_dealloc;
}
suword(argbufp++, (int)stringbufp);
cnt++;
stringbufp += stringlen;
limitonargs -= stringlen;
}
if (indir == 2) {
indir = 1;
ep = arguments;
goto thrice;
}
if (indir == 1) {
indir = 0;
/* orginal executable is 1st argument with scripts */
ep = uap->fname;
goto thrice;
}
/* terminate in case no more args to script */
suword(argbufp, 0);
if (vectp = uap->argp) vectp++; /* manually doing the first
argument with scripts */
}
do_env_as_well:
if(vectp == 0) goto dont_bother;
/* for each envp, copy in string */
do {
/* did we outgrow initial argbuf, if so, die */
if (argbufp == (char **)stringbuf) {
rv = E2BIG;
goto exec_dealloc;
}
/* get an string pointer */
ep = (char *)fuword(vectp++);
if (ep == (char *)-1) {
rv = EFAULT;
goto exec_dealloc;
}
/* if not a null pointer, copy string */
if (ep) {
if (rv = copyinoutstr(ep, stringbufp,
(u_int)limitonargs, (u_int *) &stringlen)) {
if (rv == ENAMETOOLONG)
rv = E2BIG;
goto exec_dealloc;
}
suword(argbufp++, (int)stringbufp);
cnt++;
stringbufp += stringlen;
limitonargs -= stringlen;
} else {
suword(argbufp++, 0);
break;
}
} while (limitonargs > 0);
dont_bother:
if (limitonargs <= 0) {
rv = E2BIG;
goto exec_dealloc;
}
/* have we done the environment yet ? */
if (needsenv) {
/* remember the arg count for later */
argc = cnt;
vectp = uap->envp;
needsenv = 0;
goto do_env_as_well;
}
/* At this point, one could optionally implement a
* second pass to condense the strings, arguement vectors,
* and stack to fit the fewest pages.
*
* One might selectively do this when copying was cheaper
* than leaving allocated two more pages per process.
*/
/* stuff arg count on top of "new" stack */
/* argbuf[-1] = (char *)argc;*/
suword(argbuf-1,argc);
/*
* Step 4. Build the new processes image.
*
* At this point, we are committed -- destroy old executable!
*/
/* blow away all address space, except the stack */
rv = vm_deallocate(&vs->vm_map, 0, USRSTACK - 2*MAXSSIZ);
if (rv)
goto exec_abort;
/* destroy "old" stack */
if ((unsigned)newframe < USRSTACK - MAXSSIZ) {
rv = vm_deallocate(&vs->vm_map, USRSTACK - MAXSSIZ, MAXSSIZ);
if (rv)
goto exec_abort;
} else {
rv = vm_deallocate(&vs->vm_map, USRSTACK - 2*MAXSSIZ, MAXSSIZ);
if (rv)
goto exec_abort;
}
/* build a new address space */
/* screwball mode -- special case of 413 to save space for floppy */
if (exdata.ex_hdr.a_text == 0) {
foff = tsize = 0;
exdata.ex_hdr.a_data += exdata.ex_hdr.a_text;
} else {
tsize = roundup(exdata.ex_hdr.a_text, NBPG);
/*
* QMAGIC starts mapping in at the beginning
* of the file; ZMAGIC skips the header.
*/
foff = (magic == ZMAGIC ? NBPG : 0);
}
/* treat text and data in terms of integral page size */
dsize = roundup(exdata.ex_hdr.a_data, NBPG);
bsize = roundup(exdata.ex_hdr.a_bss + dsize, NBPG);
bsize -= dsize;
/*
* QMAGIC starts at one page, ZMAGIC at 0.
*/
addr = (magic == QMAGIC ? NBPG : 0);
/* map text & data in file, as being "paged in" on demand */
rv = vm_mmap(&vs->vm_map, &addr, tsize+dsize, VM_PROT_ALL, VM_PROT_DEFAULT,
MAP_FILE|MAP_COPY|MAP_FIXED, (caddr_t)ndp->ni_vp, foff);
if (rv)
goto exec_abort;
/* mark pages r/w data, r/o text */
if (tsize) {
rv = vm_protect(&vs->vm_map, addr, tsize, FALSE,
VM_PROT_READ|VM_PROT_EXECUTE);
if (rv)
goto exec_abort;
}
/* create anonymous memory region for bss */
/* We need to take QMAGIC's offset into account, or else get abort */
addr = dsize + tsize + (magic == QMAGIC ? NBPG : 0);
rv = vm_allocate(&vs->vm_map, &addr, bsize, FALSE);
if (rv)
goto exec_abort;
/*
* Step 5. Prepare process for execution.
*/
/* touchup process information -- vm system is unfinished! */
vs->vm_tsize = tsize/NBPG; /* text size (pages) XXX */
vs->vm_dsize = (dsize+bsize)/NBPG; /* data size (pages) XXX */
/* user virtual address of text XXX */
vs->vm_taddr = (caddr_t)(magic == QMAGIC ? NBPG : 0);
/* user virtual address of data XXX */
vs->vm_daddr = (caddr_t)tsize + (magic == QMAGIC ? NBPG : 0);
vs->vm_maxsaddr = newframe; /* user VA at max stack growth XXX */
vs->vm_ssize = ((unsigned)vs->vm_maxsaddr + MAXSSIZ
- (unsigned)argbuf)/ NBPG + 1; /* stack size (pages) */
dostacklimits = 1; /* allow stack limits to be enforced XXX */
/* close files on exec, fixup signals */
fdcloseexec(p);
execsigs(p);
/* name this process - nameiexec(p, ndp), and set up
* other accounting information
*/
len = MIN(ndp->ni_namelen,MAXCOMLEN);
bcopy(ndp->ni_ptr, p->p_comm, len);
p->p_comm[len] = 0;
p->p_acflag &= ~AFORK;
/* mark as executable, wakeup any process that was vforked and tell
* it that it now has it's own resources back */
p->p_flag |= SEXEC;
if (p->p_pptr && (p->p_flag & SPPWAIT)) {
p->p_flag &= ~SPPWAIT;
wakeup(p->p_pptr);
}
/* implement set userid/groupid */
if ((attr.va_mode&VSUID) && (p->p_flag & STRC) == 0) {
p->p_ucred = crcopy(p->p_ucred);
p->p_cred->p_svuid = p->p_ucred->cr_uid = attr.va_uid;
}
if ((attr.va_mode&VSGID) && (p->p_flag & STRC) == 0) {
p->p_ucred = crcopy(p->p_ucred);
p->p_cred->p_svgid = p->p_ucred->cr_groups[0] = attr.va_gid;
}
/* setup initial register state */
p->p_regs[SP] = (unsigned) (argbuf - 1);
setregs(p, exdata.ex_hdr.a_entry);
ndp->ni_vp->v_flag |= VTEXT; /* mark vnode pure text */
vput(ndp->ni_vp);
FREE(ndp->ni_pnbuf, M_NAMEI);
/* if tracing process, pass control back to debugger so breakpoints
can be set before the program "runs" */
if (p->p_flag & STRC)
psignal(p, SIGTRAP);
return (0);
exec_dealloc:
/* remove interim "new" stack frame we were building */
vm_deallocate(&vs->vm_map, newframe, MAXSSIZ);
exec_fail:
dostacklimits = 1;
vput(ndp->ni_vp);
FREE(ndp->ni_pnbuf, M_NAMEI);
return(rv);
exec_abort:
/* sorry, no more process anymore. exit gracefully */
vm_deallocate(&vs->vm_map, newframe, MAXSSIZ);
vput(ndp->ni_vp);
FREE(ndp->ni_pnbuf, M_NAMEI);
exit(p, W_EXITCODE(0, SIGABRT));
/* NOTREACHED */
return(0);
}