2N/A/*
2N/A * CDDL HEADER START
2N/A *
2N/A * The contents of this file are subject to the terms of the
2N/A * Common Development and Distribution License (the "License").
2N/A * You may not use this file except in compliance with the License.
2N/A *
2N/A * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
2N/A * or http://www.opensolaris.org/os/licensing.
2N/A * See the License for the specific language governing permissions
2N/A * and limitations under the License.
2N/A *
2N/A * When distributing Covered Code, include this CDDL HEADER in each
2N/A * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
2N/A * If applicable, add the following below this CDDL HEADER, with the
2N/A * fields enclosed by brackets "[]" replaced with your own identifying
2N/A * information: Portions Copyright [yyyy] [name of copyright owner]
2N/A *
2N/A * CDDL HEADER END
2N/A */
2N/A
2N/A/*
2N/A * Copyright (c) 2012, Oracle and/or its affiliates. All rights reserved.
2N/A */
2N/A
2N/A#include <assert.h>
2N/A#include <errno.h>
2N/A#include <fcntl.h>
2N/A#include <libcontract.h>
2N/A#include <libcontract_priv.h>
2N/A#include <stdio.h>
2N/A#include <stropts.h>
2N/A#include <sys/contract/process.h>
2N/A#include <sys/stat.h>
2N/A#include <sys/types.h>
2N/A#include <unistd.h>
2N/A#include <wait.h>
2N/A#include <zone.h>
2N/A
2N/A#include "libnetcfg.h"
2N/A#include "libnetcfg_impl.h"
2N/A
2N/A/*
2N/A * Functions to handle db interactions across zones. These functions were
2N/A * originally implemented in dlmgmtd; they were moved into this library as
2N/A * part of the merge of the ipmgmt, dlmgmt, and nwam repositories.
2N/A */
2N/A
2N/Atypedef struct zopen_arg {
2N/A const char *zopen_modestr;
2N/A int zopen_oflag;
2N/A mode_t zopen_mode;
2N/A int *zopen_pipe;
2N/A int zopen_fd;
2N/A} zopen_arg_t;
2N/A
2N/Atypedef struct zrename_arg {
2N/A const char *zrename_newname;
2N/A} zrename_arg_t;
2N/A
2N/Atypedef union zfoparg {
2N/A zopen_arg_t zfop_openarg;
2N/A zrename_arg_t zfop_renamearg;
2N/A} zfoparg_t;
2N/A
2N/Atypedef struct zfcbarg {
2N/A boolean_t zfarg_inglobalzone; /* is callback in global zone? */
2N/A boolean_t zfarg_finglobalzone; /* is file in global zone? */
2N/A const char *zfarg_filename;
2N/A zfoparg_t *zfarg_oparg;
2N/A} zfarg_t;
2N/A#define zfarg_openarg zfarg_oparg->zfop_openarg
2N/A#define zfarg_renamearg zfarg_oparg->zfop_renamearg
2N/A
2N/A/* zone file callback */
2N/Atypedef int zfcb_t(zfarg_t *);
2N/A
2N/A/*
2N/A * Execute an operation on filename relative to zoneid's zone root. If the
2N/A * file is in the global zone, then the zfcb() callback will simply be called
2N/A * directly. If the file is in a non-global zone, then zfcb() will be called
2N/A * both from the global zone's context, and from the non-global zone's context
2N/A * (from a fork()'ed child that has entered the non-global zone). This is
2N/A * done to allow the callback to communicate with itself if needed (e.g. to
2N/A * pass back the file descriptor of an opened file).
2N/A */
2N/Astatic int
2N/Anetcfg_zfop(const char *filename, zoneid_t zoneid, zfcb_t *zfcb,
2N/A zfoparg_t *zfoparg)
2N/A{
2N/A int ctfd;
2N/A int err;
2N/A pid_t childpid;
2N/A siginfo_t info;
2N/A zfarg_t zfarg;
2N/A ctid_t ct;
2N/A
2N/A if (zoneid != GLOBAL_ZONEID) {
2N/A /*
2N/A * We need to access a file that isn't in the global zone,
2N/A * and we are running in the global zone. Accessing non-
2N/A * global zone files from the global zone is unsafe (due to
2N/A * symlink attacks); we'll need to fork a child that enters
2N/A * the zone in question and executes the callback that will
2N/A * operate on the file.
2N/A *
2N/A * Before we proceed with this zone tango, we need to create
2N/A * a new process contract for the child, as required by
2N/A * zone_enter().
2N/A */
2N/A errno = 0;
2N/A ctfd = open64("/system/contract/process/template", O_RDWR);
2N/A if (ctfd == -1)
2N/A return (errno);
2N/A if ((err = ct_tmpl_set_critical(ctfd, 0)) != 0 ||
2N/A (err = ct_tmpl_set_informative(ctfd, 0)) != 0 ||
2N/A (err = ct_pr_tmpl_set_fatal(ctfd, CT_PR_EV_HWERR)) != 0 ||
2N/A (err = ct_pr_tmpl_set_param(ctfd, CT_PR_PGRPONLY)) != 0 ||
2N/A (err = ct_tmpl_activate(ctfd)) != 0) {
2N/A (void) close(ctfd);
2N/A return (err);
2N/A }
2N/A childpid = fork();
2N/A switch (childpid) {
2N/A case -1:
2N/A (void) ct_tmpl_clear(ctfd);
2N/A (void) close(ctfd);
2N/A return (err);
2N/A case 0:
2N/A (void) ct_tmpl_clear(ctfd);
2N/A (void) close(ctfd);
2N/A /*
2N/A * Elevate our privileges as zone_enter() requires all
2N/A * privileges.
2N/A */
2N/A if ((err = netcfg_elevate_privileges()) != 0)
2N/A _exit(err);
2N/A if (zone_enter(zoneid) == -1)
2N/A _exit(errno);
2N/A if ((err = netcfg_drop_privileges()) != 0)
2N/A _exit(err);
2N/A break;
2N/A default:
2N/A if (contract_latest(&ct) == -1)
2N/A ct = -1;
2N/A (void) ct_tmpl_clear(ctfd);
2N/A (void) close(ctfd);
2N/A if (waitid(P_PID, childpid, &info, WEXITED) == -1) {
2N/A (void) contract_abandon_id(ct);
2N/A return (errno);
2N/A }
2N/A (void) contract_abandon_id(ct);
2N/A if (info.si_status != 0)
2N/A return (info.si_status);
2N/A }
2N/A }
2N/A
2N/A zfarg.zfarg_inglobalzone = (zoneid == GLOBAL_ZONEID || childpid != 0);
2N/A zfarg.zfarg_finglobalzone = (zoneid == GLOBAL_ZONEID);
2N/A zfarg.zfarg_filename = filename;
2N/A zfarg.zfarg_oparg = zfoparg;
2N/A err = zfcb(&zfarg);
2N/A if (!zfarg.zfarg_inglobalzone)
2N/A _exit(err);
2N/A return (err);
2N/A}
2N/A
2N/A/*
2N/A * Determine the PERMITTED privilege set and elevate privileges to that set.
2N/A * This is used in for open(), rename() and unlink() relative to the zoneid
2N/A * calls after zone_enter(). We cannot use netcfg_elevate_privileges() here
2N/A * because it tries to add the "zone" privileges which is "all" privileges
2N/A * since we are in the global zone. However, setppriv() fails because the
2N/A * zone does not have "all" privileges in its PERMITTED set.
2N/A */
2N/Astatic int
2N/Anetcfg_elevate_permitted_privileges(void)
2N/A{
2N/A priv_set_t *pset;
2N/A int err = 0;
2N/A
2N/A if ((pset = priv_allocset()) == NULL)
2N/A return (errno);
2N/A
2N/A if (getppriv(PRIV_PERMITTED, pset) == -1) {
2N/A err = errno;
2N/A goto done;
2N/A }
2N/A if (setppriv(PRIV_SET, PRIV_EFFECTIVE, pset) == -1)
2N/A err = errno;
2N/A
2N/Adone:
2N/A priv_freeset(pset);
2N/A return (err);
2N/A}
2N/A
2N/Astatic int
2N/Anetcfg_zopen_cb(zfarg_t *zfarg)
2N/A{
2N/A struct strrecvfd recvfd;
2N/A boolean_t newfile = B_FALSE;
2N/A boolean_t inglobalzone = zfarg->zfarg_inglobalzone;
2N/A boolean_t finglobalzone = zfarg->zfarg_finglobalzone;
2N/A const char *filename = zfarg->zfarg_filename;
2N/A const char *modestr = zfarg->zfarg_openarg.zopen_modestr;
2N/A int *p = zfarg->zfarg_openarg.zopen_pipe;
2N/A struct stat statbuf;
2N/A int oflags;
2N/A mode_t mode;
2N/A int fd = -1;
2N/A int err;
2N/A
2N/A /*
2N/A * If the caller just wants to open() the file, the oflags and mode
2N/A * args were passed in directly. For fopen(), we were passed a
2N/A * modestr that is used to set oflags and mode. In the latter case,
2N/A * we assume modestr is either 'r' or 'w'; i.e. we only ever open a
2N/A * file for reading or writing, not both.
2N/A */
2N/A if (modestr == NULL) {
2N/A oflags = zfarg->zfarg_openarg.zopen_oflag;
2N/A mode = zfarg->zfarg_openarg.zopen_mode;
2N/A } else {
2N/A oflags = (modestr[0] == 'r') ?
2N/A O_RDONLY : O_WRONLY | O_CREAT | O_TRUNC;
2N/A mode = (modestr[0] == 'r') ?
2N/A 0 : S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH;
2N/A }
2N/A
2N/A /* Open the file if we're in the same zone as the file. */
2N/A if (inglobalzone == finglobalzone) {
2N/A /*
2N/A * First determine if we will be creating the file as part of
2N/A * opening it. If so, then we'll need to ensure that it has
2N/A * the proper ownership after having opened it.
2N/A */
2N/A if ((err = netcfg_elevate_permitted_privileges()) != 0)
2N/A return (err);
2N/A
2N/A if (oflags & O_CREAT) {
2N/A if (stat(filename, &statbuf) == -1) {
2N/A if (errno == ENOENT) {
2N/A newfile = B_TRUE;
2N/A } else {
2N/A (void) netcfg_drop_privileges();
2N/A return (errno);
2N/A }
2N/A }
2N/A }
2N/A
2N/A fd = open(filename, oflags, mode);
2N/A err = errno;
2N/A if (fd < 0) {
2N/A (void) netcfg_drop_privileges();
2N/A return (err);
2N/A }
2N/A
2N/A if (newfile) {
2N/A if (chown(filename, UID_NETCFG, GID_NETADM) == -1) {
2N/A err = errno;
2N/A (void) close(fd);
2N/A (void) netcfg_drop_privileges();
2N/A return (err);
2N/A }
2N/A }
2N/A (void) netcfg_drop_privileges();
2N/A }
2N/A
2N/A /*
2N/A * If we're not in the global zone, send the file-descriptor back to
2N/A * our parent in the global zone.
2N/A */
2N/A if (!inglobalzone) {
2N/A assert(!finglobalzone);
2N/A assert(fd != -1);
2N/A return (ioctl(p[1], I_SENDFD, fd) == -1 ? errno : 0);
2N/A }
2N/A
2N/A /*
2N/A * At this point, we know we're in the global zone. If the file was
2N/A * in a non-global zone, receive the file-descriptor from our child in
2N/A * the non-global zone.
2N/A */
2N/A if (!finglobalzone) {
2N/A if (ioctl(p[0], I_RECVFD, &recvfd) == -1) {
2N/A (void) close(fd);
2N/A return (errno);
2N/A }
2N/A fd = recvfd.fd;
2N/A }
2N/A
2N/A zfarg->zfarg_openarg.zopen_fd = fd;
2N/A /* Parfait_ALLOW file-desc-leak fd is passed back to parent */
2N/A return (0);
2N/A}
2N/A
2N/Astatic int
2N/Anetcfg_zunlink_cb(zfarg_t *zfarg)
2N/A{
2N/A int err = 0;
2N/A
2N/A if (zfarg->zfarg_inglobalzone != zfarg->zfarg_finglobalzone)
2N/A return (0);
2N/A
2N/A if ((err = netcfg_elevate_permitted_privileges()) == 0) {
2N/A if (unlink(zfarg->zfarg_filename) != 0)
2N/A err = errno;
2N/A (void) netcfg_drop_privileges();
2N/A }
2N/A return (err);
2N/A}
2N/A
2N/Astatic int
2N/Anetcfg_zrename_cb(zfarg_t *zfarg)
2N/A{
2N/A int err = 0;
2N/A
2N/A if (zfarg->zfarg_inglobalzone != zfarg->zfarg_finglobalzone)
2N/A return (0);
2N/A
2N/A if ((err = netcfg_elevate_permitted_privileges()) == 0) {
2N/A if (rename(zfarg->zfarg_filename,
2N/A zfarg->zfarg_renamearg.zrename_newname) != 0)
2N/A err = errno;
2N/A (void) netcfg_drop_privileges();
2N/A }
2N/A return (err);
2N/A}
2N/A
2N/Astatic int
2N/Anetcfg_zopen_impl(const char *filename, zoneid_t zoneid, zfcb_t cb,
2N/A zfoparg_t *zfoparg)
2N/A{
2N/A int p[2];
2N/A int err;
2N/A
2N/A if (zoneid != GLOBAL_ZONEID && pipe(p) == -1)
2N/A return (errno);
2N/A
2N/A zfoparg->zfop_openarg.zopen_pipe = p;
2N/A err = netcfg_zfop(filename, zoneid, cb, zfoparg);
2N/A if (zoneid != GLOBAL_ZONEID) {
2N/A (void) close(p[0]);
2N/A (void) close(p[1]);
2N/A }
2N/A return (err);
2N/A}
2N/A
2N/A/*
2N/A * Same as open(2), except that it opens the file relative to zoneid's zone
2N/A * root.
2N/A */
2N/Aint
2N/Anetcfg_zopen(const char *filename, int oflag, mode_t mode, zoneid_t zoneid,
2N/A int *err)
2N/A{
2N/A zfoparg_t zfoparg;
2N/A
2N/A zfoparg.zfop_openarg.zopen_oflag = oflag;
2N/A zfoparg.zfop_openarg.zopen_mode = mode;
2N/A zfoparg.zfop_openarg.zopen_modestr = NULL;
2N/A *err = netcfg_zopen_impl(filename, zoneid, netcfg_zopen_cb, &zfoparg);
2N/A return (zfoparg.zfop_openarg.zopen_fd);
2N/A}
2N/A
2N/A/*
2N/A * Same as fopen(3C), except that it opens the file relative to zoneid's zone
2N/A * root.
2N/A */
2N/AFILE *
2N/Anetcfg_zfopen(const char *filename, const char *modestr, zoneid_t zoneid,
2N/A int *err)
2N/A{
2N/A zfoparg_t zfoparg;
2N/A FILE *fp;
2N/A
2N/A zfoparg.zfop_openarg.zopen_modestr = modestr;
2N/A *err = netcfg_zopen_impl(filename, zoneid, netcfg_zopen_cb, &zfoparg);
2N/A if (*err != 0)
2N/A return (NULL);
2N/A
2N/A fp = fdopen(zfoparg.zfop_openarg.zopen_fd, modestr);
2N/A if (fp == NULL) {
2N/A *err = errno;
2N/A (void) close(zfoparg.zfop_openarg.zopen_fd);
2N/A }
2N/A return (fp);
2N/A}
2N/A
2N/A/*
2N/A * Same as rename(2), except that old and new are relative to zoneid's zone
2N/A * root.
2N/A */
2N/Aint
2N/Anetcfg_zrename(const char *old, const char *new, zoneid_t zoneid)
2N/A{
2N/A zfoparg_t zfoparg;
2N/A
2N/A zfoparg.zfop_renamearg.zrename_newname = new;
2N/A return (netcfg_zfop(old, zoneid, netcfg_zrename_cb, &zfoparg));
2N/A}
2N/A
2N/A/*
2N/A * Same as unlink(2), except that filename is relative to zoneid's zone root.
2N/A */
2N/Aint
2N/Anetcfg_zunlink(const char *filename, zoneid_t zoneid)
2N/A{
2N/A return (netcfg_zfop(filename, zoneid, netcfg_zunlink_cb, NULL));
2N/A}