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