1 /* $NetBSD: exec_script.c,v 1.85 2024/12/06 16:19:41 riastradh 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.85 2024/12/06 16:19:41 riastradh Exp $"); 35 36 #ifdef _KERNEL_OPT 37 #include "opt_script.h" 38 #endif 39 40 #if defined(SETUIDSCRIPTS) && !defined(FDSCRIPTS) 41 #define FDSCRIPTS /* Need this for safe set-id scripts. */ 42 #endif 43 44 #include <sys/param.h> 45 #include <sys/types.h> 46 47 #include <sys/exec.h> 48 #include <sys/exec_elf.h> 49 #include <sys/exec_script.h> 50 #include <sys/file.h> 51 #include <sys/filedesc.h> 52 #include <sys/kmem.h> 53 #include <sys/module.h> 54 #include <sys/namei.h> 55 #include <sys/proc.h> 56 #include <sys/resourcevar.h> 57 #include <sys/sdt.h> 58 #ifdef SETUIDSCRIPTS 59 #include <sys/stat.h> 60 #endif 61 #include <sys/systm.h> 62 #include <sys/vnode.h> 63 64 MODULE(MODULE_CLASS_EXEC, exec_script, NULL); 65 66 static struct execsw exec_script_execsw = { 67 .es_hdrsz = SCRIPT_HDR_SIZE, 68 .es_makecmds = exec_script_makecmds, 69 .u = { 70 .elf_probe_func = NULL, 71 }, 72 .es_emul = NULL, 73 .es_prio = EXECSW_PRIO_ANY, 74 .es_arglen = 0, 75 .es_copyargs = NULL, 76 .es_setregs = NULL, 77 .es_coredump = NULL, 78 .es_setup_stack = exec_setup_stack, 79 }; 80 81 static int 82 exec_script_modcmd(modcmd_t cmd, void *arg) 83 { 84 85 switch (cmd) { 86 case MODULE_CMD_INIT: 87 return exec_add(&exec_script_execsw, 1); 88 89 case MODULE_CMD_FINI: 90 return exec_remove(&exec_script_execsw, 1); 91 92 case MODULE_CMD_AUTOUNLOAD: 93 /* 94 * We don't want to be autounloaded because our use is 95 * transient: no executables with p_execsw equal to 96 * exec_script_execsw will exist, so FINI will never 97 * return EBUSY. However, the system will run scripts 98 * often. Return EBUSY here to prevent this module from 99 * ping-ponging in and out of the kernel. 100 */ 101 return SET_ERROR(EBUSY); 102 103 default: 104 return SET_ERROR(ENOTTY); 105 } 106 } 107 108 /* 109 * exec_script_makecmds(): Check if it's an executable shell script. 110 * 111 * Given a proc pointer and an exec package pointer, see if the referent 112 * of the epp is in shell script. If it is, then set thing up so that 113 * the script can be run. This involves preparing the address space 114 * and arguments for the shell which will run the script. 115 * 116 * This function is ultimately responsible for creating a set of vmcmds 117 * which can be used to build the process's vm space and inserting them 118 * into the exec package. 119 */ 120 int 121 exec_script_makecmds(struct lwp *l, struct exec_package *epp) 122 { 123 int error, hdrlinelen, shellnamelen, shellarglen; 124 char *hdrstr = epp->ep_hdr; 125 char *cp, *shellname, *shellarg; 126 size_t shellargp_len; 127 struct exec_fakearg *shellargp; 128 struct exec_fakearg *tmpsap; 129 struct pathbuf *shell_pathbuf; 130 struct vnode *scriptvp; 131 #ifdef SETUIDSCRIPTS 132 /* Gcc needs those initialized for spurious uninitialized warning */ 133 uid_t script_uid = (uid_t) -1; 134 gid_t script_gid = NOGROUP; 135 u_short script_sbits; 136 #endif 137 138 /* 139 * if the magic isn't that of a shell script, or we've already 140 * done shell script processing for this exec, punt on it. 141 */ 142 if ((epp->ep_flags & EXEC_INDIR) != 0 || 143 epp->ep_hdrvalid < EXEC_SCRIPT_MAGICLEN || 144 strncmp(hdrstr, EXEC_SCRIPT_MAGIC, EXEC_SCRIPT_MAGICLEN)) 145 return SET_ERROR(ENOEXEC); 146 147 /* 148 * Check that the shell spec is terminated by a newline, and that 149 * it isn't too large. 150 */ 151 hdrlinelen = uimin(epp->ep_hdrvalid, SCRIPT_HDR_SIZE); 152 for (cp = hdrstr + EXEC_SCRIPT_MAGICLEN; cp < hdrstr + hdrlinelen; 153 cp++) { 154 if (*cp == '\n') { 155 *cp = '\0'; 156 break; 157 } 158 } 159 if (cp >= hdrstr + hdrlinelen) 160 return SET_ERROR(ENOEXEC); 161 162 /* strip spaces before the shell name */ 163 for (cp = hdrstr + EXEC_SCRIPT_MAGICLEN; *cp == ' ' || *cp == '\t'; 164 cp++) 165 ; 166 if (*cp == '\0') 167 return SET_ERROR(ENOEXEC); 168 169 shellarg = NULL; 170 shellarglen = 0; 171 172 /* collect the shell name; remember its length for later */ 173 shellname = cp; 174 shellnamelen = 0; 175 for ( /* cp = cp */ ; *cp != '\0' && *cp != ' ' && *cp != '\t'; cp++) 176 shellnamelen++; 177 if (*cp == '\0') 178 goto check_shell; 179 *cp++ = '\0'; 180 181 /* skip spaces before any argument */ 182 for ( /* cp = cp */ ; *cp == ' ' || *cp == '\t'; cp++) 183 ; 184 if (*cp == '\0') 185 goto check_shell; 186 187 /* 188 * collect the shell argument. everything after the shell name 189 * is passed as ONE argument; that's the correct (historical) 190 * behaviour. 191 */ 192 shellarg = cp; 193 for ( /* cp = cp */ ; *cp != '\0'; cp++) 194 shellarglen++; 195 *cp++ = '\0'; 196 197 check_shell: 198 #ifdef SETUIDSCRIPTS 199 /* 200 * MNT_NOSUID has already taken care of by check_exec, 201 * so we don't need to worry about it now or later. We 202 * will need to check PSL_TRACED later, however. 203 */ 204 script_sbits = epp->ep_vap->va_mode & (S_ISUID | S_ISGID); 205 if (script_sbits != 0) { 206 script_uid = epp->ep_vap->va_uid; 207 script_gid = epp->ep_vap->va_gid; 208 } 209 #endif 210 #ifdef FDSCRIPTS 211 /* 212 * if the script isn't readable, or it's set-id, then we've 213 * gotta supply a "/dev/fd/..." for the shell to read. 214 * Note that stupid shells (csh) do the wrong thing, and 215 * close all open fd's when they start. That kills this 216 * method of implementing "safe" set-id and x-only scripts. 217 */ 218 vn_lock(epp->ep_vp, LK_SHARED | LK_RETRY); 219 error = VOP_ACCESS(epp->ep_vp, VREAD, l->l_cred); 220 VOP_UNLOCK(epp->ep_vp); 221 if (error == EACCES 222 #ifdef SETUIDSCRIPTS 223 || script_sbits 224 #endif 225 ) { 226 struct file *fp; 227 228 KASSERT(!(epp->ep_flags & EXEC_HASFD)); 229 230 if ((error = fd_allocfile(&fp, &epp->ep_fd)) != 0) { 231 scriptvp = NULL; 232 shellargp = NULL; 233 goto fail; 234 } 235 epp->ep_flags |= EXEC_HASFD; 236 fp->f_type = DTYPE_VNODE; 237 fp->f_ops = &vnops; 238 fp->f_vnode = epp->ep_vp; 239 fp->f_flag = FREAD; 240 fd_affix(curproc, fp, epp->ep_fd); 241 } 242 #endif 243 244 /* set up the fake args list */ 245 shellargp_len = 4 * sizeof(*shellargp); 246 shellargp = kmem_alloc(shellargp_len, KM_SLEEP); 247 tmpsap = shellargp; 248 tmpsap->fa_len = shellnamelen + 1; 249 tmpsap->fa_arg = kmem_alloc(tmpsap->fa_len, KM_SLEEP); 250 strlcpy(tmpsap->fa_arg, shellname, tmpsap->fa_len); 251 tmpsap++; 252 if (shellarg != NULL) { 253 tmpsap->fa_len = shellarglen + 1; 254 tmpsap->fa_arg = kmem_alloc(tmpsap->fa_len, KM_SLEEP); 255 strlcpy(tmpsap->fa_arg, shellarg, tmpsap->fa_len); 256 tmpsap++; 257 } 258 tmpsap->fa_len = MAXPATHLEN; 259 tmpsap->fa_arg = kmem_alloc(tmpsap->fa_len, KM_SLEEP); 260 #ifdef FDSCRIPTS 261 if ((epp->ep_flags & EXEC_HASFD) == 0) { 262 #endif 263 /* normally can't fail, but check for it if diagnostic */ 264 error = copystr(epp->ep_kname, tmpsap->fa_arg, MAXPATHLEN, 265 NULL); 266 KASSERT(error == 0); 267 tmpsap++; 268 #ifdef FDSCRIPTS 269 } else { 270 snprintf(tmpsap->fa_arg, MAXPATHLEN, "/dev/fd/%d", epp->ep_fd); 271 tmpsap++; 272 } 273 #endif 274 tmpsap->fa_arg = NULL; 275 276 /* Save the old vnode so we can clean it up later. */ 277 scriptvp = epp->ep_vp; 278 epp->ep_vp = NULL; 279 280 /* Note that we're trying recursively. */ 281 epp->ep_flags |= EXEC_INDIR; 282 283 /* 284 * mark the header we have as invalid; check_exec will read 285 * the header from the new executable 286 */ 287 epp->ep_hdrvalid = 0; 288 289 /* try loading the interpreter */ 290 if ((error = exec_makepathbuf(l, shellname, UIO_SYSSPACE, 291 &shell_pathbuf, NULL)) == 0) { 292 error = check_exec(l, epp, shell_pathbuf, NULL); 293 pathbuf_destroy(shell_pathbuf); 294 } 295 296 /* note that we've clobbered the header */ 297 epp->ep_flags |= EXEC_DESTR; 298 299 if (error == 0) { 300 /* 301 * It succeeded. Unlock the script and 302 * close it if we aren't using it any more. 303 * Also, set things up so that the fake args 304 * list will be used. 305 */ 306 if ((epp->ep_flags & EXEC_HASFD) == 0) { 307 vn_lock(scriptvp, LK_EXCLUSIVE | LK_RETRY); 308 VOP_CLOSE(scriptvp, FREAD, l->l_cred); 309 vput(scriptvp); 310 } 311 312 epp->ep_flags |= (EXEC_HASARGL | EXEC_SKIPARG); 313 epp->ep_fa = shellargp; 314 epp->ep_fa_len = shellargp_len; 315 #ifdef SETUIDSCRIPTS 316 /* 317 * set thing up so that set-id scripts will be 318 * handled appropriately. PSL_TRACED will be 319 * checked later when the shell is actually 320 * exec'd. 321 */ 322 epp->ep_vap->va_mode |= script_sbits; 323 if (script_sbits & S_ISUID) 324 epp->ep_vap->va_uid = script_uid; 325 if (script_sbits & S_ISGID) 326 epp->ep_vap->va_gid = script_gid; 327 #endif 328 return (0); 329 } 330 331 #ifdef FDSCRIPTS 332 fail: 333 #endif 334 335 /* kill the opened file descriptor, else close the file */ 336 if (epp->ep_flags & EXEC_HASFD) { 337 epp->ep_flags &= ~EXEC_HASFD; 338 fd_close(epp->ep_fd); 339 } else if (scriptvp) { 340 vn_lock(scriptvp, LK_EXCLUSIVE | LK_RETRY); 341 VOP_CLOSE(scriptvp, FREAD, l->l_cred); 342 vput(scriptvp); 343 } 344 345 /* free the fake arg list, because we're not returning it */ 346 if ((tmpsap = shellargp) != NULL) { 347 while (tmpsap->fa_arg != NULL) { 348 kmem_free(tmpsap->fa_arg, tmpsap->fa_len); 349 tmpsap++; 350 } 351 kmem_free(shellargp, shellargp_len); 352 } 353 354 /* 355 * free any vmspace-creation commands, 356 * and release their references 357 */ 358 kill_vmcmds(&epp->ep_vmcmds); 359 360 return error; 361 } 362