1 /* $NetBSD: exec_script.c,v 1.62 2008/03/21 21:55:00 ad Exp $ */
4 * Copyright (c) 1993, 1994, 1996 Christopher G. Demetriou
7 * Redistribution and use in source and binary forms, with or without
8 * modification, are permitted provided that the following conditions
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
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.
33 #include <sys/cdefs.h>
34 __KERNEL_RCSID(0, "$NetBSD: exec_script.c,v 1.62 2008/03/21 21:55:00 ad Exp $");
36 #if defined(SETUIDSCRIPTS) && !defined(FDSCRIPTS)
37 #define FDSCRIPTS /* Need this for safe set-id scripts. */
40 #include <sys/param.h>
41 #include <sys/systm.h>
44 #include <sys/vnode.h>
45 #include <sys/namei.h>
50 #include <sys/filedesc.h>
52 #include <sys/resourcevar.h>
53 #include <sys/module.h>
54 #include <sys/exec_script.h>
55 #include <sys/exec_elf.h>
57 MODULE(MODULE_CLASS_MISC
, exec_script
, NULL
);
59 static struct execsw exec_script_execsw
[] = {
73 exec_script_modcmd(modcmd_t cmd
, void *arg
)
78 return exec_add(exec_script_execsw
,
79 __arraycount(exec_script_execsw
));
82 return exec_remove(exec_script_execsw
,
83 __arraycount(exec_script_execsw
));
85 case MODULE_CMD_AUTOUNLOAD
:
87 * We don't want to be autounloaded because our use is
88 * transient: no executables with p_execsw equal to
89 * exec_script_execsw will exist, so FINI will never
90 * return EBUSY. However, the system will run scripts
91 * often. Return EBUSY here to prevent this module from
92 * ping-ponging in and out of the kernel.
102 * exec_script_makecmds(): Check if it's an executable shell script.
104 * Given a proc pointer and an exec package pointer, see if the referent
105 * of the epp is in shell script. If it is, then set thing up so that
106 * the script can be run. This involves preparing the address space
107 * and arguments for the shell which will run the script.
109 * This function is ultimately responsible for creating a set of vmcmds
110 * which can be used to build the process's vm space and inserting them
111 * into the exec package.
114 exec_script_makecmds(struct lwp
*l
, struct exec_package
*epp
)
116 int error
, hdrlinelen
, shellnamelen
, shellarglen
;
117 char *hdrstr
= epp
->ep_hdr
;
118 char *cp
, *shellname
, *shellarg
, *oldpnbuf
;
119 size_t shellargp_len
;
120 struct exec_fakearg
*shellargp
;
121 struct exec_fakearg
*tmpsap
;
122 struct vnode
*scriptvp
;
124 /* Gcc needs those initialized for spurious uninitialized warning */
125 uid_t script_uid
= (uid_t
) -1;
126 gid_t script_gid
= NOGROUP
;
127 u_short script_sbits
;
131 * if the magic isn't that of a shell script, or we've already
132 * done shell script processing for this exec, punt on it.
134 if ((epp
->ep_flags
& EXEC_INDIR
) != 0 ||
135 epp
->ep_hdrvalid
< EXEC_SCRIPT_MAGICLEN
||
136 strncmp(hdrstr
, EXEC_SCRIPT_MAGIC
, EXEC_SCRIPT_MAGICLEN
))
140 * check that the shell spec is terminated by a newline,
141 * and that it isn't too large. Don't modify the
142 * buffer unless we're ready to commit to handling it.
143 * (The latter requirement means that we have to check
144 * for both spaces and tabs later on.)
146 hdrlinelen
= min(epp
->ep_hdrvalid
, SCRIPT_HDR_SIZE
);
147 for (cp
= hdrstr
+ EXEC_SCRIPT_MAGICLEN
; cp
< hdrstr
+ hdrlinelen
;
154 if (cp
>= hdrstr
+ hdrlinelen
)
158 * If the script has an ELF header, don't exec it.
160 if (epp
->ep_hdrvalid
>= sizeof(ELFMAG
)-1 &&
161 memcmp(hdrstr
, ELFMAG
, sizeof(ELFMAG
)-1) == 0)
168 /* strip spaces before the shell name */
169 for (cp
= hdrstr
+ EXEC_SCRIPT_MAGICLEN
; *cp
== ' ' || *cp
== '\t';
173 /* collect the shell name; remember it's length for later */
178 for ( /* cp = cp */ ; *cp
!= '\0' && *cp
!= ' ' && *cp
!= '\t'; cp
++)
184 /* skip spaces before any argument */
185 for ( /* cp = cp */ ; *cp
== ' ' || *cp
== '\t'; cp
++)
191 * collect the shell argument. everything after the shell name
192 * is passed as ONE argument; that's the correct (historical)
196 for ( /* cp = cp */ ; *cp
!= '\0'; cp
++)
203 * MNT_NOSUID has already taken care of by check_exec,
204 * so we don't need to worry about it now or later. We
205 * will need to check PSL_TRACED later, however.
207 script_sbits
= epp
->ep_vap
->va_mode
& (S_ISUID
| S_ISGID
);
208 if (script_sbits
!= 0) {
209 script_uid
= epp
->ep_vap
->va_uid
;
210 script_gid
= epp
->ep_vap
->va_gid
;
215 * if the script isn't readable, or it's set-id, then we've
216 * gotta supply a "/dev/fd/..." for the shell to read.
217 * Note that stupid shells (csh) do the wrong thing, and
218 * close all open fd's when the start. That kills this
219 * method of implementing "safe" set-id and x-only scripts.
221 vn_lock(epp
->ep_vp
, LK_EXCLUSIVE
| LK_RETRY
);
222 error
= VOP_ACCESS(epp
->ep_vp
, VREAD
, l
->l_cred
);
223 VOP_UNLOCK(epp
->ep_vp
, 0);
231 #if defined(DIAGNOSTIC) && defined(FDSCRIPTS)
232 if (epp
->ep_flags
& EXEC_HASFD
)
233 panic("exec_script_makecmds: epp already has a fd");
236 if ((error
= fd_allocfile(&fp
, &epp
->ep_fd
)) != 0) {
241 epp
->ep_flags
|= EXEC_HASFD
;
242 fp
->f_type
= DTYPE_VNODE
;
244 fp
->f_data
= (void *) epp
->ep_vp
;
246 fd_affix(curproc
, fp
, epp
->ep_fd
);
250 /* set up the parameters for the recursive check_exec() call */
251 epp
->ep_ndp
->ni_dirp
= shellname
;
252 epp
->ep_ndp
->ni_segflg
= UIO_SYSSPACE
;
253 epp
->ep_flags
|= EXEC_INDIR
;
255 /* and set up the fake args list, for later */
256 shellargp_len
= 4 * sizeof(*shellargp
);
257 shellargp
= kmem_alloc(shellargp_len
, KM_SLEEP
);
259 tmpsap
->fa_len
= shellnamelen
+ 1;
260 tmpsap
->fa_arg
= kmem_alloc(tmpsap
->fa_len
, KM_SLEEP
);
261 strlcpy(tmpsap
->fa_arg
, shellname
, tmpsap
->fa_len
);
263 if (shellarg
!= NULL
) {
264 tmpsap
->fa_len
= shellarglen
+ 1;
265 tmpsap
->fa_arg
= kmem_alloc(tmpsap
->fa_len
, KM_SLEEP
);
266 strlcpy(tmpsap
->fa_arg
, shellarg
, tmpsap
->fa_len
);
269 tmpsap
->fa_len
= MAXPATHLEN
;
270 tmpsap
->fa_arg
= kmem_alloc(tmpsap
->fa_len
, KM_SLEEP
);
272 if ((epp
->ep_flags
& EXEC_HASFD
) == 0) {
274 /* normally can't fail, but check for it if diagnostic */
275 error
= copyinstr(epp
->ep_name
, tmpsap
->fa_arg
, MAXPATHLEN
,
280 panic("exec_script: copyinstr couldn't fail");
284 snprintf(tmpsap
->fa_arg
, MAXPATHLEN
, "/dev/fd/%d", epp
->ep_fd
);
288 tmpsap
->fa_arg
= NULL
;
291 * mark the header we have as invalid; check_exec will read
292 * the header from the new executable
294 epp
->ep_hdrvalid
= 0;
297 * remember the old vp and pnbuf for later, so we can restore
298 * them if check_exec() fails.
300 scriptvp
= epp
->ep_vp
;
301 oldpnbuf
= epp
->ep_ndp
->ni_cnd
.cn_pnbuf
;
303 error
= check_exec(l
, epp
);
304 /* note that we've clobbered the header */
305 epp
->ep_flags
|= EXEC_DESTR
;
308 * It succeeded. Unlock the script and
309 * close it if we aren't using it any more.
310 * Also, set things up so that the fake args
313 if ((epp
->ep_flags
& EXEC_HASFD
) == 0) {
314 vn_lock(scriptvp
, LK_EXCLUSIVE
| LK_RETRY
);
315 VOP_CLOSE(scriptvp
, FREAD
, l
->l_cred
);
319 /* free the old pathname buffer */
322 epp
->ep_flags
|= (EXEC_HASARGL
| EXEC_SKIPARG
);
323 epp
->ep_fa
= shellargp
;
324 epp
->ep_fa_len
= shellargp_len
;
327 * set thing up so that set-id scripts will be
328 * handled appropriately. PSL_TRACED will be
329 * checked later when the shell is actually
332 epp
->ep_vap
->va_mode
|= script_sbits
;
333 if (script_sbits
& S_ISUID
)
334 epp
->ep_vap
->va_uid
= script_uid
;
335 if (script_sbits
& S_ISGID
)
336 epp
->ep_vap
->va_gid
= script_gid
;
341 /* XXX oldpnbuf not set for "goto fail" path */
342 epp
->ep_ndp
->ni_cnd
.cn_pnbuf
= oldpnbuf
;
347 /* kill the opened file descriptor, else close the file */
348 if (epp
->ep_flags
& EXEC_HASFD
) {
349 epp
->ep_flags
&= ~EXEC_HASFD
;
350 fd_close(epp
->ep_fd
);
351 } else if (scriptvp
) {
352 vn_lock(scriptvp
, LK_EXCLUSIVE
| LK_RETRY
);
353 VOP_CLOSE(scriptvp
, FREAD
, l
->l_cred
);
357 PNBUF_PUT(epp
->ep_ndp
->ni_cnd
.cn_pnbuf
);
359 /* free the fake arg list, because we're not returning it */
360 if ((tmpsap
= shellargp
) != NULL
) {
361 while (tmpsap
->fa_arg
!= NULL
) {
362 kmem_free(tmpsap
->fa_arg
, tmpsap
->fa_len
);
365 kmem_free(shellargp
, shellargp_len
);
369 * free any vmspace-creation commands,
370 * and release their references
372 kill_vmcmds(&epp
->ep_vmcmds
);