1*b5090f27Sriastradh /* $NetBSD: exec_script.c,v 1.85 2024/12/06 16:19:41 riastradh Exp $ */ 2cf92afd6Scgd 38c996398Scgd /* 43b34cba8Scgd * Copyright (c) 1993, 1994, 1996 Christopher G. Demetriou 58c996398Scgd * All rights reserved. 68c996398Scgd * 78c996398Scgd * Redistribution and use in source and binary forms, with or without 88c996398Scgd * modification, are permitted provided that the following conditions 98c996398Scgd * are met: 108c996398Scgd * 1. Redistributions of source code must retain the above copyright 118c996398Scgd * notice, this list of conditions and the following disclaimer. 128c996398Scgd * 2. Redistributions in binary form must reproduce the above copyright 138c996398Scgd * notice, this list of conditions and the following disclaimer in the 148c996398Scgd * documentation and/or other materials provided with the distribution. 158c996398Scgd * 3. All advertising materials mentioning features or use of this software 168c996398Scgd * must display the following acknowledgement: 178c996398Scgd * This product includes software developed by Christopher G. Demetriou. 188c996398Scgd * 4. The name of the author may not be used to endorse or promote products 1952351800Sjtc * derived from this software without specific prior written permission 208c996398Scgd * 218c996398Scgd * THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 228c996398Scgd * IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES 238c996398Scgd * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. 248c996398Scgd * IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, INDIRECT, 258c996398Scgd * INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT 268c996398Scgd * NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, 278c996398Scgd * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY 288c996398Scgd * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT 298c996398Scgd * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF 308c996398Scgd * THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. 318c996398Scgd */ 328c996398Scgd 33adc783d5Slukem #include <sys/cdefs.h> 34*b5090f27Sriastradh __KERNEL_RCSID(0, "$NetBSD: exec_script.c,v 1.85 2024/12/06 16:19:41 riastradh Exp $"); 35f3e9eebeSchristos 36873631e7Skre #ifdef _KERNEL_OPT 37f3e9eebeSchristos #include "opt_script.h" 38873631e7Skre #endif 39adc783d5Slukem 408c996398Scgd #if defined(SETUIDSCRIPTS) && !defined(FDSCRIPTS) 418c996398Scgd #define FDSCRIPTS /* Need this for safe set-id scripts. */ 428c996398Scgd #endif 438c996398Scgd 448c996398Scgd #include <sys/param.h> 45f47e8594Sriastradh #include <sys/types.h> 46f47e8594Sriastradh 47f47e8594Sriastradh #include <sys/exec.h> 48f47e8594Sriastradh #include <sys/exec_elf.h> 49f47e8594Sriastradh #include <sys/exec_script.h> 508c996398Scgd #include <sys/file.h> 51f47e8594Sriastradh #include <sys/filedesc.h> 52f47e8594Sriastradh #include <sys/kmem.h> 53f47e8594Sriastradh #include <sys/module.h> 54f47e8594Sriastradh #include <sys/namei.h> 55f47e8594Sriastradh #include <sys/proc.h> 56f47e8594Sriastradh #include <sys/resourcevar.h> 57*b5090f27Sriastradh #include <sys/sdt.h> 582b0c0d95Schristos #ifdef SETUIDSCRIPTS 592b0c0d95Schristos #include <sys/stat.h> 602b0c0d95Schristos #endif 61f47e8594Sriastradh #include <sys/systm.h> 62f47e8594Sriastradh #include <sys/vnode.h> 638c996398Scgd 64721e82b5Schristos MODULE(MODULE_CLASS_EXEC, exec_script, NULL); 6592ce8c6aSad 6654b7adb1Schristos static struct execsw exec_script_execsw = { 6754b7adb1Schristos .es_hdrsz = SCRIPT_HDR_SIZE, 6854b7adb1Schristos .es_makecmds = exec_script_makecmds, 6954b7adb1Schristos .u = { 7054b7adb1Schristos .elf_probe_func = NULL, 7154b7adb1Schristos }, 7254b7adb1Schristos .es_emul = NULL, 7354b7adb1Schristos .es_prio = EXECSW_PRIO_ANY, 7454b7adb1Schristos .es_arglen = 0, 7554b7adb1Schristos .es_copyargs = NULL, 7654b7adb1Schristos .es_setregs = NULL, 7754b7adb1Schristos .es_coredump = NULL, 7854b7adb1Schristos .es_setup_stack = exec_setup_stack, 7992ce8c6aSad }; 8092ce8c6aSad 8192ce8c6aSad static int 8292ce8c6aSad exec_script_modcmd(modcmd_t cmd, void *arg) 8392ce8c6aSad { 8492ce8c6aSad 8592ce8c6aSad switch (cmd) { 8692ce8c6aSad case MODULE_CMD_INIT: 8754b7adb1Schristos return exec_add(&exec_script_execsw, 1); 8892ce8c6aSad 8992ce8c6aSad case MODULE_CMD_FINI: 9054b7adb1Schristos return exec_remove(&exec_script_execsw, 1); 9192ce8c6aSad 9292ce8c6aSad case MODULE_CMD_AUTOUNLOAD: 9392ce8c6aSad /* 9492ce8c6aSad * We don't want to be autounloaded because our use is 9592ce8c6aSad * transient: no executables with p_execsw equal to 9692ce8c6aSad * exec_script_execsw will exist, so FINI will never 9792ce8c6aSad * return EBUSY. However, the system will run scripts 9892ce8c6aSad * often. Return EBUSY here to prevent this module from 9992ce8c6aSad * ping-ponging in and out of the kernel. 10092ce8c6aSad */ 101*b5090f27Sriastradh return SET_ERROR(EBUSY); 10292ce8c6aSad 10392ce8c6aSad default: 104*b5090f27Sriastradh return SET_ERROR(ENOTTY); 10592ce8c6aSad } 10692ce8c6aSad } 10792ce8c6aSad 1088c996398Scgd /* 1098c996398Scgd * exec_script_makecmds(): Check if it's an executable shell script. 1108c996398Scgd * 1118c996398Scgd * Given a proc pointer and an exec package pointer, see if the referent 1128c996398Scgd * of the epp is in shell script. If it is, then set thing up so that 1138c996398Scgd * the script can be run. This involves preparing the address space 1148c996398Scgd * and arguments for the shell which will run the script. 1158c996398Scgd * 1168c996398Scgd * This function is ultimately responsible for creating a set of vmcmds 1178c996398Scgd * which can be used to build the process's vm space and inserting them 1188c996398Scgd * into the exec package. 1198c996398Scgd */ 1208c996398Scgd int 12195e1ffb1Schristos exec_script_makecmds(struct lwp *l, struct exec_package *epp) 1228c996398Scgd { 1238c996398Scgd int error, hdrlinelen, shellnamelen, shellarglen; 1248c996398Scgd char *hdrstr = epp->ep_hdr; 12554f6c52bSdholland char *cp, *shellname, *shellarg; 12622d57011Syamt size_t shellargp_len; 12722d57011Syamt struct exec_fakearg *shellargp; 12822d57011Syamt struct exec_fakearg *tmpsap; 1298f6ed30dSdholland struct pathbuf *shell_pathbuf; 1308c996398Scgd struct vnode *scriptvp; 1318c996398Scgd #ifdef SETUIDSCRIPTS 1322b0c0d95Schristos /* Gcc needs those initialized for spurious uninitialized warning */ 1332b0c0d95Schristos uid_t script_uid = (uid_t) -1; 1342b0c0d95Schristos gid_t script_gid = NOGROUP; 1358c996398Scgd u_short script_sbits; 1368c996398Scgd #endif 1378c996398Scgd 1388c996398Scgd /* 1398c996398Scgd * if the magic isn't that of a shell script, or we've already 1408c996398Scgd * done shell script processing for this exec, punt on it. 1418c996398Scgd */ 1428c996398Scgd if ((epp->ep_flags & EXEC_INDIR) != 0 || 1438c996398Scgd epp->ep_hdrvalid < EXEC_SCRIPT_MAGICLEN || 1448c996398Scgd strncmp(hdrstr, EXEC_SCRIPT_MAGIC, EXEC_SCRIPT_MAGICLEN)) 145*b5090f27Sriastradh return SET_ERROR(ENOEXEC); 1468c996398Scgd 1478c996398Scgd /* 1481d2bb559Smaxv * Check that the shell spec is terminated by a newline, and that 1491d2bb559Smaxv * it isn't too large. 1508c996398Scgd */ 151d1579b2dSriastradh hdrlinelen = uimin(epp->ep_hdrvalid, SCRIPT_HDR_SIZE); 1528c996398Scgd for (cp = hdrstr + EXEC_SCRIPT_MAGICLEN; cp < hdrstr + hdrlinelen; 1538c996398Scgd cp++) { 1548c996398Scgd if (*cp == '\n') { 1558c996398Scgd *cp = '\0'; 1568c996398Scgd break; 1578c996398Scgd } 1588c996398Scgd } 1598c996398Scgd if (cp >= hdrstr + hdrlinelen) 160*b5090f27Sriastradh return SET_ERROR(ENOEXEC); 1618c996398Scgd 1628c996398Scgd /* strip spaces before the shell name */ 1638c996398Scgd for (cp = hdrstr + EXEC_SCRIPT_MAGICLEN; *cp == ' ' || *cp == '\t'; 1648c996398Scgd cp++) 1658c996398Scgd ; 1668bd04c63Smaxv if (*cp == '\0') 167*b5090f27Sriastradh return SET_ERROR(ENOEXEC); 1688c996398Scgd 1691d2bb559Smaxv shellarg = NULL; 1701d2bb559Smaxv shellarglen = 0; 1711d2bb559Smaxv 1721d2bb559Smaxv /* collect the shell name; remember its length for later */ 1738c996398Scgd shellname = cp; 1748c996398Scgd shellnamelen = 0; 1758c996398Scgd for ( /* cp = cp */ ; *cp != '\0' && *cp != ' ' && *cp != '\t'; cp++) 1768c996398Scgd shellnamelen++; 1778c996398Scgd if (*cp == '\0') 1788c996398Scgd goto check_shell; 1798c996398Scgd *cp++ = '\0'; 1808c996398Scgd 1818c996398Scgd /* skip spaces before any argument */ 1828c996398Scgd for ( /* cp = cp */ ; *cp == ' ' || *cp == '\t'; cp++) 1838c996398Scgd ; 1848c996398Scgd if (*cp == '\0') 1858c996398Scgd goto check_shell; 1868c996398Scgd 1878c996398Scgd /* 1888c996398Scgd * collect the shell argument. everything after the shell name 1898c996398Scgd * is passed as ONE argument; that's the correct (historical) 1908c996398Scgd * behaviour. 1918c996398Scgd */ 1928c996398Scgd shellarg = cp; 1938c996398Scgd for ( /* cp = cp */ ; *cp != '\0'; cp++) 1948c996398Scgd shellarglen++; 1958c996398Scgd *cp++ = '\0'; 1968c996398Scgd 1978c996398Scgd check_shell: 1988c996398Scgd #ifdef SETUIDSCRIPTS 1998c996398Scgd /* 2007660fd85Sthorpej * MNT_NOSUID has already taken care of by check_exec, 2017660fd85Sthorpej * so we don't need to worry about it now or later. We 202b07ec3fcSad * will need to check PSL_TRACED later, however. 2038c996398Scgd */ 204d7f33c5eSmycroft script_sbits = epp->ep_vap->va_mode & (S_ISUID | S_ISGID); 2058c996398Scgd if (script_sbits != 0) { 2068c996398Scgd script_uid = epp->ep_vap->va_uid; 2078c996398Scgd script_gid = epp->ep_vap->va_gid; 2088c996398Scgd } 2098c996398Scgd #endif 2108c996398Scgd #ifdef FDSCRIPTS 2118c996398Scgd /* 2128c996398Scgd * if the script isn't readable, or it's set-id, then we've 2138c996398Scgd * gotta supply a "/dev/fd/..." for the shell to read. 2148c996398Scgd * Note that stupid shells (csh) do the wrong thing, and 215293d113eSpgoyette * close all open fd's when they start. That kills this 2168c996398Scgd * method of implementing "safe" set-id and x-only scripts. 2178c996398Scgd */ 21832f28da6Sad vn_lock(epp->ep_vp, LK_SHARED | LK_RETRY); 2192780a147Spooka error = VOP_ACCESS(epp->ep_vp, VREAD, l->l_cred); 2201423e65bShannken VOP_UNLOCK(epp->ep_vp); 2213b34cba8Scgd if (error == EACCES 22254165a41Scgd #ifdef SETUIDSCRIPTS 22354165a41Scgd || script_sbits 22454165a41Scgd #endif 22554165a41Scgd ) { 2268c996398Scgd struct file *fp; 2278c996398Scgd 2285fa25b57Smaxv KASSERT(!(epp->ep_flags & EXEC_HASFD)); 2298c996398Scgd 230a9ca7a37Sad if ((error = fd_allocfile(&fp, &epp->ep_fd)) != 0) { 2312b0c0d95Schristos scriptvp = NULL; 2322b0c0d95Schristos shellargp = NULL; 233a790e23eScgd goto fail; 2342b0c0d95Schristos } 2358c996398Scgd epp->ep_flags |= EXEC_HASFD; 2368c996398Scgd fp->f_type = DTYPE_VNODE; 2378c996398Scgd fp->f_ops = &vnops; 23845b1ec74Smatt fp->f_vnode = epp->ep_vp; 2398c996398Scgd fp->f_flag = FREAD; 240a9ca7a37Sad fd_affix(curproc, fp, epp->ep_fd); 2418c996398Scgd } 2428c996398Scgd #endif 2438c996398Scgd 24454f6c52bSdholland /* set up the fake args list */ 24522d57011Syamt shellargp_len = 4 * sizeof(*shellargp); 24622d57011Syamt shellargp = kmem_alloc(shellargp_len, KM_SLEEP); 2478c996398Scgd tmpsap = shellargp; 24822d57011Syamt tmpsap->fa_len = shellnamelen + 1; 24922d57011Syamt tmpsap->fa_arg = kmem_alloc(tmpsap->fa_len, KM_SLEEP); 25022d57011Syamt strlcpy(tmpsap->fa_arg, shellname, tmpsap->fa_len); 25122d57011Syamt tmpsap++; 2528c996398Scgd if (shellarg != NULL) { 25322d57011Syamt tmpsap->fa_len = shellarglen + 1; 25422d57011Syamt tmpsap->fa_arg = kmem_alloc(tmpsap->fa_len, KM_SLEEP); 25522d57011Syamt strlcpy(tmpsap->fa_arg, shellarg, tmpsap->fa_len); 25622d57011Syamt tmpsap++; 2578c996398Scgd } 25822d57011Syamt tmpsap->fa_len = MAXPATHLEN; 25922d57011Syamt tmpsap->fa_arg = kmem_alloc(tmpsap->fa_len, KM_SLEEP); 2608c996398Scgd #ifdef FDSCRIPTS 2618c996398Scgd if ((epp->ep_flags & EXEC_HASFD) == 0) { 2628c996398Scgd #endif 2638c996398Scgd /* normally can't fail, but check for it if diagnostic */ 26454f6c52bSdholland error = copystr(epp->ep_kname, tmpsap->fa_arg, MAXPATHLEN, 2655fa25b57Smaxv NULL); 2665fa25b57Smaxv KASSERT(error == 0); 26722d57011Syamt tmpsap++; 2688c996398Scgd #ifdef FDSCRIPTS 26922d57011Syamt } else { 27022d57011Syamt snprintf(tmpsap->fa_arg, MAXPATHLEN, "/dev/fd/%d", epp->ep_fd); 27122d57011Syamt tmpsap++; 27222d57011Syamt } 2738c996398Scgd #endif 27422d57011Syamt tmpsap->fa_arg = NULL; 2758c996398Scgd 27654f6c52bSdholland /* Save the old vnode so we can clean it up later. */ 27754f6c52bSdholland scriptvp = epp->ep_vp; 27854f6c52bSdholland epp->ep_vp = NULL; 27954f6c52bSdholland 28054f6c52bSdholland /* Note that we're trying recursively. */ 28154f6c52bSdholland epp->ep_flags |= EXEC_INDIR; 28254f6c52bSdholland 2838c996398Scgd /* 2848c996398Scgd * mark the header we have as invalid; check_exec will read 2858c996398Scgd * the header from the new executable 2868c996398Scgd */ 2878c996398Scgd epp->ep_hdrvalid = 0; 2888c996398Scgd 28954f6c52bSdholland /* try loading the interpreter */ 290860a01d9Schristos if ((error = exec_makepathbuf(l, shellname, UIO_SYSSPACE, 291860a01d9Schristos &shell_pathbuf, NULL)) == 0) { 29209c169e2Schristos error = check_exec(l, epp, shell_pathbuf, NULL); 2938f6ed30dSdholland pathbuf_destroy(shell_pathbuf); 2948f6ed30dSdholland } 2958c996398Scgd 296a790e23eScgd /* note that we've clobbered the header */ 297b8fbaf8cSdsl epp->ep_flags |= EXEC_DESTR; 29854f6c52bSdholland 299b8fbaf8cSdsl if (error == 0) { 3008c996398Scgd /* 3018c996398Scgd * It succeeded. Unlock the script and 3028c996398Scgd * close it if we aren't using it any more. 303a790e23eScgd * Also, set things up so that the fake args 3048c996398Scgd * list will be used. 3058c996398Scgd */ 3063b34cba8Scgd if ((epp->ep_flags & EXEC_HASFD) == 0) { 3070078fc50Swrstuden vn_lock(scriptvp, LK_EXCLUSIVE | LK_RETRY); 30861e8303eSpooka VOP_CLOSE(scriptvp, FREAD, l->l_cred); 3090078fc50Swrstuden vput(scriptvp); 3103b34cba8Scgd } 3118c996398Scgd 3128c996398Scgd epp->ep_flags |= (EXEC_HASARGL | EXEC_SKIPARG); 3138c996398Scgd epp->ep_fa = shellargp; 31422d57011Syamt epp->ep_fa_len = shellargp_len; 3158c996398Scgd #ifdef SETUIDSCRIPTS 3168c996398Scgd /* 3178c996398Scgd * set thing up so that set-id scripts will be 318b07ec3fcSad * handled appropriately. PSL_TRACED will be 3197660fd85Sthorpej * checked later when the shell is actually 3207660fd85Sthorpej * exec'd. 3218c996398Scgd */ 3228c996398Scgd epp->ep_vap->va_mode |= script_sbits; 323d7f33c5eSmycroft if (script_sbits & S_ISUID) 3248c996398Scgd epp->ep_vap->va_uid = script_uid; 325d7f33c5eSmycroft if (script_sbits & S_ISGID) 3268c996398Scgd epp->ep_vap->va_gid = script_gid; 3278c996398Scgd #endif 328a790e23eScgd return (0); 329a790e23eScgd } 330a790e23eScgd 3318a5b1b92Schristos #ifdef FDSCRIPTS 332a790e23eScgd fail: 3338a5b1b92Schristos #endif 334a790e23eScgd 335a790e23eScgd /* kill the opened file descriptor, else close the file */ 336a790e23eScgd if (epp->ep_flags & EXEC_HASFD) { 337a790e23eScgd epp->ep_flags &= ~EXEC_HASFD; 338a9ca7a37Sad fd_close(epp->ep_fd); 3392b0c0d95Schristos } else if (scriptvp) { 3400078fc50Swrstuden vn_lock(scriptvp, LK_EXCLUSIVE | LK_RETRY); 34161e8303eSpooka VOP_CLOSE(scriptvp, FREAD, l->l_cred); 3420078fc50Swrstuden vput(scriptvp); 3433b34cba8Scgd } 344a790e23eScgd 3458c996398Scgd /* free the fake arg list, because we're not returning it */ 3462b0c0d95Schristos if ((tmpsap = shellargp) != NULL) { 34722d57011Syamt while (tmpsap->fa_arg != NULL) { 34822d57011Syamt kmem_free(tmpsap->fa_arg, tmpsap->fa_len); 3498c996398Scgd tmpsap++; 3508c996398Scgd } 35122d57011Syamt kmem_free(shellargp, shellargp_len); 3522b0c0d95Schristos } 353a790e23eScgd 354a790e23eScgd /* 355a790e23eScgd * free any vmspace-creation commands, 356a790e23eScgd * and release their references 357a790e23eScgd */ 358a790e23eScgd kill_vmcmds(&epp->ep_vmcmds); 3598c996398Scgd 3608c996398Scgd return error; 3618c996398Scgd } 362