xref: /illumos-gate/usr/src/uts/common/os/brand.c (revision 59f2ff5c)
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  * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
23  * Use is subject to license terms.
24  */
25 
26 #pragma ident	"%Z%%M%	%I%	%E% SMI"
27 
28 #include <sys/kmem.h>
29 #include <sys/errno.h>
30 #include <sys/systm.h>
31 #include <sys/cmn_err.h>
32 #include <sys/brand.h>
33 #include <sys/machbrand.h>
34 #include <sys/modctl.h>
35 #include <sys/rwlock.h>
36 #include <sys/zone.h>
37 
38 #define	SUPPORTED_BRAND_VERSION BRAND_VER_1
39 
40 #if defined(__sparcv9)
41 struct brand_mach_ops native_mach_ops  = {
42 		NULL, NULL
43 };
44 #else /* !__sparcv9 */
45 struct brand_mach_ops native_mach_ops  = {
46 		NULL, NULL, NULL, NULL, NULL, NULL
47 };
48 #endif /* !__sparcv9 */
49 
50 brand_t native_brand = {
51 		BRAND_VER_1,
52 		"native",
53 		NULL,
54 		&native_mach_ops
55 };
56 
57 /*
58  * Used to maintain a list of all the brands currently loaded into the
59  * kernel.
60  */
61 struct brand_list {
62 	int			bl_refcnt;
63 	struct brand_list	*bl_next;
64 	brand_t			*bl_brand;
65 };
66 
67 static struct brand_list *brand_list = NULL;
68 
69 /*
70  * Used to enable brand platform specific interposition code
71  */
72 #pragma	weak	brand_plat_interposition_init
73 extern void brand_plat_interposition_init(void);
74 
75 /*
76  * This lock protects the integrity of the brand list.
77  */
78 static kmutex_t brand_list_lock;
79 
80 void
81 brand_init()
82 {
83 	brand_plat_interposition_init();
84 	mutex_init(&brand_list_lock, NULL, MUTEX_DEFAULT, NULL);
85 	p0.p_brand = &native_brand;
86 }
87 
88 int
89 brand_register(brand_t *brand)
90 {
91 	struct brand_list *list, *scan;
92 
93 	if (brand == NULL)
94 		return (EINVAL);
95 
96 	if (is_system_labeled()) {
97 		cmn_err(CE_WARN,
98 		    "Branded zones are not allowed on labeled systems.");
99 		return (EINVAL);
100 	}
101 
102 	if (brand->b_version != SUPPORTED_BRAND_VERSION) {
103 		if (brand->b_version < SUPPORTED_BRAND_VERSION) {
104 			cmn_err(CE_WARN,
105 			    "brand '%s' was built to run on older versions "
106 			    "of Solaris.",
107 			    brand->b_name);
108 		} else {
109 			cmn_err(CE_WARN,
110 			    "brand '%s' was built to run on a newer version "
111 			    "of Solaris.",
112 			    brand->b_name);
113 		}
114 		return (EINVAL);
115 	}
116 
117 	/* Sanity checks */
118 	if (brand->b_name == NULL || brand->b_ops == NULL ||
119 	    brand->b_ops->b_brandsys == NULL) {
120 		cmn_err(CE_WARN, "Malformed brand");
121 		return (EINVAL);
122 	}
123 
124 	list = kmem_alloc(sizeof (struct brand_list), KM_SLEEP);
125 
126 	/* Add the brand to the list of loaded brands. */
127 	mutex_enter(&brand_list_lock);
128 
129 	/*
130 	 * Check to be sure we haven't already registered this brand.
131 	 */
132 	for (scan = brand_list; scan != NULL; scan = scan->bl_next) {
133 		if (strcmp(brand->b_name, scan->bl_brand->b_name) == 0) {
134 			cmn_err(CE_WARN,
135 			    "Invalid attempt to load a second instance of "
136 			    "brand %s", brand->b_name);
137 			mutex_exit(&brand_list_lock);
138 			kmem_free(list, sizeof (struct brand_list));
139 			return (EINVAL);
140 		}
141 	}
142 
143 	list->bl_brand = brand;
144 	list->bl_refcnt = 0;
145 	list->bl_next = brand_list;
146 	brand_list = list;
147 	mutex_exit(&brand_list_lock);
148 
149 	return (0);
150 }
151 
152 /*
153  * The kernel module implementing this brand is being unloaded, so remove
154  * it from the list of active brands.
155  */
156 int
157 brand_unregister(brand_t *brand)
158 {
159 	struct brand_list *list, *prev;
160 
161 	/* Sanity checks */
162 	if (brand == NULL || brand->b_name == NULL) {
163 		cmn_err(CE_WARN, "Malformed brand");
164 		return (EINVAL);
165 	}
166 
167 	prev = NULL;
168 	mutex_enter(&brand_list_lock);
169 
170 	for (list = brand_list; list != NULL; list = list->bl_next) {
171 		if (list->bl_brand == brand)
172 			break;
173 		prev = list;
174 	}
175 
176 	if (list == NULL) {
177 		cmn_err(CE_WARN, "Brand %s wasn't registered", brand->b_name);
178 		mutex_exit(&brand_list_lock);
179 		return (EINVAL);
180 	}
181 
182 	if (list->bl_refcnt > 0) {
183 		cmn_err(CE_WARN, "Unregistering brand %s which is still in use",
184 		    brand->b_name);
185 		mutex_exit(&brand_list_lock);
186 		return (EBUSY);
187 	}
188 
189 	/* Remove brand from the list */
190 	if (prev != NULL)
191 		prev->bl_next = list->bl_next;
192 	else
193 		brand_list = list->bl_next;
194 
195 	mutex_exit(&brand_list_lock);
196 
197 	kmem_free(list, sizeof (struct brand_list));
198 
199 	return (0);
200 }
201 
202 /*
203  * Record that a zone of this brand has been instantiated.  If the kernel
204  * module implementing this brand's functionality is not present, this
205  * routine attempts to load the module as a side effect.
206  */
207 brand_t *
208 brand_register_zone(struct brand_attr *attr)
209 {
210 	struct brand_list *l = NULL;
211 	ddi_modhandle_t	hdl = NULL;
212 	char *modname;
213 	int err = 0;
214 
215 	if (is_system_labeled()) {
216 		cmn_err(CE_WARN,
217 		    "Branded zones are not allowed on labeled systems.");
218 		return (NULL);
219 	}
220 
221 	/*
222 	 * We make at most two passes through this loop.  The first time
223 	 * through, we're looking to see if this is a new user of an
224 	 * already loaded brand.  If the brand hasn't been loaded, we
225 	 * call ddi_modopen() to force it to be loaded and then make a
226 	 * second pass through the list of brands.  If we don't find the
227 	 * brand the second time through it means that the modname
228 	 * specified in the brand_attr structure doesn't provide the brand
229 	 * specified in the brandname field.  This would suggest a bug in
230 	 * the brand's config.xml file.  We close the module and return
231 	 * 'NULL' to the caller.
232 	 */
233 	for (;;) {
234 		/*
235 		 * Search list of loaded brands
236 		 */
237 		mutex_enter(&brand_list_lock);
238 		for (l = brand_list; l != NULL; l = l->bl_next)
239 			if (strcmp(attr->ba_brandname,
240 			    l->bl_brand->b_name) == 0)
241 				break;
242 		if ((l != NULL) || (hdl != NULL))
243 			break;
244 		mutex_exit(&brand_list_lock);
245 
246 		/*
247 		 * We didn't find that the requested brand has been loaded
248 		 * yet, so we trigger the load of the appropriate kernel
249 		 * module and search the list again.
250 		 */
251 		modname = kmem_alloc(MAXPATHLEN, KM_SLEEP);
252 		(void) strcpy(modname, "brand/");
253 		(void) strcat(modname, attr->ba_modname);
254 		hdl = ddi_modopen(modname, KRTLD_MODE_FIRST, &err);
255 		kmem_free(modname, MAXPATHLEN);
256 
257 		if (err != 0)
258 			return (NULL);
259 	}
260 
261 	/*
262 	 * If we found the matching brand, bump its reference count.
263 	 */
264 	if (l != NULL)
265 		l->bl_refcnt++;
266 
267 	mutex_exit(&brand_list_lock);
268 
269 	if (hdl != NULL)
270 		(void) ddi_modclose(hdl);
271 
272 	return ((l != NULL) ? l->bl_brand : NULL);
273 }
274 
275 /*
276  * Return the number of zones currently using this brand.
277  */
278 int
279 brand_zone_count(struct brand *bp)
280 {
281 	struct brand_list *l;
282 	int cnt = 0;
283 
284 	mutex_enter(&brand_list_lock);
285 	for (l = brand_list; l != NULL; l = l->bl_next)
286 		if (l->bl_brand == bp) {
287 			cnt = l->bl_refcnt;
288 			break;
289 		}
290 	mutex_exit(&brand_list_lock);
291 
292 	return (cnt);
293 }
294 
295 void
296 brand_unregister_zone(struct brand *bp)
297 {
298 	struct brand_list *list;
299 
300 	mutex_enter(&brand_list_lock);
301 	for (list = brand_list; list != NULL; list = list->bl_next) {
302 		if (list->bl_brand == bp) {
303 			ASSERT(list->bl_refcnt > 0);
304 			list->bl_refcnt--;
305 			break;
306 		}
307 	}
308 	mutex_exit(&brand_list_lock);
309 }
310 
311 void
312 brand_setbrand(proc_t *p)
313 {
314 	brand_t *bp = p->p_zone->zone_brand;
315 
316 	ASSERT(bp != NULL);
317 	ASSERT(p->p_brand == &native_brand);
318 
319 	/*
320 	 * We should only be called from exec(), when we know the process
321 	 * is single-threaded.
322 	 */
323 	ASSERT(p->p_tlist == p->p_tlist->t_forw);
324 
325 	p->p_brand = bp;
326 	if (PROC_IS_BRANDED(p)) {
327 		BROP(p)->b_setbrand(p);
328 		lwp_attach_brand_hdlrs(p->p_tlist->t_lwp);
329 	}
330 }
331 
332 #if defined(__sparcv9)
333 /*
334  * Currently, only sparc has platform level brand syscall interposition.
335  * On x86 we're able to enable syscall interposition on a per-cpu basis
336  * when a branded thread is scheduled to run on a cpu.
337  */
338 
339 /* Local variables needed for dynamic syscall interposition support */
340 static kmutex_t	brand_interposition_lock;
341 static int	brand_interposition_count;
342 static uint32_t	syscall_trap_patch_instr_orig;
343 static uint32_t	syscall_trap32_patch_instr_orig;
344 
345 /* Trap Table syscall entry hot patch points */
346 extern void	syscall_trap_patch_point(void);
347 extern void	syscall_trap32_patch_point(void);
348 
349 /* Alternate syscall entry handlers used when branded zones are running */
350 extern void	syscall_wrapper(void);
351 extern void	syscall_wrapper32(void);
352 
353 /* Macros used to facilitate sparcv9 instruction generation */
354 #define	BA_A_INSTR	0x30800000	/* ba,a addr */
355 #define	DISP22(from, to) \
356 	((((uintptr_t)(to) - (uintptr_t)(from)) >> 2) & 0x3fffff)
357 
358 void
359 brand_plat_interposition_init(void)
360 {
361 	mutex_init(&brand_interposition_lock, NULL, MUTEX_DEFAULT, NULL);
362 	brand_interposition_count = 0;
363 }
364 
365 /*ARGSUSED*/
366 void
367 brand_plat_interposition_enable(brand_t *bp)
368 {
369 	ASSERT((bp != NULL) && (bp != &native_brand));
370 
371 	mutex_enter(&brand_interposition_lock);
372 	ASSERT(brand_interposition_count >= 0);
373 
374 	if (brand_interposition_count++ > 0) {
375 		mutex_exit(&brand_interposition_lock);
376 		return;
377 	}
378 
379 	/*
380 	 * This is the first branded zone that is being enabled on
381 	 * this system.
382 	 *
383 	 * Before we hot patch the kernel save the current instructions
384 	 * so that we can restore them if all branded zones on the
385 	 * system are shutdown.
386 	 */
387 	syscall_trap_patch_instr_orig =
388 	    *(uint32_t *)syscall_trap_patch_point;
389 	syscall_trap32_patch_instr_orig =
390 	    *(uint32_t *)syscall_trap32_patch_point;
391 
392 	/*
393 	 * Modify the trap table at the patch points.
394 	 *
395 	 * We basically replace the first instruction at the patch
396 	 * point with a ba,a instruction that will transfer control
397 	 * to syscall_wrapper or syscall_wrapper32 for 64-bit and
398 	 * 32-bit syscalls respectively.  It's important to note that
399 	 * the annul bit is set in the branch so we don't execute
400 	 * the instruction directly following the one we're patching
401 	 * during the branch's delay slot.
402 	 *
403 	 * It also doesn't matter that we're not atomically updating both
404 	 * the 64 and 32 bit syscall paths at the same time since there's
405 	 * no actual branded processes running on the system yet.
406 	 */
407 	hot_patch_kernel_text((caddr_t)syscall_trap_patch_point,
408 	    BA_A_INSTR | DISP22(syscall_trap_patch_point, syscall_wrapper),
409 	    4);
410 	hot_patch_kernel_text((caddr_t)syscall_trap32_patch_point,
411 	    BA_A_INSTR | DISP22(syscall_trap32_patch_point, syscall_wrapper32),
412 	    4);
413 
414 	mutex_exit(&brand_interposition_lock);
415 }
416 
417 /*ARGSUSED*/
418 void
419 brand_plat_interposition_disable(brand_t *bp)
420 {
421 	ASSERT((bp != NULL) && (bp != &native_brand));
422 
423 	mutex_enter(&brand_interposition_lock);
424 	ASSERT(brand_interposition_count > 0);
425 
426 	if (--brand_interposition_count > 0) {
427 		mutex_exit(&brand_interposition_lock);
428 		return;
429 	}
430 
431 	/*
432 	 * The last branded zone on this system has been shutdown.
433 	 *
434 	 * Restore the original instructions at the trap table syscall
435 	 * patch points to disable the brand syscall interposition
436 	 * mechanism.
437 	 */
438 	hot_patch_kernel_text((caddr_t)syscall_trap_patch_point,
439 	    syscall_trap_patch_instr_orig, 4);
440 	hot_patch_kernel_text((caddr_t)syscall_trap32_patch_point,
441 	    syscall_trap32_patch_instr_orig, 4);
442 
443 	mutex_exit(&brand_interposition_lock);
444 }
445 #endif /* __sparcv9 */
446