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 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( 195 vnode_t *vp, 196 int mode, 197 int flags, 198 cred_t *cr, 199 caller_context_t *cct) 200 { 201 ctfs_evnode_t *evnode = vp->v_data; 202 contract_t *ct = evnode->ctfs_ev_contract; 203 int error; 204 205 if (mode & (VWRITE | VEXEC)) 206 return (EACCES); 207 208 if (error = secpolicy_contract_observer(cr, ct)) 209 return (error); 210 211 return (0); 212 } 213 214 /* 215 * ctfs_ev_open - VOP_OPEN entry point 216 * 217 * Performs the same privilege checks as ctfs_ev_access, and then calls 218 * ctfs_endpoint_open to perform the common endpoint initialization. 219 */ 220 /* ARGSUSED */ 221 static int 222 ctfs_ev_open(vnode_t **vpp, int flag, cred_t *cr, caller_context_t *cct) 223 { 224 ctfs_evnode_t *evnode = (*vpp)->v_data; 225 contract_t *ct = evnode->ctfs_ev_contract; 226 int error; 227 228 if (error = secpolicy_contract_observer(cr, ct)) 229 return (error); 230 231 /* 232 * See comment in ctfs_bu_open. 233 */ 234 return (ctfs_endpoint_open(&evnode->ctfs_ev_listener, 235 &evnode->ctfs_ev_contract->ct_events, flag)); 236 } 237 238 /* 239 * ctfs_ev_inactive - VOP_INACTIVE entry point 240 */ 241 /* ARGSUSED */ 242 static void 243 ctfs_ev_inactive(vnode_t *vp, cred_t *cr, caller_context_t *ct) 244 { 245 ctfs_evnode_t *evnode; 246 vnode_t *pvp = gfs_file_parent(vp); 247 248 /* 249 * We must destroy the endpoint before releasing the parent; otherwise 250 * we will try to destroy a contract with active listeners. To prevent 251 * this, we grab an extra hold on the parent. 252 */ 253 VN_HOLD(pvp); 254 if ((evnode = gfs_file_inactive(vp)) != NULL) { 255 ctfs_endpoint_inactive(&evnode->ctfs_ev_listener); 256 kmem_free(evnode, sizeof (ctfs_evnode_t)); 257 } 258 VN_RELE(pvp); 259 } 260 261 /* 262 * ctfs_ev_getattr - VOP_GETATTR entry point 263 */ 264 /* ARGSUSED */ 265 static int 266 ctfs_ev_getattr( 267 vnode_t *vp, 268 vattr_t *vap, 269 int flags, 270 cred_t *cr, 271 caller_context_t *ct) 272 { 273 ctfs_evnode_t *evnode = vp->v_data; 274 275 vap->va_type = VREG; 276 vap->va_mode = 0444; 277 vap->va_nlink = 1; 278 vap->va_size = 0; 279 vap->va_ctime = evnode->ctfs_ev_contract->ct_ctime; 280 mutex_enter(&evnode->ctfs_ev_contract->ct_events.ctq_lock); 281 vap->va_atime = vap->va_mtime = 282 evnode->ctfs_ev_contract->ct_events.ctq_atime; 283 mutex_exit(&evnode->ctfs_ev_contract->ct_events.ctq_lock); 284 ctfs_common_getattr(vp, vap); 285 286 return (0); 287 } 288 289 /* 290 * ctfs_ev_ioctl - VOP_IOCTL entry point 291 */ 292 /* ARGSUSED */ 293 static int 294 ctfs_ev_ioctl( 295 vnode_t *vp, 296 int cmd, 297 intptr_t arg, 298 int flag, 299 cred_t *cr, 300 int *rvalp, 301 caller_context_t *ct) 302 { 303 ctfs_evnode_t *evnode = vp->v_data; 304 305 return (ctfs_endpoint_ioctl(&evnode->ctfs_ev_listener, cmd, arg, cr, 306 VTOZONE(vp), 0)); 307 } 308 309 /* 310 * ctfs_ev_poll - VOP_POLL entry point 311 */ 312 /*ARGSUSED*/ 313 static int 314 ctfs_ev_poll( 315 vnode_t *vp, 316 short events, 317 int anyyet, 318 short *reventsp, 319 pollhead_t **php, 320 caller_context_t *ct) 321 { 322 ctfs_evnode_t *evnode = vp->v_data; 323 324 return (ctfs_endpoint_poll(&evnode->ctfs_ev_listener, events, anyyet, 325 reventsp, php)); 326 } 327 328 const fs_operation_def_t ctfs_tops_event[] = { 329 { VOPNAME_OPEN, { .vop_open = ctfs_ev_open } }, 330 { VOPNAME_CLOSE, { .vop_close = ctfs_close } }, 331 { VOPNAME_IOCTL, { .vop_ioctl = ctfs_ev_ioctl } }, 332 { VOPNAME_GETATTR, { .vop_getattr = ctfs_ev_getattr } }, 333 { VOPNAME_ACCESS, { .vop_access = ctfs_ev_access } }, 334 { VOPNAME_READDIR, { .error = fs_notdir } }, 335 { VOPNAME_LOOKUP, { .error = fs_notdir } }, 336 { VOPNAME_INACTIVE, { .vop_inactive = ctfs_ev_inactive } }, 337 { VOPNAME_POLL, { .vop_poll = ctfs_ev_poll } }, 338 { NULL, { NULL } } 339 }; 340 341 /* 342 * ctfs_create_pbundle 343 * 344 * Creates and returns a bunode for a /system/contract/<type>/pbundle 345 * file. 346 */ 347 vnode_t * 348 ctfs_create_pbundle(vnode_t *pvp) 349 { 350 vnode_t *vp; 351 ctfs_bunode_t *bundle; 352 353 vp = gfs_file_create(sizeof (ctfs_bunode_t), pvp, ctfs_ops_bundle); 354 bundle = vp->v_data; 355 bundle->ctfs_bu_queue = 356 contract_type_pbundle(ct_types[gfs_file_index(pvp)], curproc); 357 358 return (vp); 359 } 360 361 /* 362 * ctfs_create_bundle 363 * 364 * Creates and returns a bunode for a /system/contract/<type>/bundle 365 * file. 366 */ 367 vnode_t * 368 ctfs_create_bundle(vnode_t *pvp) 369 { 370 vnode_t *vp; 371 ctfs_bunode_t *bundle; 372 373 vp = gfs_file_create(sizeof (ctfs_bunode_t), pvp, ctfs_ops_bundle); 374 bundle = vp->v_data; 375 bundle->ctfs_bu_queue = 376 contract_type_bundle(ct_types[gfs_file_index(pvp)]); 377 378 return (vp); 379 } 380 381 /* 382 * ctfs_bu_open - VOP_OPEN entry point 383 */ 384 /* ARGSUSED */ 385 static int 386 ctfs_bu_open(vnode_t **vpp, int flag, cred_t *cr, caller_context_t *ct) 387 { 388 ctfs_bunode_t *bunode = (*vpp)->v_data; 389 390 /* 391 * This assumes we are only ever called immediately after a 392 * VOP_LOOKUP. We could clone ourselves here, but doing so 393 * would make /proc/pid/fd accesses less useful. 394 */ 395 return (ctfs_endpoint_open(&bunode->ctfs_bu_listener, 396 bunode->ctfs_bu_queue, flag)); 397 } 398 399 /* 400 * ctfs_bu_inactive - VOP_INACTIVE entry point 401 */ 402 /* ARGSUSED */ 403 static void 404 ctfs_bu_inactive(vnode_t *vp, cred_t *cr, caller_context_t *ct) 405 { 406 ctfs_bunode_t *bunode; 407 vnode_t *pvp = gfs_file_parent(vp); 408 409 /* 410 * See comments in ctfs_ev_inactive() above. 411 */ 412 VN_HOLD(pvp); 413 if ((bunode = gfs_file_inactive(vp)) != NULL) { 414 ctfs_endpoint_inactive(&bunode->ctfs_bu_listener); 415 kmem_free(bunode, sizeof (ctfs_bunode_t)); 416 } 417 VN_RELE(pvp); 418 } 419 420 /* 421 * ctfs_bu_getattr - VOP_GETATTR entry point 422 */ 423 /* ARGSUSED */ 424 static int 425 ctfs_bu_getattr( 426 vnode_t *vp, 427 vattr_t *vap, 428 int flags, 429 cred_t *cr, 430 caller_context_t *ct) 431 { 432 ctfs_bunode_t *bunode = vp->v_data; 433 434 vap->va_type = VREG; 435 vap->va_mode = 0444; 436 vap->va_nodeid = gfs_file_index(vp); 437 vap->va_nlink = 1; 438 vap->va_size = 0; 439 vap->va_ctime.tv_sec = vp->v_vfsp->vfs_mtime; 440 vap->va_ctime.tv_nsec = 0; 441 mutex_enter(&bunode->ctfs_bu_queue->ctq_lock); 442 vap->va_mtime = vap->va_atime = bunode->ctfs_bu_queue->ctq_atime; 443 mutex_exit(&bunode->ctfs_bu_queue->ctq_lock); 444 ctfs_common_getattr(vp, vap); 445 446 return (0); 447 } 448 449 /* 450 * ctfs_bu_ioctl - VOP_IOCTL entry point 451 */ 452 /* ARGSUSED */ 453 static int 454 ctfs_bu_ioctl( 455 vnode_t *vp, 456 int cmd, 457 intptr_t arg, 458 int flag, 459 cred_t *cr, 460 int *rvalp, 461 caller_context_t *ct) 462 { 463 ctfs_bunode_t *bunode = vp->v_data; 464 465 return (ctfs_endpoint_ioctl(&bunode->ctfs_bu_listener, cmd, arg, cr, 466 VTOZONE(vp), bunode->ctfs_bu_queue->ctq_listno == CTEL_BUNDLE)); 467 } 468 469 /* 470 * ctfs_bu_poll - VOP_POLL entry point 471 */ 472 /*ARGSUSED*/ 473 static int 474 ctfs_bu_poll( 475 vnode_t *vp, 476 short events, 477 int anyyet, 478 short *reventsp, 479 pollhead_t **php, 480 caller_context_t *ct) 481 { 482 ctfs_bunode_t *bunode = vp->v_data; 483 484 return (ctfs_endpoint_poll(&bunode->ctfs_bu_listener, events, anyyet, 485 reventsp, php)); 486 } 487 488 const fs_operation_def_t ctfs_tops_bundle[] = { 489 { VOPNAME_OPEN, { .vop_open = ctfs_bu_open } }, 490 { VOPNAME_CLOSE, { .vop_close = ctfs_close } }, 491 { VOPNAME_IOCTL, { .vop_ioctl = ctfs_bu_ioctl } }, 492 { VOPNAME_GETATTR, { .vop_getattr = ctfs_bu_getattr } }, 493 { VOPNAME_ACCESS, { .vop_access = ctfs_access_readonly } }, 494 { VOPNAME_READDIR, { .error = fs_notdir } }, 495 { VOPNAME_LOOKUP, { .error = fs_notdir } }, 496 { VOPNAME_INACTIVE, { .vop_inactive = ctfs_bu_inactive } }, 497 { VOPNAME_POLL, { .vop_poll = ctfs_bu_poll } }, 498 { NULL, { NULL } } 499 };