/ duct-tape / xnu / osfmk / kern / suid_cred.c
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  }