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