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