xref: /netbsd-src/sys/kern/exec_script.c (revision cac8e449158efc7261bebc8657cbb0125a2cfdde)
1 /*	$NetBSD: exec_script.c,v 1.62 2008/03/21 21:55:00 ad Exp $	*/
2 
3 /*
4  * Copyright (c) 1993, 1994, 1996 Christopher G. Demetriou
5  * All rights reserved.
6  *
7  * Redistribution and use in source and binary forms, with or without
8  * modification, are permitted provided that the following conditions
9  * are met:
10  * 1. Redistributions of source code must retain the above copyright
11  *    notice, this list of conditions and the following disclaimer.
12  * 2. Redistributions in binary form must reproduce the above copyright
13  *    notice, this list of conditions and the following disclaimer in the
14  *    documentation and/or other materials provided with the distribution.
15  * 3. All advertising materials mentioning features or use of this software
16  *    must display the following acknowledgement:
17  *      This product includes software developed by Christopher G. Demetriou.
18  * 4. The name of the author may not be used to endorse or promote products
19  *    derived from this software without specific prior written permission
20  *
21  * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR
22  * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
23  * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
24  * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT,
25  * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT
26  * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
27  * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
28  * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
29  * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF
30  * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
31  */
32 
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: exec_script.c,v 1.62 2008/03/21 21:55:00 ad Exp $");
35 
36 #if defined(SETUIDSCRIPTS) && !defined(FDSCRIPTS)
37 #define FDSCRIPTS		/* Need this for safe set-id scripts. */
38 #endif
39 
40 #include "veriexec.h"
41 
42 #include <sys/param.h>
43 #include <sys/systm.h>
44 #include <sys/proc.h>
45 #include <sys/kmem.h>
46 #include <sys/vnode.h>
47 #include <sys/namei.h>
48 #include <sys/file.h>
49 #ifdef SETUIDSCRIPTS
50 #include <sys/stat.h>
51 #endif
52 #include <sys/filedesc.h>
53 #include <sys/exec.h>
54 #include <sys/resourcevar.h>
55 
56 #include <sys/exec_script.h>
57 #include <sys/exec_elf.h>
58 
59 /*
60  * exec_script_makecmds(): Check if it's an executable shell script.
61  *
62  * Given a proc pointer and an exec package pointer, see if the referent
63  * of the epp is in shell script.  If it is, then set thing up so that
64  * the script can be run.  This involves preparing the address space
65  * and arguments for the shell which will run the script.
66  *
67  * This function is ultimately responsible for creating a set of vmcmds
68  * which can be used to build the process's vm space and inserting them
69  * into the exec package.
70  */
71 int
72 exec_script_makecmds(struct lwp *l, struct exec_package *epp)
73 {
74 	int error, hdrlinelen, shellnamelen, shellarglen;
75 	char *hdrstr = epp->ep_hdr;
76 	char *cp, *shellname, *shellarg, *oldpnbuf;
77 	size_t shellargp_len;
78 	struct exec_fakearg *shellargp;
79 	struct exec_fakearg *tmpsap;
80 	struct vnode *scriptvp;
81 #ifdef SETUIDSCRIPTS
82 	/* Gcc needs those initialized for spurious uninitialized warning */
83 	uid_t script_uid = (uid_t) -1;
84 	gid_t script_gid = NOGROUP;
85 	u_short script_sbits;
86 #endif
87 
88 	/*
89 	 * if the magic isn't that of a shell script, or we've already
90 	 * done shell script processing for this exec, punt on it.
91 	 */
92 	if ((epp->ep_flags & EXEC_INDIR) != 0 ||
93 	    epp->ep_hdrvalid < EXEC_SCRIPT_MAGICLEN ||
94 	    strncmp(hdrstr, EXEC_SCRIPT_MAGIC, EXEC_SCRIPT_MAGICLEN))
95 		return ENOEXEC;
96 
97 	/*
98 	 * check that the shell spec is terminated by a newline,
99 	 * and that it isn't too large.  Don't modify the
100 	 * buffer unless we're ready to commit to handling it.
101 	 * (The latter requirement means that we have to check
102 	 * for both spaces and tabs later on.)
103 	 */
104 	hdrlinelen = min(epp->ep_hdrvalid, SCRIPT_HDR_SIZE);
105 	for (cp = hdrstr + EXEC_SCRIPT_MAGICLEN; cp < hdrstr + hdrlinelen;
106 	    cp++) {
107 		if (*cp == '\n') {
108 			*cp = '\0';
109 			break;
110 		}
111 	}
112 	if (cp >= hdrstr + hdrlinelen)
113 		return ENOEXEC;
114 
115 	/*
116 	 * If the script has an ELF header, don't exec it.
117 	 */
118 	if (epp->ep_hdrvalid >= sizeof(ELFMAG)-1 &&
119 	    memcmp(hdrstr, ELFMAG, sizeof(ELFMAG)-1) == 0)
120 		return ENOEXEC;
121 
122 	shellname = NULL;
123 	shellarg = NULL;
124 	shellarglen = 0;
125 
126 	/* strip spaces before the shell name */
127 	for (cp = hdrstr + EXEC_SCRIPT_MAGICLEN; *cp == ' ' || *cp == '\t';
128 	    cp++)
129 		;
130 
131 	/* collect the shell name; remember it's length for later */
132 	shellname = cp;
133 	shellnamelen = 0;
134 	if (*cp == '\0')
135 		goto check_shell;
136 	for ( /* cp = cp */ ; *cp != '\0' && *cp != ' ' && *cp != '\t'; cp++)
137 		shellnamelen++;
138 	if (*cp == '\0')
139 		goto check_shell;
140 	*cp++ = '\0';
141 
142 	/* skip spaces before any argument */
143 	for ( /* cp = cp */ ; *cp == ' ' || *cp == '\t'; cp++)
144 		;
145 	if (*cp == '\0')
146 		goto check_shell;
147 
148 	/*
149 	 * collect the shell argument.  everything after the shell name
150 	 * is passed as ONE argument; that's the correct (historical)
151 	 * behaviour.
152 	 */
153 	shellarg = cp;
154 	for ( /* cp = cp */ ; *cp != '\0'; cp++)
155 		shellarglen++;
156 	*cp++ = '\0';
157 
158 check_shell:
159 #ifdef SETUIDSCRIPTS
160 	/*
161 	 * MNT_NOSUID has already taken care of by check_exec,
162 	 * so we don't need to worry about it now or later.  We
163 	 * will need to check PSL_TRACED later, however.
164 	 */
165 	script_sbits = epp->ep_vap->va_mode & (S_ISUID | S_ISGID);
166 	if (script_sbits != 0) {
167 		script_uid = epp->ep_vap->va_uid;
168 		script_gid = epp->ep_vap->va_gid;
169 	}
170 #endif
171 #ifdef FDSCRIPTS
172 	/*
173 	 * if the script isn't readable, or it's set-id, then we've
174 	 * gotta supply a "/dev/fd/..." for the shell to read.
175 	 * Note that stupid shells (csh) do the wrong thing, and
176 	 * close all open fd's when the start.  That kills this
177 	 * method of implementing "safe" set-id and x-only scripts.
178 	 */
179 	vn_lock(epp->ep_vp, LK_EXCLUSIVE | LK_RETRY);
180 	error = VOP_ACCESS(epp->ep_vp, VREAD, l->l_cred);
181 	VOP_UNLOCK(epp->ep_vp, 0);
182 	if (error == EACCES
183 #ifdef SETUIDSCRIPTS
184 	    || script_sbits
185 #endif
186 	    ) {
187 		struct file *fp;
188 
189 #if defined(DIAGNOSTIC) && defined(FDSCRIPTS)
190 		if (epp->ep_flags & EXEC_HASFD)
191 			panic("exec_script_makecmds: epp already has a fd");
192 #endif
193 
194 		if ((error = fd_allocfile(&fp, &epp->ep_fd)) != 0) {
195 			scriptvp = NULL;
196 			shellargp = NULL;
197 			goto fail;
198 		}
199 		epp->ep_flags |= EXEC_HASFD;
200 		fp->f_type = DTYPE_VNODE;
201 		fp->f_ops = &vnops;
202 		fp->f_data = (void *) epp->ep_vp;
203 		fp->f_flag = FREAD;
204 		fd_affix(curproc, fp, epp->ep_fd);
205 	}
206 #endif
207 
208 	/* set up the parameters for the recursive check_exec() call */
209 	epp->ep_ndp->ni_dirp = shellname;
210 	epp->ep_ndp->ni_segflg = UIO_SYSSPACE;
211 	epp->ep_flags |= EXEC_INDIR;
212 
213 	/* and set up the fake args list, for later */
214 	shellargp_len = 4 * sizeof(*shellargp);
215 	shellargp = kmem_alloc(shellargp_len, KM_SLEEP);
216 	tmpsap = shellargp;
217 	tmpsap->fa_len = shellnamelen + 1;
218 	tmpsap->fa_arg = kmem_alloc(tmpsap->fa_len, KM_SLEEP);
219 	strlcpy(tmpsap->fa_arg, shellname, tmpsap->fa_len);
220 	tmpsap++;
221 	if (shellarg != NULL) {
222 		tmpsap->fa_len = shellarglen + 1;
223 		tmpsap->fa_arg = kmem_alloc(tmpsap->fa_len, KM_SLEEP);
224 		strlcpy(tmpsap->fa_arg, shellarg, tmpsap->fa_len);
225 		tmpsap++;
226 	}
227 	tmpsap->fa_len = MAXPATHLEN;
228 	tmpsap->fa_arg = kmem_alloc(tmpsap->fa_len, KM_SLEEP);
229 #ifdef FDSCRIPTS
230 	if ((epp->ep_flags & EXEC_HASFD) == 0) {
231 #endif
232 		/* normally can't fail, but check for it if diagnostic */
233 		error = copyinstr(epp->ep_name, tmpsap->fa_arg, MAXPATHLEN,
234 		    (size_t *)0);
235 		tmpsap++;
236 #ifdef DIAGNOSTIC
237 		if (error != 0)
238 			panic("exec_script: copyinstr couldn't fail");
239 #endif
240 #ifdef FDSCRIPTS
241 	} else {
242 		snprintf(tmpsap->fa_arg, MAXPATHLEN, "/dev/fd/%d", epp->ep_fd);
243 		tmpsap++;
244 	}
245 #endif
246 	tmpsap->fa_arg = NULL;
247 
248 	/*
249 	 * mark the header we have as invalid; check_exec will read
250 	 * the header from the new executable
251 	 */
252 	epp->ep_hdrvalid = 0;
253 
254 	/*
255 	 * remember the old vp and pnbuf for later, so we can restore
256 	 * them if check_exec() fails.
257 	 */
258 	scriptvp = epp->ep_vp;
259 	oldpnbuf = epp->ep_ndp->ni_cnd.cn_pnbuf;
260 
261 	error = check_exec(l, epp);
262 	/* note that we've clobbered the header */
263 	epp->ep_flags |= EXEC_DESTR;
264 	if (error == 0) {
265 		/*
266 		 * It succeeded.  Unlock the script and
267 		 * close it if we aren't using it any more.
268 		 * Also, set things up so that the fake args
269 		 * list will be used.
270 		 */
271 		if ((epp->ep_flags & EXEC_HASFD) == 0) {
272 			vn_lock(scriptvp, LK_EXCLUSIVE | LK_RETRY);
273 			VOP_CLOSE(scriptvp, FREAD, l->l_cred);
274 			vput(scriptvp);
275 		}
276 
277 		/* free the old pathname buffer */
278 		PNBUF_PUT(oldpnbuf);
279 
280 		epp->ep_flags |= (EXEC_HASARGL | EXEC_SKIPARG);
281 		epp->ep_fa = shellargp;
282 		epp->ep_fa_len = shellargp_len;
283 #ifdef SETUIDSCRIPTS
284 		/*
285 		 * set thing up so that set-id scripts will be
286 		 * handled appropriately.  PSL_TRACED will be
287 		 * checked later when the shell is actually
288 		 * exec'd.
289 		 */
290 		epp->ep_vap->va_mode |= script_sbits;
291 		if (script_sbits & S_ISUID)
292 			epp->ep_vap->va_uid = script_uid;
293 		if (script_sbits & S_ISGID)
294 			epp->ep_vap->va_gid = script_gid;
295 #endif
296 		return (0);
297 	}
298 
299 	/* XXX oldpnbuf not set for "goto fail" path */
300 	epp->ep_ndp->ni_cnd.cn_pnbuf = oldpnbuf;
301 #ifdef FDSCRIPTS
302 fail:
303 #endif
304 
305 	/* kill the opened file descriptor, else close the file */
306         if (epp->ep_flags & EXEC_HASFD) {
307                 epp->ep_flags &= ~EXEC_HASFD;
308                 fd_close(epp->ep_fd);
309         } else if (scriptvp) {
310 		vn_lock(scriptvp, LK_EXCLUSIVE | LK_RETRY);
311 		VOP_CLOSE(scriptvp, FREAD, l->l_cred);
312 		vput(scriptvp);
313 	}
314 
315         PNBUF_PUT(epp->ep_ndp->ni_cnd.cn_pnbuf);
316 
317 	/* free the fake arg list, because we're not returning it */
318 	if ((tmpsap = shellargp) != NULL) {
319 		while (tmpsap->fa_arg != NULL) {
320 			kmem_free(tmpsap->fa_arg, tmpsap->fa_len);
321 			tmpsap++;
322 		}
323 		kmem_free(shellargp, shellargp_len);
324 	}
325 
326         /*
327          * free any vmspace-creation commands,
328          * and release their references
329          */
330         kill_vmcmds(&epp->ep_vmcmds);
331 
332         return error;
333 }
334