xref: /xnu-8019.80.24/osfmk/kern/suid_cred.c (revision a325d9c4a84054e40bbe985afedcb50ab80993ea)
1 /*
2  * Copyright (c) 2019-2020 Apple Inc. All rights reserved.
3  *
4  * @APPLE_OSREFERENCE_LICENSE_HEADER_START@
5  *
6  * This file contains Original Code and/or Modifications of Original Code
7  * as defined in and that are subject to the Apple Public Source License
8  * Version 2.0 (the 'License'). You may not use this file except in
9  * compliance with the License. The rights granted to you under the License
10  * may not be used to create, or enable the creation or redistribution of,
11  * unlawful or unlicensed copies of an Apple operating system, or to
12  * circumvent, violate, or enable the circumvention or violation of, any
13  * terms of an Apple operating system software license agreement.
14  *
15  * Please obtain a copy of the License at
16  * http://www.opensource.apple.com/apsl/ and read it before using this file.
17  *
18  * The Original Code and all software distributed under the License are
19  * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
20  * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
21  * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
22  * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
23  * Please see the License for the specific language governing rights and
24  * limitations under the License.
25  *
26  * @APPLE_OSREFERENCE_LICENSE_HEADER_END@
27  */
28 
29 /*
30  *
31  * An SUID credential is a port type which allows a process to create a new
32  * process with a specific user id. It provides an alternative means to acheive
33  * this to the more traditional SUID bit file permission.
34  *
35  * To create a new SUID credential the process must be running as root and must
36  * have a special entitlement. When created, the credential is associated with a
37  * specific vnode and UID so the unprivileged owner of the credential may only
38  * create a new process from the file associated with that vnode and the
39  * resulting effective UID will be that of the UID in the credential.
40  */
41 
42 #include <kern/ipc_kobject.h>
43 #include <kern/queue.h>
44 #include <kern/suid_cred.h>
45 
46 #include <mach/mach_types.h>
47 #include <mach/task.h>
48 
49 #include <IOKit/IOBSD.h>
50 
51 /* Declarations necessary to call vnode_lookup()/vnode_put(). */
52 struct vnode;
53 struct vfs_context;
54 extern int vnode_lookup(const char *, int, struct vnode **,
55     struct vfs_context *);
56 extern struct vfs_context * vfs_context_current(void);
57 extern int vnode_put(struct vnode *);
58 
59 /* Declarations necessary to call kauth_cred_issuser(). */
60 struct ucred;
61 extern int kauth_cred_issuser(struct ucred *);
62 extern struct ucred *kauth_cred_get(void);
63 
64 /* Data associated with the suid cred port. Consumed during posix_spawn(). */
65 struct suid_cred {
66 	ipc_port_t port;
67 	struct vnode *vnode;
68 	uint32_t uid;
69 };
70 
71 static ZONE_DECLARE(suid_cred_zone, "suid_cred",
72     sizeof(struct suid_cred), ZC_ZFREE_CLEARMEM);
73 
74 static void suid_cred_no_senders(ipc_port_t port, mach_port_mscount_t mscount);
75 static void suid_cred_destroy(ipc_port_t port);
76 
77 IPC_KOBJECT_DEFINE(IKOT_SUID_CRED,
78     .iko_op_no_senders = suid_cred_no_senders,
79     .iko_op_destroy    = suid_cred_destroy);
80 
81 
82 /* Allocs a new suid credential. The vnode reference will be owned by the newly
83  * created suid_cred_t. */
84 static suid_cred_t
suid_cred_alloc(struct vnode * vnode,uint32_t uid)85 suid_cred_alloc(struct vnode *vnode, uint32_t uid)
86 {
87 	suid_cred_t sc;
88 
89 	assert(vnode != NULL);
90 
91 	sc = zalloc_flags(suid_cred_zone, Z_WAITOK | Z_ZERO | Z_NOFAIL);
92 	sc->vnode = vnode;
93 	sc->uid = uid;
94 	sc->port = ipc_kobject_alloc_port(sc, IKOT_SUID_CRED,
95 	    IPC_KOBJECT_ALLOC_MAKE_SEND | IPC_KOBJECT_ALLOC_NSREQUEST);
96 	return sc;
97 }
98 
99 static void
suid_cred_free(suid_cred_t sc)100 suid_cred_free(suid_cred_t sc)
101 {
102 	vnode_put(sc->vnode);
103 	zfree(suid_cred_zone, sc);
104 }
105 
106 static void
suid_cred_destroy(ipc_port_t port)107 suid_cred_destroy(ipc_port_t port)
108 {
109 	suid_cred_t sc = ipc_kobject_disable(port, IKOT_SUID_CRED);
110 
111 	assert(sc->port == port);
112 
113 	suid_cred_free(sc);
114 }
115 
116 static void
suid_cred_no_senders(ipc_port_t port,mach_port_mscount_t mscount)117 suid_cred_no_senders(ipc_port_t port, mach_port_mscount_t mscount)
118 {
119 	ipc_kobject_dealloc_port(port, mscount, IKOT_SUID_CRED);
120 }
121 
122 ipc_port_t
convert_suid_cred_to_port(suid_cred_t sc)123 convert_suid_cred_to_port(suid_cred_t sc)
124 {
125 	if (sc) {
126 		zone_require(suid_cred_zone, sc);
127 		return sc->port;
128 	}
129 	return IP_NULL;
130 }
131 
132 /*
133  * Verify the suid cred port. The cached vnode should match the passed vnode.
134  * The uid to be used to spawn the new process is returned in 'uid'.
135  */
136 int
suid_cred_verify(ipc_port_t port,struct vnode * vnode,uint32_t * uid)137 suid_cred_verify(ipc_port_t port, struct vnode *vnode, uint32_t *uid)
138 {
139 	suid_cred_t sc;
140 
141 	if (!IP_VALID(port)) {
142 		return -1;
143 	}
144 
145 	ip_mq_lock(port);
146 	sc = ipc_kobject_get_locked(port, IKOT_SUID_CRED);
147 
148 	if (sc && sc->vnode == vnode) {
149 		*uid = sc->uid;
150 		ipc_port_destroy(port);
151 		/* port unlocked */
152 		return 0;
153 	}
154 
155 	ip_mq_unlock(port);
156 	return -1;
157 }
158 
159 kern_return_t
task_create_suid_cred(task_t task,suid_cred_path_t path,suid_cred_uid_t uid,suid_cred_t * sc_p)160 task_create_suid_cred(
161 	task_t task,
162 	suid_cred_path_t path,
163 	suid_cred_uid_t uid,
164 	suid_cred_t *sc_p)
165 {
166 	struct vnode *vnode;
167 	int  err = -1;
168 
169 	if (task == TASK_NULL || task != current_task()) {
170 		return KERN_INVALID_ARGUMENT;
171 	}
172 
173 	// Task must have entitlement.
174 	if (!IOTaskHasEntitlement(task, "com.apple.private.suid_cred")) {
175 		return KERN_NO_ACCESS;
176 	}
177 
178 	// Thread must be root owned.
179 	if (!kauth_cred_issuser(kauth_cred_get())) {
180 		return KERN_NO_ACCESS;
181 	}
182 
183 	// Find the vnode for the path.
184 	err = vnode_lookup(path, 0, &vnode, vfs_context_current());
185 	if (err != 0) {
186 		return KERN_INVALID_ARGUMENT;
187 	}
188 
189 	*sc_p = suid_cred_alloc(vnode, uid);
190 	return KERN_SUCCESS;
191 }
192