1/*
2 * Copyright (c) 2000-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#include <mach/port.h>
30#include <mach/kern_return.h>
31#include <kern/ipc_tt.h>
32#include <ipc/ipc_port.h>
33#include <kern/zalloc.h>
34#include <kern/kalloc.h>
35#include <kern/mach_param.h>
36#include <mach/message.h>
37#include <kern/mach_filter.h>
38#include <ipc/ipc_service_port.h>
39#include <security/mac_mach_internal.h>
40
41#define XPC_DOMAIN_PORT 7 /* This value should match what is in <xpc/launch_private.h> */
42
43ZONE_DEFINE_TYPE(ipc_service_port_label_zone, "ipc_service_port_label",
44 struct ipc_service_port_label, ZC_ZFREE_CLEARMEM | ZC_NOCACHING);
45
46#if CONFIG_SERVICE_PORT_INFO
47const bool kdp_ipc_have_splabel = true;
48#else
49const bool kdp_ipc_have_splabel = false;
50#endif
51
52void
53kdp_ipc_splabel_size(size_t *ispl_size, size_t *maxnamelen)
54{
55 *ispl_size = sizeof(struct ipc_service_port_label);
56 *maxnamelen = MACH_SERVICE_PORT_INFO_STRING_NAME_MAX_BUF_LEN + 1;
57}
58
59void
60kdp_ipc_fill_splabel(struct ipc_service_port_label *ispl,
61 struct portlabel_info *spl, const char **namep)
62{
63#pragma unused(ispl, spl, namep)
64
65 /* validate that ispl is in our zone */
66#if CONFIG_SERVICE_PORT_INFO
67 *namep = ispl->ispl_service_name;
68 spl->portlabel_domain = ispl->ispl_domain;
69 if (ipc_service_port_label_is_throttled(ispl)) {
70 spl->portlabel_flags |= STACKSHOT_PORTLABEL_THROTTLED;
71 }
72#endif
73}
74
75/*
76 * Name: ipc_service_port_label_alloc
77 *
78 * Description: Allocates the service port label
79 *
80 * Args:
81 * sp_info: service port string name, length, domain information
82 * send_side_filtering: indicates if the messages should be filtered during mach_msg_send
83 * port_label_ptr: used to return the allocated service_port_label
84 *
85 * Returns:
86 * KERN_SUCCESS
87 */
88kern_return_t
89ipc_service_port_label_alloc(mach_service_port_info_t sp_info, void **port_label_ptr)
90{
91 ipc_service_port_label_t sp_label = IPC_SERVICE_PORT_LABEL_NULL;
92 kern_return_t ret;
93 void *sblabel = NULL;
94
95 sp_label = zalloc(zone: ipc_service_port_label_zone);
96
97 if (mach_msg_filter_alloc_service_port_sblabel_callback) {
98 ret = mach_msg_filter_alloc_service_port_sblabel_callback(sp_info, &sblabel);
99 if (ret) {
100 zfree(ipc_service_port_label_zone, sp_label);
101 return ret;
102 }
103 }
104
105 sp_label->ispl_sblabel = sblabel;
106#if CONFIG_SERVICE_PORT_INFO
107 size_t sp_string_name_len = strlen(s: sp_info->mspi_string_name);
108 /* We could investigate compressing the names, but it doesn't seem worth it */
109 sp_label->ispl_service_name = kalloc_data(sp_string_name_len + 1, Z_WAITOK);
110 strlcpy(dst: sp_label->ispl_service_name, src: sp_info->mspi_string_name, n: sp_string_name_len + 1);
111 sp_label->ispl_domain = sp_info->mspi_domain_type;
112#endif /* CONFIG_SERVICE_PORT_INFO */
113
114 if (sp_info->mspi_domain_type == XPC_DOMAIN_PORT) {
115 sp_label->ispl_flags |= ISPL_FLAGS_BOOTSTRAP_PORT;
116 }
117 *port_label_ptr = (void *)sp_label;
118 return KERN_SUCCESS;
119}
120
121/*
122 * Name: ipc_service_port_dealloc
123 *
124 * Description: Deallocates the service port label
125 *
126 * Args:
127 * ip_splabel: port's ip_splabel
128 *
129 * Returns: None
130 *
131 * Should not be called with the port lock held.
132 */
133void
134ipc_service_port_label_dealloc(void *ip_splabel, bool service_port)
135{
136 void *sblabel = ip_splabel;
137
138 if (service_port) {
139 ipc_service_port_label_t sp_label = (ipc_service_port_label_t)ip_splabel;
140 sblabel = sp_label->ispl_sblabel;
141#if CONFIG_SERVICE_PORT_INFO
142 kfree_data(sp_label->ispl_service_name, strlen(sp_label->ispl_service_name) + 1);
143#endif /* CONFIG_SERVICE_PORT_INFO */
144 zfree(ipc_service_port_label_zone, sp_label);
145 }
146
147 if (sblabel) {
148 assert(mach_msg_filter_dealloc_service_port_sblabel_callback);
149 mach_msg_filter_dealloc_service_port_sblabel_callback(sblabel);
150 }
151}
152
153/*
154 * Name: ipc_service_port_derive_sblabel
155 *
156 * Description: Derive the port's sandbox label using info from the service port's label
157 *
158 * Args:
159 * service_port_name: send right to a service port
160 * sblabel_ptr: used to return the allocated sblabel
161 *
162 * Returns:
163 * KERN_SUCCESS
164 * KERN_INVALID_NAME: service_port_name is mach_port_null or mach_port_dead
165 * KERN_INVALID_RIGHT: service_port_name is not a send right
166 * KERN_INVALID_CAPABILITY: service_port_name is not a right to a service port
167 */
168kern_return_t
169ipc_service_port_derive_sblabel(mach_port_name_t service_port_name, void **sblabel_ptr, bool *filter_msgs)
170{
171 ipc_service_port_label_t port_label;
172 void *derived_sblabel = NULL;
173 void *sblabel = NULL;
174 ipc_port_t port;
175 kern_return_t kr;
176 boolean_t send_side_filtering = FALSE;
177#if CONFIG_MACF && XNU_TARGET_OS_OSX
178 struct mach_service_port_info sp_info = {};
179#endif
180
181 if (!MACH_PORT_VALID(service_port_name)) {
182 return KERN_INVALID_NAME;
183 }
184
185 if (mach_msg_filter_at_least(MACH_MSG_FILTER_CALLBACKS_VERSION_1)) {
186 kr = ipc_port_translate_send(current_space(), name: service_port_name, portp: &port);
187 if (kr != KERN_SUCCESS) {
188 return kr;
189 }
190 /* port is locked and active */
191
192 if (ip_is_kolabeled(port) || !port->ip_service_port) {
193 ip_mq_unlock(port);
194 return KERN_INVALID_CAPABILITY;
195 }
196
197 port_label = (ipc_service_port_label_t)port->ip_splabel;
198 if (!port_label) {
199 ip_mq_unlock(port);
200 return KERN_SUCCESS;
201 }
202
203#if CONFIG_MACF && XNU_TARGET_OS_OSX
204 ipc_service_port_label_get_info(port_splabel: port_label, info: &sp_info);
205#endif
206
207 sblabel = port_label->ispl_sblabel;
208 if (sblabel) {
209 mach_msg_filter_retain_sblabel_callback(sblabel);
210 }
211 ip_mq_unlock(port);
212
213 if (sblabel) {
214 /* This callback will release the reference on sblabel */
215 derived_sblabel = mach_msg_filter_derive_sblabel_from_service_port_callback(sblabel, &send_side_filtering);
216 }
217
218#if CONFIG_MACF && XNU_TARGET_OS_OSX
219 if (sp_info.mspi_string_name[0] != '\0') {
220 mac_proc_notify_service_port_derive(sp_info: &sp_info);
221 }
222#endif
223 }
224
225 *sblabel_ptr = derived_sblabel;
226 *filter_msgs = (bool)send_side_filtering;
227 return KERN_SUCCESS;
228}
229
230/*
231 * Name: ipc_service_port_get_sblabel
232 *
233 * Description: Get the port's sandbox label.
234 *
235 * Args:
236 * port
237 *
238 * Conditions:
239 * Should be called on an active port with the lock held.
240 *
241 * Returns:
242 * Sandbox label
243 */
244void *
245ipc_service_port_get_sblabel(ipc_port_t port)
246{
247 void *sblabel = NULL;
248 void *ip_splabel = NULL;
249
250 if (port == IP_NULL) {
251 return NULL;
252 }
253
254 ip_mq_lock_held(port);
255 assert(ip_active(port));
256
257 if (ip_is_kolabeled(port) || !port->ip_splabel) {
258 return NULL;
259 }
260
261 ip_splabel = port->ip_splabel;
262
263 if (!port->ip_service_port) {
264 sblabel = ip_splabel;
265 assert(sblabel != NULL);
266 } else {
267 ipc_service_port_label_t sp_label = (ipc_service_port_label_t)ip_splabel;
268 sblabel = sp_label->ispl_sblabel;
269 }
270
271 return sblabel;
272}
273
274/*
275 * Name: ipc_service_port_label_set_attr
276 *
277 * Description: Set the remaining port label attributes after port allocation
278 *
279 * Args:
280 * port_splabel
281 * name : port name in launchd's ipc space
282 * context : launchd's port guard; will be restored after a port destroyed notification if non-zero
283 *
284 * Conditions:
285 * Should be called only once in mach_port_construct on a newly created port with the lock held
286 * The context should be set only if the port is guarded.
287 */
288void
289ipc_service_port_label_set_attr(ipc_service_port_label_t port_splabel, mach_port_name_t name, mach_port_context_t context)
290{
291 assert(port_splabel->ispl_launchd_name == MACH_PORT_NULL);
292 port_splabel->ispl_launchd_name = name;
293 port_splabel->ispl_launchd_context = context;
294 if (context) {
295 ipc_service_port_label_set_flag(port_splabel, flag: ISPL_FLAGS_SPECIAL_PDREQUEST);
296 }
297}
298
299/*
300 * Name: ipc_service_port_label_get_attr
301 *
302 * Description: Get the port label attributes
303 *
304 * Args:
305 * port_splabel
306 * name : port name in launchd's ipc space
307 * context : launchd's port guard
308 *
309 * Conditions:
310 * Should be called with port lock held.
311 */
312void
313ipc_service_port_label_get_attr(ipc_service_port_label_t port_splabel, mach_port_name_t *name, mach_port_context_t *context)
314{
315 *name = port_splabel->ispl_launchd_name;
316 *context = port_splabel->ispl_launchd_context;
317}
318
319#if CONFIG_SERVICE_PORT_INFO
320void
321ipc_service_port_label_get_info(ipc_service_port_label_t port_splabel, mach_service_port_info_t info)
322{
323 info->mspi_domain_type = (uint8_t)port_splabel->ispl_domain;
324 size_t sp_string_name_len = strlen(s: port_splabel->ispl_service_name);
325 strlcpy(dst: info->mspi_string_name, src: port_splabel->ispl_service_name, n: sp_string_name_len + 1);
326}
327#endif /* CONFIG_SERVICE_PORT_INFO */
328