]> git.saurik.com Git - apple/xnu.git/blob - osfmk/kern/suid_cred.c
xnu-7195.101.1.tar.gz
[apple/xnu.git] / osfmk / kern / suid_cred.c
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_NONE);
73
74 /* Allocs a new suid credential. The vnode reference will be owned by the newly
75 * created suid_cred_t. */
76 static suid_cred_t
77 suid_cred_alloc(struct vnode *vnode, uint32_t uid)
78 {
79 suid_cred_t sc = SUID_CRED_NULL;
80
81 assert(vnode != NULL);
82
83 sc = zalloc(suid_cred_zone);
84 if (sc != NULL) {
85 // Lazily allocated in convert_suid_cred_to_port().
86 sc->port = IP_NULL;
87 sc->vnode = vnode;
88 sc->uid = uid;
89 }
90
91 return sc;
92 }
93
94 static void
95 suid_cred_free(suid_cred_t sc)
96 {
97 assert(sc != NULL);
98 assert(sc->vnode != NULL);
99
100 vnode_put(sc->vnode);
101
102 sc->uid = UINT32_MAX;
103 sc->vnode = NULL;
104 sc->port = IP_NULL;
105
106 zfree(suid_cred_zone, sc);
107 }
108
109 void
110 suid_cred_destroy(ipc_port_t port)
111 {
112 suid_cred_t sc = NULL;
113
114 ip_lock(port);
115 assert(ip_kotype(port) == IKOT_SUID_CRED);
116 sc = (suid_cred_t)ipc_kobject_get(port);
117 ipc_kobject_set_atomically(port, IKO_NULL, IKOT_NONE);
118 ip_unlock(port);
119
120 assert(sc->port == port);
121
122 suid_cred_free(sc);
123 }
124
125 void
126 suid_cred_notify(mach_msg_header_t *msg)
127 {
128 assert(msg->msgh_id == MACH_NOTIFY_NO_SENDERS);
129
130 mach_no_senders_notification_t *not = (mach_no_senders_notification_t *)msg;
131 ipc_port_t port = not->not_header.msgh_remote_port;
132
133 if (IP_VALID(port)) {
134 ipc_port_dealloc_kernel(port);
135 }
136 }
137
138 ipc_port_t
139 convert_suid_cred_to_port(suid_cred_t sc)
140 {
141 if (sc == NULL) {
142 return IP_NULL;
143 }
144
145 if (!ipc_kobject_make_send_lazy_alloc_port(&sc->port,
146 (ipc_kobject_t) sc, IKOT_SUID_CRED, IPC_KOBJECT_ALLOC_NONE, false, 0)) {
147 suid_cred_free(sc);
148 return IP_NULL;
149 }
150
151 return sc->port;
152 }
153
154 /*
155 * Verify the suid cred port. The cached vnode should match the passed vnode.
156 * The uid to be used to spawn the new process is returned in 'uid'.
157 */
158 int
159 suid_cred_verify(ipc_port_t port, struct vnode *vnode, uint32_t *uid)
160 {
161 suid_cred_t sc = NULL;
162 int ret = -1;
163
164 if (!IP_VALID(port)) {
165 return -1;
166 }
167
168 ip_lock(port);
169
170 if (ip_kotype(port) != IKOT_SUID_CRED) {
171 ip_unlock(port);
172 return -1;
173 }
174
175 if (!ip_active(port)) {
176 ip_unlock(port);
177 return -1;
178 }
179
180 sc = (suid_cred_t)ipc_kobject_get(port);
181
182 if (vnode != sc->vnode) {
183 ip_unlock(port);
184 return -1;
185 }
186
187 *uid = sc->uid;
188 ret = 0;
189
190 ipc_port_destroy(port);
191 return ret;
192 }
193
194 kern_return_t
195 task_create_suid_cred(
196 task_t task,
197 suid_cred_path_t path,
198 suid_cred_uid_t uid,
199 suid_cred_t *sc_p)
200 {
201 suid_cred_t sc = NULL;
202 struct vnode *vnode;
203 int err = -1;
204
205 if (task == TASK_NULL || task != current_task()) {
206 return KERN_INVALID_ARGUMENT;
207 }
208
209 // Task must have entitlement.
210 if (!IOTaskHasEntitlement(task, "com.apple.private.suid_cred")) {
211 return KERN_NO_ACCESS;
212 }
213
214 // Thread must be root owned.
215 if (!kauth_cred_issuser(kauth_cred_get())) {
216 return KERN_NO_ACCESS;
217 }
218
219 // Find the vnode for the path.
220 err = vnode_lookup(path, 0, &vnode, vfs_context_current());
221 if (err != 0) {
222 return KERN_INVALID_ARGUMENT;
223 }
224
225 sc = suid_cred_alloc(vnode, uid);
226 if (sc == NULL) {
227 (void) vnode_put(vnode);
228 return KERN_RESOURCE_SHORTAGE;
229 }
230
231 *sc_p = sc;
232
233 return KERN_SUCCESS;
234 }