Skip to content

Commit

Permalink
enter: pass nsactions array rather than path array
Browse files Browse the repository at this point in the history
Before this commit, we used to construct shared namespace paths purely
from string operations; that is, --share pid,mnt=/foo would be
reconstructed as --share pid=/foo/pid --share mnt=/foo/mnt. The problem
with this approach is that this doesn't work well with magic links to
file descriptors in /proc.

Instead, we now open the directory path and use its file descriptor to
open further nsfs files.
  • Loading branch information
Snaipe committed Nov 28, 2024
1 parent e113f22 commit 54cff3d
Show file tree
Hide file tree
Showing 5 changed files with 95 additions and 68 deletions.
7 changes: 3 additions & 4 deletions enter.c
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ int enter(struct entry_settings *opts)
}

int timens_offsets = -1;
if (opts->shares[NS_TIME] != SHARE_WITH_PARENT) {
if (opts->nsactions[NS_TIME] != NSACTION_SHARE_WITH_PARENT) {

/* Because this process is privileged, /proc/self/timens_offsets
is unfortunately owned by root and not ourselves, so we have
Expand All @@ -158,14 +158,13 @@ int enter(struct entry_settings *opts)
/* The kernel evidently doesn't support time namespaces yet.
Don't try to open the time namespace file with --share-all=<dir>,
or try to unshare or setns the time namespace below. */
opts->shares[NS_TIME] = SHARE_WITH_PARENT;
opts->nsactions[NS_TIME] = NSACTION_SHARE_WITH_PARENT;
}

reset_capabilities();
}

enum nsaction nsactions[MAX_NS];
opts_to_nsactions(opts->shares, nsactions);
enum nsaction *nsactions = opts->nsactions;

