1 /* 2 * CDDL HEADER START 3 * 4 * The contents of this file are subject to the terms of the 5 * Common Development and Distribution License (the "License"). 6 * You may not use this file except in compliance with the License. 7 * 8 * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE 9 * or http://www.opensolaris.org/os/licensing. 10 * See the License for the specific language governing permissions 11 * and limitations under the License. 12 * 13 * When distributing Covered Code, include this CDDL HEADER in each 14 * file and include the License file at usr/src/OPENSOLARIS.LICENSE. 15 * If applicable, add the following below this CDDL HEADER, with the 16 * fields enclosed by brackets "[]" replaced with your own identifying 17 * information: Portions Copyright [yyyy] [name of copyright owner] 18 * 19 * CDDL HEADER END 20 */ 21 /* 22 * Copyright 2007 Sun Microsystems, Inc. All rights reserved. 23 * Use is subject to license terms. 24 */ 25 26 #pragma ident "%Z%%M% %I% %E% SMI" 27 28 #include <sys/types.h> 29 #include <sys/param.h> 30 #include <sys/time.h> 31 #include <sys/cred.h> 32 #include <sys/vfs.h> 33 #include <sys/vfs_opreg.h> 34 #include <sys/gfs.h> 35 #include <sys/vnode.h> 36 #include <sys/systm.h> 37 #include <sys/errno.h> 38 #include <sys/sysmacros.h> 39 #include <fs/fs_subr.h> 40 #include <sys/contract.h> 41 #include <sys/contract_impl.h> 42 #include <sys/ctfs.h> 43 #include <sys/ctfs_impl.h> 44 #include <sys/file.h> 45 #include <sys/policy.h> 46 47 /* 48 * CTFS routines for the /system/contract/<type>/bundle vnode. 49 * CTFS routines for the /system/contract/<type>/pbundle vnode. 50 * CTFS routines for the /system/contract/<type>/<ctid>/events vnode. 51 */ 52 53 /* 54 * ctfs_endpoint_open 55 * 56 * Called by the VOP_OPEN entry points to perform some common checks 57 * and set up the endpoint listener, if not already done. 58 */ 59 static int 60 ctfs_endpoint_open(ctfs_endpoint_t *endpt, ct_equeue_t *q, int flag) 61 { 62 if ((flag & ~FNONBLOCK) != (FREAD | FOFFMAX)) 63 return (EINVAL); 64 65 mutex_enter(&endpt->ctfs_endpt_lock); 66 if ((endpt->ctfs_endpt_flags & CTFS_ENDPT_SETUP) == 0) { 67 endpt->ctfs_endpt_flags |= CTFS_ENDPT_SETUP; 68 if (flag & FNONBLOCK) 69 endpt->ctfs_endpt_flags |= CTFS_ENDPT_NBLOCK; 70 cte_add_listener(q, &endpt->ctfs_endpt_listener); 71 } 72 mutex_exit(&endpt->ctfs_endpt_lock); 73 74 return (0); 75 } 76 77 /* 78 * ctfs_endpoint inactive 79 * 80 * Called by the VOP_INACTIVE entry points to perform common listener 81 * cleanup. 82 */ 83 static void 84 ctfs_endpoint_inactive(ctfs_endpoint_t *endpt) 85 { 86 mutex_enter(&endpt->ctfs_endpt_lock); 87 if (endpt->ctfs_endpt_flags & CTFS_ENDPT_SETUP) { 88 endpt->ctfs_endpt_flags = 0; 89 cte_remove_listener(&endpt->ctfs_endpt_listener); 90 } 91 mutex_exit(&endpt->ctfs_endpt_lock); 92 } 93 94 /* 95 * ctfs_endpoint_ioctl 96 * 97 * Implements the common VOP_IOCTL handling for the event endpoints. 98 * rprivchk, if true, indicates that event receive requests should 99 * check the provided credentials. This distinction exists because 100 * contract endpoints perform their privilege checks at open-time, and 101 * process bundle queue listeners by definition may view all events 102 * their queues contain. 103 */ 104 static int 105 ctfs_endpoint_ioctl(ctfs_endpoint_t *endpt, int cmd, intptr_t arg, cred_t *cr, 106 zone_t *zone, int rprivchk) 107 { 108 uint64_t id, zuniqid; 109 110 zuniqid = zone->zone_uniqid; 111 112 switch (cmd) { 113 case CT_ERESET: 114 cte_reset_listener(&endpt->ctfs_endpt_listener); 115 break; 116 case CT_ERECV: 117 /* 118 * We pass in NULL for the cred when reading from 119 * process bundle queues and contract queues because 120 * the privilege check was performed at open time. 121 */ 122 return (cte_get_event(&endpt->ctfs_endpt_listener, 123 endpt->ctfs_endpt_flags & CTFS_ENDPT_NBLOCK, 124 (void *)arg, rprivchk ? cr : NULL, zuniqid, 0)); 125 case CT_ECRECV: 126 return (cte_get_event(&endpt->ctfs_endpt_listener, 127 endpt->ctfs_endpt_flags & CTFS_ENDPT_NBLOCK, 128 (void *)arg, rprivchk ? cr : NULL, zuniqid, 1)); 129 case CT_ENEXT: 130 if (copyin((void *)arg, &id, sizeof (uint64_t))) 131 return (EFAULT); 132 return (cte_next_event(&endpt->ctfs_endpt_listener, id)); 133 case CT_ERELIABLE: 134 return (cte_set_reliable(&endpt->ctfs_endpt_listener, cr)); 135 default: 136 return (EINVAL); 137 } 138 139 return (0); 140 } 141 142 /* 143 * ctfs_endpoint_poll 144 * 145 * Called by the VOP_POLL entry points. 146 */ 147 static int 148 ctfs_endpoint_poll(ctfs_endpoint_t *endpt, short events, int anyyet, 149 short *reventsp, pollhead_t **php) 150 { 151 if ((events & POLLIN) && endpt->ctfs_endpt_listener.ctl_position) { 152 *reventsp = POLLIN; 153 } else { 154 *reventsp = 0; 155 if (!anyyet) 156 *php = &endpt->ctfs_endpt_listener.ctl_pollhead; 157 } 158 159 return (0); 160 } 161 162 /* 163 * ctfs_create_evnode 164 * 165 * Creates and returns a new evnode. 166 */ 167 vnode_t * 168 ctfs_create_evnode(vnode_t *pvp) 169 { 170 vnode_t *vp; 171 ctfs_evnode_t *evnode; 172 ctfs_cdirnode_t *cdirnode = pvp->v_data; 173 174 vp = gfs_file_create(sizeof (ctfs_evnode_t), pvp, ctfs_ops_event); 175 evnode = vp->v_data; 176 177 /* 178 * We transitively have a hold on the contract through our 179 * parent directory. 180 */ 181 evnode->ctfs_ev_contract = cdirnode->ctfs_cn_contract; 182 183 return (vp); 184 } 185 186 /* 187 * ctfs_ev_access - VOP_ACCESS entry point 188 * 189 * You only get to access event files for contracts you or your 190 * effective user id owns, unless you have a privilege. 191 */ 192 /*ARGSUSED*/ 193 static int 194 ctfs_ev_access(vnode_t *vp, int mode, int flags, cred_t *cr) 195 { 196 ctfs_evnode_t *evnode = vp->v_data; 197 contract_t *ct = evnode->ctfs_ev_contract; 198 int error; 199 200 if (mode & (VWRITE | VEXEC)) 201 return (EACCES); 202 203 if (error = secpolicy_contract_observer(cr, ct)) 204 return (error); 205 206 return (0); 207 } 208 209 /* 210 * ctfs_ev_open - VOP_OPEN entry point 211 * 212 * Performs the same privilege checks as ctfs_ev_access, and then calls 213 * ctfs_endpoint_open to perform the common endpoint initialization. 214 */ 215 /* ARGSUSED */ 216 static int 217 ctfs_ev_open(vnode_t **vpp, int flag, cred_t *cr) 218 { 219 ctfs_evnode_t *evnode = (*vpp)->v_data; 220 contract_t *ct = evnode->ctfs_ev_contract; 221 int error; 222 223 if (error = secpolicy_contract_observer(cr, ct)) 224 return (error); 225 226 /* 227 * See comment in ctfs_bu_open. 228 */ 229 return (ctfs_endpoint_open(&evnode->ctfs_ev_listener, 230 &evnode->ctfs_ev_contract->ct_events, flag)); 231 } 232 233 /* 234 * ctfs_ev_inactive - VOP_INACTIVE entry point 235 */ 236 /* ARGSUSED */ 237 static void 238 ctfs_ev_inactive(vnode_t *vp, cred_t *cr) 239 { 240 ctfs_evnode_t *evnode; 241 vnode_t *pvp = gfs_file_parent(vp); 242 243 /* 244 * We must destroy the endpoint before releasing the parent; otherwise 245 * we will try to destroy a contract with active listeners. To prevent 246 * this, we grab an extra hold on the parent. 247 */ 248 VN_HOLD(pvp); 249 if ((evnode = gfs_file_inactive(vp)) != NULL) { 250 ctfs_endpoint_inactive(&evnode->ctfs_ev_listener); 251 kmem_free(evnode, sizeof (ctfs_evnode_t)); 252 } 253 VN_RELE(pvp); 254 } 255 256 /* 257 * ctfs_ev_getattr - VOP_GETATTR entry point 258 */ 259 /* ARGSUSED */ 260 static int 261 ctfs_ev_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr) 262 { 263 ctfs_evnode_t *evnode = vp->v_data; 264 265 vap->va_type = VREG; 266 vap->va_mode = 0444; 267 vap->va_nlink = 1; 268 vap->va_size = 0; 269 vap->va_ctime = evnode->ctfs_ev_contract->ct_ctime; 270 mutex_enter(&evnode->ctfs_ev_contract->ct_events.ctq_lock); 271 vap->va_atime = vap->va_mtime = 272 evnode->ctfs_ev_contract->ct_events.ctq_atime; 273 mutex_exit(&evnode->ctfs_ev_contract->ct_events.ctq_lock); 274 ctfs_common_getattr(vp, vap); 275 276 return (0); 277 } 278 279 /* 280 * ctfs_ev_ioctl - VOP_IOCTL entry point 281 */ 282 /* ARGSUSED */ 283 static int 284 ctfs_ev_ioctl(vnode_t *vp, int cmd, intptr_t arg, int flag, cred_t *cr, 285 int *rvalp) 286 { 287 ctfs_evnode_t *evnode = vp->v_data; 288 289 return (ctfs_endpoint_ioctl(&evnode->ctfs_ev_listener, cmd, arg, cr, 290 VTOZONE(vp), 0)); 291 } 292 293 /* 294 * ctfs_ev_poll - VOP_POLL entry point 295 */ 296 static int 297 ctfs_ev_poll(vnode_t *vp, short events, int anyyet, short *reventsp, 298 pollhead_t **php) 299 { 300 ctfs_evnode_t *evnode = vp->v_data; 301 302 return (ctfs_endpoint_poll(&evnode->ctfs_ev_listener, events, anyyet, 303 reventsp, php)); 304 } 305 306 const fs_operation_def_t ctfs_tops_event[] = { 307 { VOPNAME_OPEN, { .vop_open = ctfs_ev_open } }, 308 { VOPNAME_CLOSE, { .vop_close = ctfs_close } }, 309 { VOPNAME_IOCTL, { .vop_ioctl = ctfs_ev_ioctl } }, 310 { VOPNAME_GETATTR, { .vop_getattr = ctfs_ev_getattr } }, 311 { VOPNAME_ACCESS, { .vop_access = ctfs_ev_access } }, 312 { VOPNAME_READDIR, { .error = fs_notdir } }, 313 { VOPNAME_LOOKUP, { .error = fs_notdir } }, 314 { VOPNAME_INACTIVE, { .vop_inactive = ctfs_ev_inactive } }, 315 { VOPNAME_POLL, { .vop_poll = ctfs_ev_poll } }, 316 { NULL, NULL } 317 }; 318 319 /* 320 * ctfs_create_pbundle 321 * 322 * Creates and returns a bunode for a /system/contract/<type>/pbundle 323 * file. 324 */ 325 vnode_t * 326 ctfs_create_pbundle(vnode_t *pvp) 327 { 328 vnode_t *vp; 329 ctfs_bunode_t *bundle; 330 331 vp = gfs_file_create(sizeof (ctfs_bunode_t), pvp, ctfs_ops_bundle); 332 bundle = vp->v_data; 333 bundle->ctfs_bu_queue = 334 contract_type_pbundle(ct_types[gfs_file_index(pvp)], curproc); 335 336 return (vp); 337 } 338 339 /* 340 * ctfs_create_bundle 341 * 342 * Creates and returns a bunode for a /system/contract/<type>/bundle 343 * file. 344 */ 345 vnode_t * 346 ctfs_create_bundle(vnode_t *pvp) 347 { 348 vnode_t *vp; 349 ctfs_bunode_t *bundle; 350 351 vp = gfs_file_create(sizeof (ctfs_bunode_t), pvp, ctfs_ops_bundle); 352 bundle = vp->v_data; 353 bundle->ctfs_bu_queue = 354 contract_type_bundle(ct_types[gfs_file_index(pvp)]); 355 356 return (vp); 357 } 358 359 /* 360 * ctfs_bu_open - VOP_OPEN entry point 361 */ 362 /* ARGSUSED */ 363 static int 364 ctfs_bu_open(vnode_t **vpp, int flag, cred_t *cr) 365 { 366 ctfs_bunode_t *bunode = (*vpp)->v_data; 367 368 /* 369 * This assumes we are only ever called immediately after a 370 * VOP_LOOKUP. We could clone ourselves here, but doing so 371 * would make /proc/pid/fd accesses less useful. 372 */ 373 return (ctfs_endpoint_open(&bunode->ctfs_bu_listener, 374 bunode->ctfs_bu_queue, flag)); 375 } 376 377 /* 378 * ctfs_bu_inactive - VOP_INACTIVE entry point 379 */ 380 /* ARGSUSED */ 381 static void 382 ctfs_bu_inactive(vnode_t *vp, cred_t *cr) 383 { 384 ctfs_bunode_t *bunode; 385 vnode_t *pvp = gfs_file_parent(vp); 386 387 /* 388 * See comments in ctfs_ev_inactive() above. 389 */ 390 VN_HOLD(pvp); 391 if ((bunode = gfs_file_inactive(vp)) != NULL) { 392 ctfs_endpoint_inactive(&bunode->ctfs_bu_listener); 393 kmem_free(bunode, sizeof (ctfs_bunode_t)); 394 } 395 VN_RELE(pvp); 396 } 397 398 /* 399 * ctfs_bu_getattr - VOP_GETATTR entry point 400 */ 401 /* ARGSUSED */ 402 static int 403 ctfs_bu_getattr(vnode_t *vp, vattr_t *vap, int flags, cred_t *cr) 404 { 405 ctfs_bunode_t *bunode = vp->v_data; 406 407 vap->va_type = VREG; 408 vap->va_mode = 0444; 409 vap->va_nodeid = gfs_file_index(vp); 410 vap->va_nlink = 1; 411 vap->va_size = 0; 412 vap->va_ctime.tv_sec = vp->v_vfsp->vfs_mtime; 413 vap->va_ctime.tv_nsec = 0; 414 mutex_enter(&bunode->ctfs_bu_queue->ctq_lock); 415 vap->va_mtime = vap->va_atime = bunode->ctfs_bu_queue->ctq_atime; 416 mutex_exit(&bunode->ctfs_bu_queue->ctq_lock); 417 ctfs_common_getattr(vp, vap); 418 419 return (0); 420 } 421 422 /* 423 * ctfs_bu_ioctl - VOP_IOCTL entry point 424 */ 425 /* ARGSUSED */ 426 static int 427 ctfs_bu_ioctl(vnode_t *vp, int cmd, intptr_t arg, int flag, cred_t *cr, 428 int *rvalp) 429 { 430 ctfs_bunode_t *bunode = vp->v_data; 431 432 return (ctfs_endpoint_ioctl(&bunode->ctfs_bu_listener, cmd, arg, cr, 433 VTOZONE(vp), bunode->ctfs_bu_queue->ctq_listno == CTEL_BUNDLE)); 434 } 435 436 /* 437 * ctfs_bu_poll - VOP_POLL entry point 438 */ 439 static int 440 ctfs_bu_poll(vnode_t *vp, short events, int anyyet, short *reventsp, 441 pollhead_t **php) 442 { 443 ctfs_bunode_t *bunode = vp->v_data; 444 445 return (ctfs_endpoint_poll(&bunode->ctfs_bu_listener, events, anyyet, 446 reventsp, php)); 447 } 448 449 const fs_operation_def_t ctfs_tops_bundle[] = { 450 { VOPNAME_OPEN, { .vop_open = ctfs_bu_open } }, 451 { VOPNAME_CLOSE, { .vop_close = ctfs_close } }, 452 { VOPNAME_IOCTL, { .vop_ioctl = ctfs_bu_ioctl } }, 453 { VOPNAME_GETATTR, { .vop_getattr = ctfs_bu_getattr } }, 454 { VOPNAME_ACCESS, { .vop_access = ctfs_access_readonly } }, 455 { VOPNAME_READDIR, { .error = fs_notdir } }, 456 { VOPNAME_LOOKUP, { .error = fs_notdir } }, 457 { VOPNAME_INACTIVE, { .vop_inactive = ctfs_bu_inactive } }, 458 { VOPNAME_POLL, { .vop_poll = ctfs_bu_poll } }, 459 { NULL, NULL } 460 }; 461