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