xref: /illumos-gate/usr/src/uts/common/exec/shbin/shbin.c (revision 1fa2a66491e7d8ae0be84e7da4da8e812480c710)
1 /*
2  * CDDL HEADER START
3  *
4  * The contents of this file are subject to the terms of the
5  * Common Development and Distribution License (the "License").
6  * You may not use this file except in compliance with the License.
7  *
8  * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
9  * or http://www.opensolaris.org/os/licensing.
10  * See the License for the specific language governing permissions
11  * and limitations under the License.
12  *
13  * When distributing Covered Code, include this CDDL HEADER in each
14  * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
15  * If applicable, add the following below this CDDL HEADER, with the
16  * fields enclosed by brackets "[]" replaced with your own identifying
17  * information: Portions Copyright [yyyy] [name of copyright owner]
18  *
19  * CDDL HEADER END
20  */
21 
22 /*
23  * Copyright 2010 Sun Microsystems, Inc.  All rights reserved.
24  * Use is subject to license terms.
25  */
26 
27 #include <sys/types.h>
28 #include <sys/param.h>
29 #include <sys/sysmacros.h>
30 #include <sys/signal.h>
31 #include <sys/cred.h>
32 #include <sys/user.h>
33 #include <sys/errno.h>
34 #include <sys/vnode.h>
35 #include <sys/proc.h>
36 #include <sys/cmn_err.h>
37 #include <sys/debug.h>
38 #include <sys/pathname.h>
39 #include <sys/disp.h>
40 #include <sys/exec.h>
41 #include <sys/kmem.h>
42 #include <sys/note.h>
43 
44 /*
45  * This is the loadable module wrapper.
46  */
47 #include <sys/modctl.h>
48 
49 /* Local prototypes */
50 static int
51 shbinexec(
52 	struct vnode *vp,
53 	struct execa *uap,
54 	struct uarg *args,
55 	struct intpdata *idatap,
56 	int level,
57 	long *execsz,
58 	int setid,
59 	caddr_t exec_file,
60 	struct cred *cred,
61 	int brand_action);
62 
63 #define	SHBIN_CNTL(x)	((x)&037)
64 #define	SHBINMAGIC_LEN	4
65 extern char shbinmagicstr[];
66 
67 /*
68  * Our list where we may find a copy of ksh93. The ordering is:
69  * 1. 64bit (may not be installed or not supported in hardware)
70  * 2. 32bit
71  * 3. Use /sbin/ksh93 when /usr is not available
72  *
73  * ([1] and [2] explicitly bypass /usr/bin/ksh93 to avoid the
74  * isaexec overhead).
75  */
76 static char *shell_list[] =
77 {
78 /* Bypass /usr/bin/ksh93 (which is "isaexec") for performance */
79 #if defined(__sparc)
80 	"/usr/bin/sparcv9/ksh93",
81 	"/usr/bin/sparcv7/ksh93",
82 #elif defined(__x86)
83 	"/usr/bin/amd64/ksh93",
84 	"/usr/bin/i86/ksh93",
85 #else
86 #error "Unrecognized platform/CPU (use /usr/bin/ksh93 when in doubt)."
87 #endif
88 	"/sbin/ksh93",
89 	NULL
90 };
91 
92 static struct execsw esw = {
93 	shbinmagicstr,
94 	0,
95 	SHBINMAGIC_LEN,
96 	shbinexec,
97 	NULL
98 };
99 
100 /*
101  * Module linkage information for the kernel.
102  */
103 extern struct mod_ops mod_execops;
104 
105 static struct modlexec modlexec = {
106 	&mod_execops, "exec mod for shell binaries (ksh93)", &esw
107 };
108 
109 static struct modlinkage modlinkage = {
110 	MODREV_1, (void *)&modlexec, NULL
111 };
112 
113 int
114 _init(void)
115 {
116 	return (mod_install(&modlinkage));
117 }
118 
119 int
120 _fini(void)
121 {
122 	return (mod_remove(&modlinkage));
123 }
124 
125 int
126 _info(struct modinfo *modinfop)
127 {
128 	return (mod_info(&modlinkage, modinfop));
129 }
130 
131 static int
132 checkshbinmagic(struct vnode *vp)
133 {
134 	int error;
135 	char linep[SHBINMAGIC_LEN];
136 	ssize_t resid;
137 
138 	/*
139 	 * Read the entire line and confirm that it starts with the magic
140 	 * sequence for compiled ksh93 shell scripts.
141 	 */
142 	if (error = vn_rdwr(UIO_READ, vp, linep, sizeof (linep), (offset_t)0,
143 	    UIO_SYSSPACE, 0, (rlim64_t)0, CRED(), &resid))
144 		return (error);
145 
146 	if (memcmp(linep, shbinmagicstr, SHBINMAGIC_LEN) != 0)
147 		return (ENOEXEC);
148 
149 	return (0);
150 }
151 
152 static int
153 shbinexec(
154 	struct vnode *vp,
155 	struct execa *uap,
156 	struct uarg *args,
157 	struct intpdata *idatap,
158 	int level,
159 	long *execsz,
160 	int setid,
161 	caddr_t exec_file,
162 	struct cred *cred,
163 	int brand_action)
164 {
165 	_NOTE(ARGUNUSED(brand_action))
166 	vnode_t *nvp;
167 	int error = 0;
168 	struct intpdata idata;
169 	struct pathname intppn;
170 	struct pathname resolvepn;
171 	char *opath;
172 	char devfd[19]; /* 32-bit int fits in 10 digits + 8 for "/dev/fd/" */
173 	int fd = -1;
174 	int i;
175 
176 	if (level) {		/* Can't recurse */
177 		error = ENOEXEC;
178 		goto bad;
179 	}
180 
181 	ASSERT(idatap == (struct intpdata *)NULL);
182 
183 	/*
184 	 * Check whether the executable has the correct magic value.
185 	 */
186 	if (error = checkshbinmagic(vp))
187 		goto fail;
188 
189 	pn_alloc(&resolvepn);
190 
191 	/*
192 	 * Travel the list of shells and look for one which is available...
193 	 */
194 	for (i = 0; shell_list[i] != NULL; i++) {
195 		error = pn_get(shell_list[i], UIO_SYSSPACE, &intppn);
196 		if (error != 0) {
197 			break;
198 		}
199 
200 		error = lookuppn(&intppn, &resolvepn, FOLLOW, NULLVPP, &nvp);
201 		if (!error) {
202 			/* Found match */
203 			break;
204 		}
205 
206 		/* No match found ? Then continue with the next item... */
207 		pn_free(&intppn);
208 	}
209 
210 	if (error) {
211 		pn_free(&resolvepn);
212 		goto fail;
213 	}
214 
215 	/*
216 	 * Set up interpreter data
217 	 * "--" is passed to mark the end-of-arguments before adding
218 	 * the script's file name, preventing problems when a
219 	 * script's name starts with a '-' character.
220 	 */
221 	bzero(&idata, sizeof (intpdata_t));
222 	idata.intp = NULL;
223 	idata.intp_name[0] = shell_list[i];
224 	idata.intp_arg[0] = "--";
225 
226 	opath = args->pathname;
227 	args->pathname = resolvepn.pn_path;
228 	/* don't free resolvepn until we are done with args */
229 	pn_free(&intppn);
230 
231 	/*
232 	 * When we're executing a set-uid script resulting in uids
233 	 * mismatching or when we execute with additional privileges,
234 	 * we close the "replace script between exec and open by shell"
235 	 * hole by passing the script as /dev/fd parameter.
236 	 */
237 	if ((setid & EXECSETID_PRIVS) != 0 ||
238 	    (setid & (EXECSETID_UGIDS|EXECSETID_SETID)) ==
239 	    (EXECSETID_UGIDS|EXECSETID_SETID)) {
240 		(void) strcpy(devfd, "/dev/fd/");
241 		if (error = execopen(&vp, &fd))
242 			goto done;
243 		numtos(fd, &devfd[8]);
244 		args->fname = devfd;
245 	}
246 
247 	error = gexec(&nvp, uap, args, &idata, ++level, execsz, exec_file, cred,
248 	    EBA_NONE);
249 
250 	if (!error) {
251 		/*
252 		 * Close this script as the sh interpreter
253 		 * will open and close it later on.
254 		 */
255 		(void) VOP_CLOSE(vp, FREAD, 1, (offset_t)0, cred, NULL);
256 	}
257 done:
258 	VN_RELE(nvp);
259 	args->pathname = opath;
260 	pn_free(&resolvepn);
261 fail:
262 	if (error && fd != -1)
263 		(void) execclose(fd);
264 bad:
265 	return (error);
266 }
267