Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
Already on GitHub? Sign in to your account
cmd/snap-update-ns: add C preamble for setns #3095
Merged
Commits
Show all changes
27 commits
Select commit
Hold shift + click to select a range
b649089
cmd/snap-update-ns: add C preamble for setns
zyga 32170e7
cmd/snap-update-ns: move C parts to separate file
zyga 3872f38
cmd/snap-update-ns: move C code to dedicated files
zyga f70c288
cmd/snap-update-ns: remove Go headers
zyga 14069c1
cmd/snap-update-ns: handle argc == 1 better
zyga fcf684d
cmd/snap-update-ns: tweak code style
zyga 7020c4d
cmd/snap-update-ns: do trivial validation of snap-name in C
zyga 7871e51
cmd/snap-update-ns: add tests for C code
zyga cf0f73a
cmd/snap-update-ns: tweak everything to make testing work
zyga 0748aea
cmd/snap-update-ns: separate error conditions for buffer-full
zyga d36c901
cmd/snap-update-ns: simplify error propagation
zyga 652d769
cmd/snap-update-ns: explain bootstrap enale trick
zyga 11d4ac3
cmd/snap-update-ns: return -1 if we cannot fit cmdline into buffer
zyga afc60ed
cmd/snap-update-ns: fix typo
zyga a79cef5
tests: add initial (partial) test for snap-update-ns
zyga b91479c
cmd/snap-update-ns: add Commentf for failing test
zyga 5a388a6
cmd/snap-update-ns: fix test to work with coverage
zyga bee98df
cmd/snap-update-ns: rename sanitize to partially_validate
zyga 8e638d3
Merge branch 'master' of github.com:snapcore/snapd into setns-on-startup
zyga 07bd08b
Merge branch 'master' of github.com:snapcore/snapd into setns-on-startup
zyga 53711c6
Merge branch 'master' of github.com:snapcore/snapd into setns-on-startup
zyga 1fb6386
cmd/snap-update-ns: simplify find_snap_name
zyga db33d6b
cmd/snap-update-ns: move trivial logic into find_snap_name
zyga 55b4be4
cmd/snap-update-ns: skip boostrap while testing, drop SNAPD_INTERNAL
zyga ee6dcc7
Merge branch 'master' of github.com:snapcore/snapd into setns-on-startup
zyga 26b202e
cmd/snap-update-ns: remove pointless check
zyga 7da913d
Merge branch 'master' of github.com:snapcore/snapd into setns-on-startup
zyga
Jump to file or symbol
Failed to load files and symbols.
| @@ -0,0 +1,187 @@ | ||
| +/* | ||
| + * Copyright (C) 2017 Canonical Ltd | ||
| + * | ||
| + * This program is free software: you can redistribute it and/or modify | ||
| + * it under the terms of the GNU General Public License version 3 as | ||
| + * published by the Free Software Foundation. | ||
| + * | ||
| + * This program is distributed in the hope that it will be useful, | ||
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| + * GNU General Public License for more details. | ||
| + * | ||
| + * You should have received a copy of the GNU General Public License | ||
| + * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| + * | ||
| + */ | ||
| + | ||
| +#include "bootstrap.h" | ||
zyga
Contributor
|
||
| + | ||
| +#include <errno.h> | ||
| +#include <fcntl.h> | ||
| +#include <limits.h> | ||
| +#include <sched.h> | ||
| +#include <stdio.h> | ||
| +#include <stdlib.h> | ||
| +#include <string.h> | ||
| +#include <sys/stat.h> | ||
| +#include <sys/types.h> | ||
| +#include <unistd.h> | ||
| + | ||
| +// bootstrap_errno contains a copy of errno if a system call fails. | ||
| +int bootstrap_errno = 0; | ||
| +// bootstrap_msg contains a static string if something fails. | ||
| +const char* bootstrap_msg = NULL; | ||
| + | ||
| +// read_cmdline reads /proc/self/cmdline into the specified buffer, returning | ||
| +// number of bytes read. | ||
| +ssize_t read_cmdline(char* buf, size_t buf_size) | ||
| +{ | ||
| + int fd = open("/proc/self/cmdline", O_RDONLY | O_CLOEXEC | O_NOFOLLOW); | ||
| + if (fd < 0) { | ||
| + bootstrap_errno = errno; | ||
| + bootstrap_msg = "cannot open /proc/self/cmdline"; | ||
| + return -1; | ||
| + } | ||
| + memset(buf, 0, buf_size); | ||
| + ssize_t num_read = read(fd, buf, buf_size); | ||
| + if (num_read < 0) { | ||
| + bootstrap_errno = errno; | ||
| + bootstrap_msg = "cannot read /proc/self/cmdline"; | ||
| + } else if (num_read == buf_size) { | ||
| + bootstrap_errno = 0; | ||
| + bootstrap_msg = "cannot fit all of /proc/self/cmdline, buffer too small"; | ||
mvo5
Collaborator
|
||
| + num_read = -1; | ||
| + } | ||
| + close(fd); | ||
| + return num_read; | ||
| +} | ||
| + | ||
| +// find_argv0 scans the command line buffer and looks for the 0st argument. | ||
| +const char* | ||
| +find_argv0(char* buf, size_t num_read) | ||
| +{ | ||
| + // cmdline is an array of NUL ('\0') separated strings. | ||
| + size_t argv0_len = strnlen(buf, num_read); | ||
| + if (argv0_len == num_read) { | ||
| + // ensure that the buffer is properly terminated. | ||
| + return NULL; | ||
| + } | ||
| + return buf; | ||
| +} | ||
| + | ||
| +// find_snap_name scans the command line buffer and looks for the 1st argument. | ||
| +// if the 1st argument exists but is empty NULL is returned. | ||
| +const char* | ||
| +find_snap_name(char* buf, size_t num_read) | ||
| +{ | ||
| + // cmdline is an array of NUL ('\0') separated strings. We can skip over | ||
| + // the first entry (program name) and look at the second entry, in our case | ||
| + // it should be the snap name. | ||
| + size_t argv0_len = strnlen(buf, num_read); | ||
| + if (argv0_len + 1 >= num_read) { | ||
| + return NULL; | ||
| + } | ||
| + char* snap_name = &buf[argv0_len + 1]; | ||
| + if (*snap_name == '\0') { | ||
| + return NULL; | ||
| + } | ||
| + return snap_name; | ||
| +} | ||
| + | ||
| +// setns_into_snap switches mount namespace into that of a given snap. | ||
| +static int | ||
| +setns_into_snap(const char* snap_name) | ||
| +{ | ||
| + // Construct the name of the .mnt file to open. | ||
| + char buf[PATH_MAX]; | ||
| + int n = snprintf(buf, sizeof buf, "/run/snapd/ns/%s.mnt", snap_name); | ||
| + if (n >= sizeof buf || n < 0) { | ||
| + bootstrap_errno = errno; | ||
| + bootstrap_msg = "cannot format mount namespace file name"; | ||
| + return -1; | ||
| + } | ||
| + | ||
| + // Open the mount namespace file, note that we don't specify O_NOFOLLOW as | ||
| + // that file is always a special, broken symbolic link. | ||
| + int fd = open(buf, O_RDONLY | O_CLOEXEC); | ||
| + if (fd < 0) { | ||
| + bootstrap_errno = errno; | ||
| + bootstrap_msg = "cannot open mount namespace file"; | ||
| + return -1; | ||
| + } | ||
| + | ||
| + // Switch to the mount namespace of the given snap. | ||
| + int err = setns(fd, CLONE_NEWNS); | ||
| + if (err < 0) { | ||
| + bootstrap_errno = errno; | ||
| + bootstrap_msg = "cannot switch mount namespace"; | ||
| + }; | ||
| + | ||
| + close(fd); | ||
| + return err; | ||
| +} | ||
| + | ||
| +// partially_validate_snap_name performs partial validation of the given name. | ||
| +// The goal is to ensure that there are no / or .. in the name. | ||
| +int partially_validate_snap_name(const char* snap_name) | ||
| +{ | ||
| + // NOTE: neither set bootstrap_{msg,errno} but the return value means that | ||
| + // bootstrap does nothing. The name is re-validated by golang. | ||
| + if (strstr(snap_name, "..") != NULL) { | ||
| + return -1; | ||
| + } | ||
| + if (strchr(snap_name, '/') != NULL) { | ||
| + return -1; | ||
| + } | ||
| +} | ||
| + | ||
| +// bootstrap prepares snap-update-ns to work in the namespace of the snap given | ||
| +// on command line. | ||
| +void bootstrap(void) | ||
| +{ | ||
| + // We don't have argc/argv so let's imitate that by reading cmdline | ||
| + char cmdline[1024]; | ||
| + memset(cmdline, 0, sizeof cmdline); | ||
| + ssize_t num_read; | ||
| + if ((num_read = read_cmdline(cmdline, sizeof cmdline)) < 0) { | ||
| + return; | ||
| + } | ||
| + | ||
| + // Find the name of the called program. If it is ending with "-test" then do nothing. | ||
| + // NOTE: This lets us use cgo/go to write tests without running the bulk | ||
| + // of the code automatically. In snapd we can just set the required | ||
| + // environment variable. | ||
| + const char* argv0 = find_argv0(cmdline, (size_t)num_read); | ||
| + if (argv0 == NULL) { | ||
| + bootstrap_errno = 0; | ||
| + bootstrap_msg = "argv0 is corrupted"; | ||
| + return; | ||
| + } | ||
| + const char* argv0_suffix_maybe = strstr(argv0, ".test"); | ||
| + if (argv0_suffix_maybe != NULL && argv0_suffix_maybe[strlen(".test")] == '\0') { | ||
| + bootstrap_errno = 0; | ||
| + bootstrap_msg = "bootstrap is not enabled while testing"; | ||
| + return; | ||
| + } | ||
| + | ||
| + // Find the name of the snap by scanning the cmdline. If there's no snap | ||
| + // name given, just bail out. The go parts will scan this too. | ||
| + const char* snap_name = find_snap_name(cmdline, (size_t)num_read); | ||
| + if (snap_name == NULL) { | ||
| + return; | ||
| + } | ||
| + | ||
| + // Look for known offenders in the snap name (.. and /) and do nothing if | ||
| + // those are found. The golang code will validate snap name and print a | ||
| + // proper error message but this just ensures we don't try to open / setns | ||
| + // anything unusual. | ||
|
|
||
| + if (partially_validate_snap_name(snap_name) < 0) { | ||
| + return; | ||
| + } | ||
| + | ||
| + // Switch the mount namespace to that of the snap given on command line. | ||
| + if (setns_into_snap(snap_name) < 0) { | ||
| + return; | ||
| + } | ||
| +} | ||
| @@ -0,0 +1,85 @@ | ||
| +// -*- Mode: Go; indent-tabs-mode: t -*- | ||
| + | ||
| +/* | ||
| + * Copyright (C) 2017 Canonical Ltd | ||
| + * | ||
| + * This program is free software: you can redistribute it and/or modify | ||
| + * it under the terms of the GNU General Public License version 3 as | ||
| + * published by the Free Software Foundation. | ||
| + * | ||
| + * This program is distributed in the hope that it will be useful, | ||
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| + * GNU General Public License for more details. | ||
| + * | ||
| + * You should have received a copy of the GNU General Public License | ||
| + * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| + * | ||
| + */ | ||
| + | ||
| +package main | ||
| + | ||
| +// Use a pre-main helper to switch the mount namespace. This is required as | ||
| +// golang creates threads at will and setns(..., CLONE_NEWNS) fails if any | ||
| +// threads apart from the main thread exist. | ||
| + | ||
| +/* | ||
| + | ||
| +#include <stdlib.h> | ||
| +#include "bootstrap.h" | ||
| + | ||
| +__attribute__((constructor)) static void init(void) { | ||
| + bootstrap(); | ||
| +} | ||
| + | ||
| +// NOTE: do not add anything before the following `import "C"' | ||
| +*/ | ||
| +import "C" | ||
| + | ||
| +import ( | ||
| + "fmt" | ||
| + "syscall" | ||
| + "unsafe" | ||
| +) | ||
| + | ||
| +// Error returns error (if any) encountered in pre-main C code. | ||
| +func BootstrapError() error { | ||
| + if C.bootstrap_msg == nil { | ||
| + return nil | ||
| + } | ||
| + errno := syscall.Errno(C.bootstrap_errno) | ||
| + if errno != 0 { | ||
| + return fmt.Errorf("%s: %s", C.GoString(C.bootstrap_msg), errno) | ||
| + } | ||
| + return fmt.Errorf("%s", C.GoString(C.bootstrap_msg)) | ||
| +} | ||
| + | ||
| +// readCmdline is a wrapper around the C function read_cmdline. | ||
| +func readCmdline(buf []byte) C.ssize_t { | ||
| + return C.read_cmdline((*C.char)(unsafe.Pointer(&buf[0])), C.size_t(cap(buf))) | ||
| +} | ||
| + | ||
| +// findArgv0 parses the argv-like array and finds the 0st argument. | ||
| +func findArgv0(buf []byte) *string { | ||
| + if ptr := C.find_argv0((*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))); ptr != nil { | ||
| + str := C.GoString(ptr) | ||
| + return &str | ||
| + } | ||
| + return nil | ||
| +} | ||
| + | ||
| +// findSnapName parses the argv-like array and finds the 1st argument. | ||
| +func findSnapName(buf []byte) *string { | ||
| + if ptr := C.find_snap_name((*C.char)(unsafe.Pointer(&buf[0])), C.size_t(len(buf))); ptr != nil { | ||
| + str := C.GoString(ptr) | ||
| + return &str | ||
| + } | ||
| + return nil | ||
| +} | ||
| + | ||
| +// partiallyValidateSnapName checks if snap name is seemingly valid. | ||
| +// The real part of the validation happens on the go side. | ||
| +func partiallyValidateSnapName(snapName string) int { | ||
| + cStr := C.CString(snapName) | ||
| + return int(C.partially_validate_snap_name(cStr)) | ||
| +} |
| @@ -0,0 +1,34 @@ | ||
| +/* | ||
| + * Copyright (C) 2017 Canonical Ltd | ||
| + * | ||
| + * This program is free software: you can redistribute it and/or modify | ||
| + * it under the terms of the GNU General Public License version 3 as | ||
| + * published by the Free Software Foundation. | ||
| + * | ||
| + * This program is distributed in the hope that it will be useful, | ||
| + * but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
| + * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the | ||
| + * GNU General Public License for more details. | ||
| + * | ||
| + * You should have received a copy of the GNU General Public License | ||
| + * along with this program. If not, see <http://www.gnu.org/licenses/>. | ||
| + * | ||
| + */ | ||
| + | ||
morphis
Contributor
|
||
| +#ifndef SNAPD_CMD_SNAP_UPDATE_NS_H | ||
| +#define SNAPD_CMD_SNAP_UPDATE_NS_H | ||
| + | ||
| +#define _GNU_SOURCE | ||
| + | ||
| +#include <unistd.h> | ||
| + | ||
| +extern int bootstrap_errno; | ||
| +extern const char* bootstrap_msg; | ||
| + | ||
| +void bootstrap(void); | ||
| +ssize_t read_cmdline(char* buf, size_t buf_size); | ||
| +const char* find_argv0(char* buf, size_t num_read); | ||
| +const char* find_snap_name(char* buf, size_t num_read); | ||
| +int partially_validate_snap_name(const char* snap_name); | ||
| + | ||
| +#endif | ||
Oops, something went wrong.
Not really needed.