if (nsactions[NS_NET] != NSACTION_UNSHARE && opts->nnics > 0) {
errx(1, "cannot create NICs when not in a network namespace");
Expand Down
10 changes: 2 additions & 8 deletions enter.h
Original file line number Diff line number Diff line change
Expand Up @@ -48,15 +48,9 @@ enum {
MAX_CLOSE_FDS = 4096,
};

/* SHARE_WITH_PARENT is a special value for entry_settings.shares[ns]. */
# define SHARE_WITH_PARENT ((char *) -1)

struct entry_settings {
/* shares[] is indexed by SHARE_CGROUP, etc. Legal values are:
NULL: unshare.
SHARE_WITH_PARENT: special marker meaning don't unshare or setns.
filename: setns to the given namespace file. */
const char *shares[MAX_NS];
/* nsactions[] is indexed by NS_CGROUP, etc. */
enum nsaction nsactions[MAX_NS];
const char *persist[MAX_NS];

const char *pathname;
Expand Down
115 changes: 88 additions & 27 deletions main.c
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@
#include <ctype.h>
#include <err.h>
#include <errno.h>
#include <fcntl.h>
#include <getopt.h>
#include <inttypes.h>
#include <limits.h>
Expand Down Expand Up @@ -73,25 +74,25 @@ enum {
OPTION_NO_COPY_HARD_RLIMITS,
};

static void process_nslist_entry(const char **out, const char *share, const char *path, int append_nsname)
/* SHARE_WITH_PARENT is a special value for the path passed to process_nslist_entry
* that means sharing with the parent. */
# define SHARE_WITH_PARENT ((char *) -1)

static enum nstype find_nstype_by_name(const char *nsname)
{
if (!strcmp(share, "network")) {
share = "net";
if (!strcmp(nsname, "network")) {
nsname = "net";
}
if (!strcmp(share, "mount")) {
share = "mnt";
if (!strcmp(nsname, "mount")) {
nsname = "mnt";
}
for (enum nstype ns = 0; ns < MAX_NS; ns++) {
if (!strcmp(share, ns_name(ns))) {
if (append_nsname) {
out[ns] = strdup(makepath("%s/%s", path, ns_name(ns)));
} else {
out[ns] = path;
}
return;
if (!strcmp(nsname, ns_name(ns))) {
return ns;
}
}
fprintf(stderr, "namespace `%s` does not exist.\n", share);

fprintf(stderr, "namespace `%s` does not exist.\n", nsname);
fprintf(stderr, "valid namespaces are: ");
for (enum nstype ns = 0; ns < MAX_NS; ns++) {
fprintf(stderr, "%s%s", ns == 0 ? "" : ", ", ns_name(ns));
Expand All @@ -100,9 +101,56 @@ static void process_nslist_entry(const char **out, const char *share, const char
exit(2);
}

static void process_nslist_entry(enum nsaction *nsactions, const char *nsname, int dirfd, const char *path)
{
enum nstype ns = find_nstype_by_name(nsname);
enum nsaction *action = &nsactions[ns];

if (*action >= 0) {
close(*action);
}

if (path == NULL) {
*action = NSACTION_UNSHARE;
} else if (path == SHARE_WITH_PARENT) {
*action = NSACTION_SHARE_WITH_PARENT;
} else {
/* nsname might not be the canonical filename, so fix it */
nsname = ns_name(ns);

int fd = openat(dirfd, nsname, O_RDONLY | O_CLOEXEC);
if (fd == -1) {
err(1, "open <nsdir>/%s", nsname);
}

/* If the nsfd refers to the same namespace as the one we are
currently in, treat as for SHARE_WITH_PARENT.
We want to give semantics to --share <ns>=/proc/self/ns/<ns>
being the same as --share <ns>. */
if (is_nsfd_current(fd, nsname)) {
close(fd);
fd = NSACTION_SHARE_WITH_PARENT;
}

*action = fd;
}
}

static void build_ns_path(const char **out, const char *nsname, const char *path, int append_nsname)
{
enum nstype ns = find_nstype_by_name(nsname);

if (append_nsname) {
out[ns] = strdup(makepath("%s/%s", path, ns_name(ns)));
} else {
out[ns] = path;
}
}

#define ALL_NAMESPACES "cgroup,ipc,mnt,net,pid,time,user,uts"

static void process_share(const char **out, const char *optarg)
static void process_share(enum nsaction *nsactions, const char *optarg)
{
char *nsnames = strtok((char *) optarg, "=");
char *path = strtok(NULL, "");
Expand All @@ -119,18 +167,27 @@ static void process_share(const char **out, const char *optarg)
}

size_t nsnames_len = strlen(nsnames);
const char *share = strtok(nsnames, ",");
const char *nsname = strtok(nsnames, ",");

int dirfd = AT_FDCWD;

/* Only append the ns name to the path if there is more than one
namespace specified. */
bool append_nsname = share + strlen(share) != nsnames + nsnames_len;
if (path == NULL) {
path = SHARE_WITH_PARENT;
append_nsname = false;
} else if (nsname + strlen(nsname) != nsnames + nsnames_len) {
/* We have more that one ns name specified, which means the path
refers to a directory of nsfs files */
dirfd = open(path, O_PATH | O_DIRECTORY | O_CLOEXEC);
if (dirfd == -1) {
err(1, "open %s", path);
}
}

for (; share != NULL; share = strtok(NULL, ",")) {
process_nslist_entry(out, share, path, append_nsname);
for (; nsname != NULL; nsname = strtok(NULL, ",")) {
process_nslist_entry(nsactions, nsname, dirfd, path);
}

if (dirfd != AT_FDCWD) {
close(dirfd);
}
}

Expand Down Expand Up @@ -158,11 +215,11 @@ static void process_persist(const char **out, const char *optarg)
bool multiple = share + strlen(share) != nsnames + nsnames_len;

for (; share != NULL; share = strtok(NULL, ",")) {
process_nslist_entry(out, share, path, multiple);
build_ns_path(out, share, path, multiple);
}
}

static void process_unshare(const char **out, char *nsnames)
static void process_unshare(enum nsaction *nsactions, char *nsnames)
{
/* Similar rules as for process_share, but we do not take in paths. */

Expand All @@ -172,7 +229,7 @@ static void process_unshare(const char **out, char *nsnames)
}

for (const char *ns = strtok(nsnames, ","); ns != NULL; ns = strtok(NULL, ",")) {
process_nslist_entry(out, ns, NULL, false);
process_nslist_entry(nsactions, ns, 0, NULL);
}
}

Expand Down Expand Up @@ -279,6 +336,10 @@ int main(int argc, char *argv[], char *envp[])
opts.umask = (mode_t) -1;
opts.cgroup_driver = (enum cgroup_driver) -1;

for (enum nstype ns = 0; ns < MAX_NS; ns++) {
opts.nsactions[ns] = NSACTION_UNSHARE;
}

static struct option options[] = {
{ "help", no_argument, NULL, 'h' },
{ "root", required_argument, NULL, 'r' },
Expand Down Expand Up @@ -431,11 +492,11 @@ int main(int argc, char *argv[], char *envp[])
break;

case OPTION_SHARE:
process_share(opts.shares, optarg);
process_share(opts.nsactions, optarg);
break;

case OPTION_UNSHARE:
process_unshare(opts.shares, optarg);
process_unshare(opts.nsactions, optarg);
break;

case OPTION_PERSIST:
Expand Down Expand Up @@ -872,7 +933,7 @@ int main(int argc, char *argv[], char *envp[])

/* Use our own default init if we unshare the pid namespace, and no
--init has been specified. */
if (opts.shares[NS_PID] == NULL && opts.init == NULL) {
if (opts.nsactions[NS_PID] == NSACTION_UNSHARE && opts.init == NULL) {
opts.init = BINDIR "/bst-init";
}

Expand Down
29 changes: 1 addition & 28 deletions ns.c
Original file line number Diff line number Diff line change
Expand Up @@ -40,7 +40,7 @@ int ns_cloneflag(enum nstype ns)
return flags[ns].flag;
}

static bool is_nsfd_current(int nsfd, const char *name)
bool is_nsfd_current(int nsfd, const char *name)
{
const char *path = makepath("/proc/self/ns/%s", name);

Expand All @@ -61,33 +61,6 @@ static bool is_nsfd_current(int nsfd, const char *name)
return self.st_ino == stat.st_ino;
}

void opts_to_nsactions(const char *shares[], enum nsaction *nsactions)
{
for (enum nstype ns = 0; ns < MAX_NS; ns++) {
const char *share = shares[ns];
if (share == NULL) {
nsactions[ns] = NSACTION_UNSHARE;
} else if (share == SHARE_WITH_PARENT) {
nsactions[ns] = NSACTION_SHARE_WITH_PARENT;
} else {
nsactions[ns] = open(share, O_RDONLY | O_CLOEXEC);
if (nsactions[ns] < 0) {
err(1, "open %s", share);
}

/* If the nsfd refers to the same namespace as the one we are
currently in, treat as for SHARE_WITH_PARENT.
We want to give semantics to --share <ns>=/proc/self/ns/<ns>
being the same as --share <ns>. */
if (is_nsfd_current(nsactions[ns], ns_name(ns))) {
close(nsactions[ns]);
nsactions[ns] = NSACTION_SHARE_WITH_PARENT;
}
}
}
}

static int is_setns(const struct nsid *ns)
{
switch (ns->action) {
Expand Down
2 changes: 1 addition & 1 deletion ns.h
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,7 @@ struct nsid {
const char *ns_name(enum nstype);
int ns_cloneflag(enum nstype);

void opts_to_nsactions(const char *shares[], enum nsaction *nsactions);
bool is_nsfd_current(int nsfd, const char *name);
void ns_enter_prefork(struct nsid *namespaces, size_t *len);
void ns_enter_postfork(struct nsid *namespaces, size_t len);

Expand Down

0 comments on commit 54cff3d

Please sign in to comment.