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 }