static int cgetlabel(bslabel_t *label_p, vnode_t *vp) { ts_label_t *tsl; int error = 0; if ((tsl = getflabel(vp)) == NULL) return (EIO); if (copyout((caddr_t)label2bslabel(tsl), (caddr_t)label_p, sizeof (*(label_p))) != 0) error = EFAULT; label_rele(tsl); return (error); }
/*ARGSUSED*/ static int lo_mount(struct vfs *vfsp, struct vnode *vp, struct mounta *uap, struct cred *cr) { int error; struct vnode *srootvp = NULL; /* the server's root */ struct vnode *realrootvp; struct loinfo *li; int nodev; nodev = vfs_optionisset(vfsp, MNTOPT_NODEVICES, NULL); if ((error = secpolicy_fs_mount(cr, vp, vfsp)) != 0) return (EPERM); /* * Loopback devices which get "nodevices" added can be done without * "nodevices" set because we cannot import devices into a zone * with loopback. Note that we have all zone privileges when * this happens; if not, we'd have gotten "nosuid". */ if (!nodev && vfs_optionisset(vfsp, MNTOPT_NODEVICES, NULL)) vfs_setmntopt(vfsp, MNTOPT_DEVICES, NULL, VFS_NODISPLAY); mutex_enter(&vp->v_lock); if (!(uap->flags & MS_OVERLAY) && (vp->v_count != 1 || (vp->v_flag & VROOT))) { mutex_exit(&vp->v_lock); return (EBUSY); } mutex_exit(&vp->v_lock); /* * Find real root, and make vfs point to real vfs */ if (error = lookupname(uap->spec, (uap->flags & MS_SYSSPACE) ? UIO_SYSSPACE : UIO_USERSPACE, FOLLOW, NULLVPP, &realrootvp)) return (error); /* * Enforce MAC policy if needed. * * Loopback mounts must not allow writing up. The dominance test * is intended to prevent a global zone caller from accidentally * creating write-up conditions between two labeled zones. * Local zones can't violate MAC on their own without help from * the global zone because they can't name a pathname that * they don't already have. * * The special case check for the NET_MAC_AWARE process flag is * to support the case of the automounter in the global zone. We * permit automounting of local zone directories such as home * directories, into the global zone as required by setlabel, * zonecopy, and saving of desktop sessions. Such mounts are * trusted not to expose the contents of one zone's directories * to another by leaking them through the global zone. */ if (is_system_labeled() && crgetzoneid(cr) == GLOBAL_ZONEID) { char specname[MAXPATHLEN]; zone_t *from_zptr; zone_t *to_zptr; if (vnodetopath(NULL, realrootvp, specname, sizeof (specname), CRED()) != 0) { VN_RELE(realrootvp); return (EACCES); } from_zptr = zone_find_by_path(specname); to_zptr = zone_find_by_path(refstr_value(vfsp->vfs_mntpt)); /* * Special case for zone devfs: the zone for /dev will * incorrectly appear as the global zone since it's not * under the zone rootpath. So for zone devfs check allow * read-write mounts. * * Second special case for scratch zones used for Live Upgrade: * this is used to mount the zone's root from /root to /a in * the scratch zone. As with the other special case, this * appears to be outside of the zone because it's not under * the zone rootpath, which is $ZONEPATH/lu in the scratch * zone case. */ if (from_zptr != to_zptr && !(to_zptr->zone_flags & ZF_IS_SCRATCH)) { /* * We know at this point that the labels aren't equal * because the zone pointers aren't equal, and zones * can't share a label. * * If the source is the global zone then making * it available to a local zone must be done in * read-only mode as the label will become admin_low. * * If it is a mount between local zones then if * the current process is in the global zone and has * the NET_MAC_AWARE flag, then regular read-write * access is allowed. If it's in some other zone, but * the label on the mount point dominates the original * source, then allow the mount as read-only * ("read-down"). */ if (from_zptr->zone_id == GLOBAL_ZONEID) { /* make the mount read-only */ vfs_setmntopt(vfsp, MNTOPT_RO, NULL, 0); } else { /* cross-zone mount */ if (to_zptr->zone_id == GLOBAL_ZONEID && /* LINTED: no consequent */ getpflags(NET_MAC_AWARE, cr) != 0) { /* Allow the mount as read-write */ } else if (bldominates( label2bslabel(to_zptr->zone_slabel), label2bslabel(from_zptr->zone_slabel))) { /* make the mount read-only */ vfs_setmntopt(vfsp, MNTOPT_RO, NULL, 0); } else { VN_RELE(realrootvp); zone_rele(to_zptr); zone_rele(from_zptr); return (EACCES); } } } zone_rele(to_zptr); zone_rele(from_zptr); } /* * realrootvp may be an AUTOFS node, in which case we * perform a VOP_ACCESS() to trigger the mount of the * intended filesystem, so we loopback mount the intended * filesystem instead of the AUTOFS filesystem. */ (void) VOP_ACCESS(realrootvp, 0, 0, cr, NULL); /* * We're interested in the top most filesystem. * This is specially important when uap->spec is a trigger * AUTOFS node, since we're really interested in mounting the * filesystem AUTOFS mounted as result of the VOP_ACCESS() * call not the AUTOFS node itself. */ if (vn_mountedvfs(realrootvp) != NULL) { if (error = traverse(&realrootvp)) { VN_RELE(realrootvp); return (error); } } /* * Allocate a vfs info struct and attach it */ li = kmem_zalloc(sizeof (struct loinfo), KM_SLEEP); li->li_realvfs = realrootvp->v_vfsp; li->li_mountvfs = vfsp; /* * Set mount flags to be inherited by loopback vfs's */ if (vfs_optionisset(vfsp, MNTOPT_RO, NULL)) { li->li_mflag |= VFS_RDONLY; } if (vfs_optionisset(vfsp, MNTOPT_NOSUID, NULL)) { li->li_mflag |= (VFS_NOSETUID|VFS_NODEVICES); } if (vfs_optionisset(vfsp, MNTOPT_NODEVICES, NULL)) { li->li_mflag |= VFS_NODEVICES; } if (vfs_optionisset(vfsp, MNTOPT_NOSETUID, NULL)) { li->li_mflag |= VFS_NOSETUID; } /* * Permissive flags are added to the "deny" bitmap. */ if (vfs_optionisset(vfsp, MNTOPT_NOXATTR, NULL)) { li->li_dflag |= VFS_XATTR; } if (vfs_optionisset(vfsp, MNTOPT_NONBMAND, NULL)) { li->li_dflag |= VFS_NBMAND; } /* * Propagate inheritable mount flags from the real vfs. */ if ((li->li_realvfs->vfs_flag & VFS_RDONLY) && !vfs_optionisset(vfsp, MNTOPT_RO, NULL)) vfs_setmntopt(vfsp, MNTOPT_RO, NULL, VFS_NODISPLAY); if ((li->li_realvfs->vfs_flag & VFS_NOSETUID) && !vfs_optionisset(vfsp, MNTOPT_NOSETUID, NULL)) vfs_setmntopt(vfsp, MNTOPT_NOSETUID, NULL, VFS_NODISPLAY); if ((li->li_realvfs->vfs_flag & VFS_NODEVICES) && !vfs_optionisset(vfsp, MNTOPT_NODEVICES, NULL)) vfs_setmntopt(vfsp, MNTOPT_NODEVICES, NULL, VFS_NODISPLAY); /* * Permissive flags such as VFS_XATTR, as opposed to restrictive flags * such as VFS_RDONLY, are handled differently. An explicit * MNTOPT_NOXATTR should override the underlying filesystem's VFS_XATTR. */ if ((li->li_realvfs->vfs_flag & VFS_XATTR) && !vfs_optionisset(vfsp, MNTOPT_NOXATTR, NULL) && !vfs_optionisset(vfsp, MNTOPT_XATTR, NULL)) vfs_setmntopt(vfsp, MNTOPT_XATTR, NULL, VFS_NODISPLAY); if ((li->li_realvfs->vfs_flag & VFS_NBMAND) && !vfs_optionisset(vfsp, MNTOPT_NBMAND, NULL) && !vfs_optionisset(vfsp, MNTOPT_NONBMAND, NULL)) vfs_setmntopt(vfsp, MNTOPT_NBMAND, NULL, VFS_NODISPLAY); li->li_refct = 0; vfsp->vfs_data = (caddr_t)li; vfsp->vfs_bcount = 0; vfsp->vfs_fstype = lofsfstype; vfsp->vfs_bsize = li->li_realvfs->vfs_bsize; vfsp->vfs_dev = li->li_realvfs->vfs_dev; vfsp->vfs_fsid.val[0] = li->li_realvfs->vfs_fsid.val[0]; vfsp->vfs_fsid.val[1] = li->li_realvfs->vfs_fsid.val[1]; if (vfs_optionisset(vfsp, MNTOPT_LOFS_NOSUB, NULL)) { li->li_flag |= LO_NOSUB; } /* * Propagate any VFS features */ vfs_propagate_features(li->li_realvfs, vfsp); /* * Setup the hashtable. If the root of this mount isn't a directory, * there's no point in allocating a large hashtable. A table with one * bucket is sufficient. */ if (realrootvp->v_type != VDIR) lsetup(li, 1); else lsetup(li, 0); /* * Make the root vnode */ srootvp = makelonode(realrootvp, li, 0); srootvp->v_flag |= VROOT; li->li_rootvp = srootvp; #ifdef LODEBUG lo_dprint(4, "lo_mount: vfs %p realvfs %p root %p realroot %p li %p\n", vfsp, li->li_realvfs, srootvp, realrootvp, li); #endif return (0); }
/* * Return SNMP stuff in buffer in mpdata. */ mblk_t * tcp_snmp_get(queue_t *q, mblk_t *mpctl, boolean_t legacy_req) { mblk_t *mpdata; mblk_t *mp_conn_ctl = NULL; mblk_t *mp_conn_tail; mblk_t *mp_attr_ctl = NULL; mblk_t *mp_attr_tail; mblk_t *mp6_conn_ctl = NULL; mblk_t *mp6_conn_tail; mblk_t *mp6_attr_ctl = NULL; mblk_t *mp6_attr_tail; struct opthdr *optp; mib2_tcpConnEntry_t tce; mib2_tcp6ConnEntry_t tce6; mib2_transportMLPEntry_t mlp; connf_t *connfp; int i; boolean_t ispriv; zoneid_t zoneid; int v4_conn_idx; int v6_conn_idx; conn_t *connp = Q_TO_CONN(q); tcp_stack_t *tcps; ip_stack_t *ipst; mblk_t *mp2ctl; mib2_tcp_t tcp_mib; size_t tcp_mib_size, tce_size, tce6_size; /* * make a copy of the original message */ mp2ctl = copymsg(mpctl); if (mpctl == NULL || (mpdata = mpctl->b_cont) == NULL || (mp_conn_ctl = copymsg(mpctl)) == NULL || (mp_attr_ctl = copymsg(mpctl)) == NULL || (mp6_conn_ctl = copymsg(mpctl)) == NULL || (mp6_attr_ctl = copymsg(mpctl)) == NULL) { freemsg(mp_conn_ctl); freemsg(mp_attr_ctl); freemsg(mp6_conn_ctl); freemsg(mp6_attr_ctl); freemsg(mpctl); freemsg(mp2ctl); return (NULL); } ipst = connp->conn_netstack->netstack_ip; tcps = connp->conn_netstack->netstack_tcp; if (legacy_req) { tcp_mib_size = LEGACY_MIB_SIZE(&tcp_mib, mib2_tcp_t); tce_size = LEGACY_MIB_SIZE(&tce, mib2_tcpConnEntry_t); tce6_size = LEGACY_MIB_SIZE(&tce6, mib2_tcp6ConnEntry_t); } else { tcp_mib_size = sizeof (mib2_tcp_t); tce_size = sizeof (mib2_tcpConnEntry_t); tce6_size = sizeof (mib2_tcp6ConnEntry_t); } bzero(&tcp_mib, sizeof (tcp_mib)); /* build table of connections -- need count in fixed part */ SET_MIB(tcp_mib.tcpRtoAlgorithm, 4); /* vanj */ SET_MIB(tcp_mib.tcpRtoMin, tcps->tcps_rexmit_interval_min); SET_MIB(tcp_mib.tcpRtoMax, tcps->tcps_rexmit_interval_max); SET_MIB(tcp_mib.tcpMaxConn, -1); SET_MIB(tcp_mib.tcpCurrEstab, 0); ispriv = secpolicy_ip_config((Q_TO_CONN(q))->conn_cred, B_TRUE) == 0; zoneid = Q_TO_CONN(q)->conn_zoneid; v4_conn_idx = v6_conn_idx = 0; mp_conn_tail = mp_attr_tail = mp6_conn_tail = mp6_attr_tail = NULL; for (i = 0; i < CONN_G_HASH_SIZE; i++) { ipst = tcps->tcps_netstack->netstack_ip; connfp = &ipst->ips_ipcl_globalhash_fanout[i]; connp = NULL; while ((connp = ipcl_get_next_conn(connfp, connp, IPCL_TCPCONN)) != NULL) { tcp_t *tcp; boolean_t needattr; if (connp->conn_zoneid != zoneid) continue; /* not in this zone */ tcp = connp->conn_tcp; TCPS_UPDATE_MIB(tcps, tcpHCInSegs, tcp->tcp_ibsegs); tcp->tcp_ibsegs = 0; TCPS_UPDATE_MIB(tcps, tcpHCOutSegs, tcp->tcp_obsegs); tcp->tcp_obsegs = 0; tce6.tcp6ConnState = tce.tcpConnState = tcp_snmp_state(tcp); if (tce.tcpConnState == MIB2_TCP_established || tce.tcpConnState == MIB2_TCP_closeWait) BUMP_MIB(&tcp_mib, tcpCurrEstab); needattr = B_FALSE; bzero(&mlp, sizeof (mlp)); if (connp->conn_mlp_type != mlptSingle) { if (connp->conn_mlp_type == mlptShared || connp->conn_mlp_type == mlptBoth) mlp.tme_flags |= MIB2_TMEF_SHARED; if (connp->conn_mlp_type == mlptPrivate || connp->conn_mlp_type == mlptBoth) mlp.tme_flags |= MIB2_TMEF_PRIVATE; needattr = B_TRUE; } if (connp->conn_anon_mlp) { mlp.tme_flags |= MIB2_TMEF_ANONMLP; needattr = B_TRUE; } switch (connp->conn_mac_mode) { case CONN_MAC_DEFAULT: break; case CONN_MAC_AWARE: mlp.tme_flags |= MIB2_TMEF_MACEXEMPT; needattr = B_TRUE; break; case CONN_MAC_IMPLICIT: mlp.tme_flags |= MIB2_TMEF_MACIMPLICIT; needattr = B_TRUE; break; } if (connp->conn_ixa->ixa_tsl != NULL) { ts_label_t *tsl; tsl = connp->conn_ixa->ixa_tsl; mlp.tme_flags |= MIB2_TMEF_IS_LABELED; mlp.tme_doi = label2doi(tsl); mlp.tme_label = *label2bslabel(tsl); needattr = B_TRUE; } /* Create a message to report on IPv6 entries */ if (connp->conn_ipversion == IPV6_VERSION) { tce6.tcp6ConnLocalAddress = connp->conn_laddr_v6; tce6.tcp6ConnRemAddress = connp->conn_faddr_v6; tce6.tcp6ConnLocalPort = ntohs(connp->conn_lport); tce6.tcp6ConnRemPort = ntohs(connp->conn_fport); if (connp->conn_ixa->ixa_flags & IXAF_SCOPEID_SET) { tce6.tcp6ConnIfIndex = connp->conn_ixa->ixa_scopeid; } else { tce6.tcp6ConnIfIndex = connp->conn_bound_if; } /* Don't want just anybody seeing these... */ if (ispriv) { tce6.tcp6ConnEntryInfo.ce_snxt = tcp->tcp_snxt; tce6.tcp6ConnEntryInfo.ce_suna = tcp->tcp_suna; tce6.tcp6ConnEntryInfo.ce_rnxt = tcp->tcp_rnxt; tce6.tcp6ConnEntryInfo.ce_rack = tcp->tcp_rack; } else { /* * Netstat, unfortunately, uses this to * get send/receive queue sizes. How to fix? * Why not compute the difference only? */ tce6.tcp6ConnEntryInfo.ce_snxt = tcp->tcp_snxt - tcp->tcp_suna; tce6.tcp6ConnEntryInfo.ce_suna = 0; tce6.tcp6ConnEntryInfo.ce_rnxt = tcp->tcp_rnxt - tcp->tcp_rack; tce6.tcp6ConnEntryInfo.ce_rack = 0; } tce6.tcp6ConnEntryInfo.ce_swnd = tcp->tcp_swnd; tce6.tcp6ConnEntryInfo.ce_rwnd = tcp->tcp_rwnd; tce6.tcp6ConnEntryInfo.ce_rto = tcp->tcp_rto; tce6.tcp6ConnEntryInfo.ce_mss = tcp->tcp_mss; tce6.tcp6ConnEntryInfo.ce_state = tcp->tcp_state; tce6.tcp6ConnCreationProcess = (connp->conn_cpid < 0) ? MIB2_UNKNOWN_PROCESS : connp->conn_cpid; tce6.tcp6ConnCreationTime = connp->conn_open_time; (void) snmp_append_data2(mp6_conn_ctl->b_cont, &mp6_conn_tail, (char *)&tce6, tce6_size); mlp.tme_connidx = v6_conn_idx++; if (needattr) (void) snmp_append_data2(mp6_attr_ctl->b_cont, &mp6_attr_tail, (char *)&mlp, sizeof (mlp)); } /* * Create an IPv4 table entry for IPv4 entries and also * for IPv6 entries which are bound to in6addr_any * but don't have IPV6_V6ONLY set. * (i.e. anything an IPv4 peer could connect to) */ if (connp->conn_ipversion == IPV4_VERSION || (tcp->tcp_state <= TCPS_LISTEN && !connp->conn_ipv6_v6only && IN6_IS_ADDR_UNSPECIFIED(&connp->conn_laddr_v6))) { if (connp->conn_ipversion == IPV6_VERSION) { tce.tcpConnRemAddress = INADDR_ANY; tce.tcpConnLocalAddress = INADDR_ANY; } else { tce.tcpConnRemAddress = connp->conn_faddr_v4; tce.tcpConnLocalAddress = connp->conn_laddr_v4; } tce.tcpConnLocalPort = ntohs(connp->conn_lport); tce.tcpConnRemPort = ntohs(connp->conn_fport); /* Don't want just anybody seeing these... */ if (ispriv) { tce.tcpConnEntryInfo.ce_snxt = tcp->tcp_snxt; tce.tcpConnEntryInfo.ce_suna = tcp->tcp_suna; tce.tcpConnEntryInfo.ce_rnxt = tcp->tcp_rnxt; tce.tcpConnEntryInfo.ce_rack = tcp->tcp_rack; } else { /* * Netstat, unfortunately, uses this to * get send/receive queue sizes. How * to fix? * Why not compute the difference only? */ tce.tcpConnEntryInfo.ce_snxt = tcp->tcp_snxt - tcp->tcp_suna; tce.tcpConnEntryInfo.ce_suna = 0; tce.tcpConnEntryInfo.ce_rnxt = tcp->tcp_rnxt - tcp->tcp_rack; tce.tcpConnEntryInfo.ce_rack = 0; } tce.tcpConnEntryInfo.ce_swnd = tcp->tcp_swnd; tce.tcpConnEntryInfo.ce_rwnd = tcp->tcp_rwnd; tce.tcpConnEntryInfo.ce_rto = tcp->tcp_rto; tce.tcpConnEntryInfo.ce_mss = tcp->tcp_mss; tce.tcpConnEntryInfo.ce_state = tcp->tcp_state; tce.tcpConnCreationProcess = (connp->conn_cpid < 0) ? MIB2_UNKNOWN_PROCESS : connp->conn_cpid; tce.tcpConnCreationTime = connp->conn_open_time; (void) snmp_append_data2(mp_conn_ctl->b_cont, &mp_conn_tail, (char *)&tce, tce_size); mlp.tme_connidx = v4_conn_idx++; if (needattr) (void) snmp_append_data2( mp_attr_ctl->b_cont, &mp_attr_tail, (char *)&mlp, sizeof (mlp)); } } } tcp_sum_mib(tcps, &tcp_mib); /* Fixed length structure for IPv4 and IPv6 counters */ SET_MIB(tcp_mib.tcpConnTableSize, tce_size); SET_MIB(tcp_mib.tcp6ConnTableSize, tce6_size); /* * Synchronize 32- and 64-bit counters. Note that tcpInSegs and * tcpOutSegs are not updated anywhere in TCP. The new 64 bits * counters are used. Hence the old counters' values in tcp_sc_mib * are always 0. */ SYNC32_MIB(&tcp_mib, tcpInSegs, tcpHCInSegs); SYNC32_MIB(&tcp_mib, tcpOutSegs, tcpHCOutSegs); optp = (struct opthdr *)&mpctl->b_rptr[sizeof (struct T_optmgmt_ack)]; optp->level = MIB2_TCP; optp->name = 0; (void) snmp_append_data(mpdata, (char *)&tcp_mib, tcp_mib_size); optp->len = msgdsize(mpdata); qreply(q, mpctl); /* table of connections... */ optp = (struct opthdr *)&mp_conn_ctl->b_rptr[ sizeof (struct T_optmgmt_ack)]; optp->level = MIB2_TCP; optp->name = MIB2_TCP_CONN; optp->len = msgdsize(mp_conn_ctl->b_cont); qreply(q, mp_conn_ctl); /* table of MLP attributes... */ optp = (struct opthdr *)&mp_attr_ctl->b_rptr[ sizeof (struct T_optmgmt_ack)]; optp->level = MIB2_TCP; optp->name = EXPER_XPORT_MLP; optp->len = msgdsize(mp_attr_ctl->b_cont); if (optp->len == 0) freemsg(mp_attr_ctl); else qreply(q, mp_attr_ctl); /* table of IPv6 connections... */ optp = (struct opthdr *)&mp6_conn_ctl->b_rptr[ sizeof (struct T_optmgmt_ack)]; optp->level = MIB2_TCP6; optp->name = MIB2_TCP6_CONN; optp->len = msgdsize(mp6_conn_ctl->b_cont); qreply(q, mp6_conn_ctl); /* table of IPv6 MLP attributes... */ optp = (struct opthdr *)&mp6_attr_ctl->b_rptr[ sizeof (struct T_optmgmt_ack)]; optp->level = MIB2_TCP6; optp->name = EXPER_XPORT_MLP; optp->len = msgdsize(mp6_attr_ctl->b_cont); if (optp->len == 0) freemsg(mp6_attr_ctl); else qreply(q, mp6_attr_ctl); return (mp2ctl); }
/* * smbfs_mount_label_policy: * Determine whether the mount is allowed according to MAC check, * by comparing (where appropriate) label of the remote server * against the label of the zone being mounted into. * * Returns: * 0 : access allowed * -1 : read-only access allowed (i.e., read-down) * >0 : error code, such as EACCES * * NB: * NFS supports Cipso labels by parsing the vfs_resource * to see what the Solaris server global zone has shared. * We can't support that for CIFS since resource names * contain share names, not paths. */ static int smbfs_mount_label_policy(vfs_t *vfsp, void *ipaddr, int addr_type, cred_t *cr) { bslabel_t *server_sl, *mntlabel; zone_t *mntzone = NULL; ts_label_t *zlabel; tsol_tpc_t *tp; ts_label_t *tsl = NULL; int retv; /* * Get the zone's label. Each zone on a labeled system has a label. */ mntzone = zone_find_by_any_path(refstr_value(vfsp->vfs_mntpt), B_FALSE); zlabel = mntzone->zone_slabel; ASSERT(zlabel != NULL); label_hold(zlabel); retv = EACCES; /* assume the worst */ /* * Next, get the assigned label of the remote server. */ tp = find_tpc(ipaddr, addr_type, B_FALSE); if (tp == NULL) goto out; /* error getting host entry */ if (tp->tpc_tp.tp_doi != zlabel->tsl_doi) goto rel_tpc; /* invalid domain */ if ((tp->tpc_tp.host_type != UNLABELED)) goto rel_tpc; /* invalid hosttype */ server_sl = &tp->tpc_tp.tp_def_label; mntlabel = label2bslabel(zlabel); /* * Now compare labels to complete the MAC check. If the labels * are equal or if the requestor is in the global zone and has * NET_MAC_AWARE, then allow read-write access. (Except for * mounts into the global zone itself; restrict these to * read-only.) * * If the requestor is in some other zone, but his label * dominates the server, then allow read-down. * * Otherwise, access is denied. */ if (blequal(mntlabel, server_sl) || (crgetzoneid(cr) == GLOBAL_ZONEID && getpflags(NET_MAC_AWARE, cr) != 0)) { if ((mntzone == global_zone) || !blequal(mntlabel, server_sl)) retv = -1; /* read-only */ else retv = 0; /* access OK */ } else if (bldominates(mntlabel, server_sl)) { retv = -1; /* read-only */ } else { retv = EACCES; } if (tsl != NULL) label_rele(tsl); rel_tpc: /*LINTED*/ TPC_RELE(tp); out: if (mntzone) zone_rele(mntzone); label_rele(zlabel); return (retv); }