X-Git-Url: http://git.onelab.eu/?a=blobdiff_plain;f=src%2Fsecure-mount.c;fp=src%2Fsecure-mount.c;h=520a0859c0d90c033b918148f9735ce11279d55d;hb=8cf13bb177d92c93eb73dc8939777150536c2d00;hp=0000000000000000000000000000000000000000;hpb=6bf3f95de36c804c97716b2d0bdf10680c559044;p=util-vserver.git diff --git a/src/secure-mount.c b/src/secure-mount.c new file mode 100644 index 0000000..520a085 --- /dev/null +++ b/src/secure-mount.c @@ -0,0 +1,706 @@ +// $Id: secure-mount.c,v 1.24 2005/03/24 12:45:06 ensc Exp $ --*- c++ -*-- + +// Copyright (C) 2003 Enrico Scholz +// +// This program is free software; you can redistribute it and/or modify +// it under the terms of the GNU General Public License as published by +// the Free Software Foundation; version 2 of the License. +// +// 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, write to the Free Software +// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. + + + // secure-mount [--chroot] + // [--mtab ] [--fstab ] + // + // Executes mount-operations under the current directory: it assumes sources + // in the current root-dir while destinations are expected in the chroot + // environment. + + +#ifdef HAVE_CONFIG_H +# include +#endif + +#include "util.h" +#include "pathconfig.h" + +#include + +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#define ENSC_WRAPPERS_FCNTL 1 +#define ENSC_WRAPPERS_UNISTD 1 +#include + +#define MNTPOINT "/etc" + +typedef enum { rfsYES, rfsNO, rfsONLY } RootFsOption; + +struct MountInfo { + char const * src; + char const * dst; + char const * type; + unsigned long flag; + unsigned long xflag; + unsigned long mask; + char * data; +}; + +struct Options { + char const * mtab; + char const * fstab; + bool do_chroot; + bool ignore_mtab; + bool mount_all; + RootFsOption rootfs; + + int cur_dir_fd; + int cur_rootdir_fd; +}; + +#define OPTION_BIND 1024 +#define OPTION_MOVE 1025 +#define OPTION_MTAB 1026 +#define OPTION_FSTAB 1027 +#define OPTION_CHROOT 1028 +#define OPTION_SECURE 1029 +#define OPTION_RBIND 1030 +#define OPTION_ROOTFS 1031 + +#define XFLAG_NOAUTO 0x01 + +static struct option const +CMDLINE_OPTIONS[] = { + { "help", no_argument, 0, 'h' }, + { "version", no_argument, 0, 'v' }, + { "bind", no_argument, 0, OPTION_BIND }, + { "move", no_argument, 0, OPTION_MOVE }, + { "mtab", required_argument, 0, OPTION_MTAB }, + { "fstab", required_argument, 0, OPTION_FSTAB }, + { "rootfs", required_argument, 0, OPTION_ROOTFS }, + { "chroot", no_argument, 0, OPTION_CHROOT }, + { "secure", no_argument, 0, OPTION_SECURE }, + { "rbind", no_argument, 0, OPTION_RBIND }, + { 0, 0, 0, 0 } +}; + +#ifndef MS_REC +# define MS_REC 0x4000 +#endif + +static struct FstabOption { + char const * const opt; + unsigned long const flag; + unsigned long const mask; + unsigned long const xflag; + bool const is_dflt; +} const FSTAB_OPTIONS[] = { + { "defaults", 0, (MS_RDONLY|MS_NOSUID|MS_NODEV|MS_NOEXEC| + MS_SYNCHRONOUS), 0, false }, + { "rbind", MS_BIND|MS_REC, MS_BIND|MS_REC, 0, false }, + { "bind", MS_BIND, MS_BIND, 0, false }, + { "move", MS_MOVE, MS_MOVE, 0, false }, + { "async", 0, MS_SYNCHRONOUS, 0, false }, + { "sync", MS_SYNCHRONOUS, MS_SYNCHRONOUS, 0, false }, + { "atime", 0, MS_NOATIME, 0, false }, + { "noatime", MS_NOATIME, MS_NOATIME, 0, false }, + { "dev", 0, MS_NODEV, 0, false }, + { "nodev", MS_NODEV, MS_NODEV, 0, false }, + { "exec", 0, MS_NOEXEC, 0, false }, + { "noexec", MS_NOEXEC, MS_NOEXEC, 0, false }, + { "suid", 0, MS_NOSUID, 0, false }, + { "nosuid", MS_NOSUID, MS_NOSUID, 0, false }, + { "ro", MS_RDONLY, MS_RDONLY, 0, false }, + { "rw", 0, MS_RDONLY, 0, false }, + + { "remount", MS_REMOUNT, MS_REMOUNT, 0, false }, + { "users", MS_NOEXEC|MS_NOSUID|MS_NODEV, + MS_NOEXEC|MS_NOSUID|MS_NODEV, 0, false }, + { "mandlock", MS_MANDLOCK, MS_MANDLOCK, 0, false }, + { "nodiratime", MS_NODIRATIME, MS_NODIRATIME, 0, false }, +#ifdef MS_DIRSYNC + { "dirsync", MS_DIRSYNC, MS_DIRSYNC, 0, false }, +#endif + { "_netdev", 0, 0, 0, false }, + { "auto", 0, 0, 0, false }, + { "noauto", 0, 0, XFLAG_NOAUTO, false }, + { "user", 0, 0, 0, false }, + { "nouser", 0, 0, 0, false }, +}; + +int wrapper_exit_code = 1; + +static void +showHelp(int fd, char const *cmd, int res) +{ + VSERVER_DECLARE_CMD(cmd); + + WRITE_MSG(fd, "Usage: "); + WRITE_STR(fd, cmd); + WRITE_MSG(fd, + " [--help] [--version] [--bind] [--move] [--rbind] [-t ] [--chroot]\n" + " [--mtab ] [--fstab ] [--rootfs yes|no|only]\n" + " [-n] -a|([-o ] [--] )\n\n" + "Executes mount-operations under the current directory: it assumes sources in\n" + "the current root-dir while destinations are expected in the chroot environment.\n\n" + "For non-trivial mount-operations it uses the external 'mount' program which\n" + "can be overridden by the $MOUNT environment variable.\n\n" + "Options:\n" + " --bind|move|rbind ... set the correspond flags; with this options\n" + " the mount will be executed internally without\n" + " calling an external mount program.\n" + " -t ... assume the given filesystem type\n" + " -o ... set additional options; see mount(2) for details\n" + " -n ... do not update the mtab-file\n" + " --mtab ... use as an alternative mtab file\n" + " [default: /etc/mtab]\n" + " --chroot ... chroot into the current directory before\n" + " mounting the filesystem\n" + " --fstab ... use as an alternative fstab file;\n" + " this option has an effect only with the '-a'\n" + " option [default: /etc/fstab]\n" + " --rootfs yes|no|only ... specifies how to handle an entry for a rootfs\n" + " ('/') when processing an fstab file. 'yes' will\n" + " mount it among the other entries, 'only' will\n" + " mount only the rootfs entry, and 'no' will ignore\n" + " it and mount only the other entries [default: yes]\n" + " -a ... mount everything listed in the fstab-file\n\n" + " ... the source-filesystem; this path is absolute\n" + " to the current root-filesystem. Only valid\n" + " without the '-a' option.\n" + " ... the destination mount-point; when used with\n" + " '--chroot', this path is relative to the current\n" + " directory. Only valid without the '-a' option\n\n" + "Please report bugs to " PACKAGE_BUGREPORT "\n"); + + exit(res); +} + +static void +showVersion() +{ + WRITE_MSG(1, + "secure-mount " VERSION " -- secure mounting of directories\n" + "This program is part of " PACKAGE_STRING "\n\n" + "Copyright (C) 2003 Enrico Scholz\n" + VERSION_COPYRIGHT_DISCLAIMER); + exit(0); +} + +inline static bool +isSameObject(struct stat const *lhs, + struct stat const *rhs) +{ + return (lhs->st_dev==rhs->st_dev && + lhs->st_ino==rhs->st_ino); +} + +static int +fchroot(int fd) +{ + if (fchdir(fd)==-1 || chroot(".")==-1) return -1; + return 0; +} + +static int +writeX(int fd, void const *buf, size_t len) +{ + if ((size_t)(write(fd, buf, len))!=len) return -1; + return 0; +} + +static int +writeStrX(int fd, char const *str) +{ + return writeX(fd, str, strlen(str)); +} + +static inline char const * +getType(struct MountInfo const *mnt) +{ + if (mnt->type==0) return "none"; + else if (strncmp(mnt->type, "ext", 3)==0) return "ufs"; + else return mnt->type; +} + +inline static void +restoreRoot(struct Options const *opt) +{ + if (opt->do_chroot!=0 && fchroot(opt->cur_rootdir_fd)==-1) { + perror("secure-mount: fchdir(\"/\")"); + WRITE_MSG(2, "Failed to restore root-directory; aborting\n"); + exit(1); + } +} + +static int +updateMtab(struct MountInfo const *mnt, struct Options const *opt) +{ + int res = -1; + int fd; + assert(opt->mtab!=0); + + if (opt->do_chroot && fchroot(opt->cur_dir_fd)==-1) { + perror("secure-mount: fchroot(\".\")"); + return -1; + } + + fd=open(opt->mtab, O_CREAT|O_APPEND|O_WRONLY, 0644); + + if (fd==-1) { + perror("secure-mount: open()"); + goto err0; + } + + if (flock(fd, LOCK_EX)==-1) { + perror("secure-mount: flock()"); + goto err1; + } + + if (writeStrX(fd, mnt->src)==-1 || + writeStrX(fd, " ")==-1 || + writeStrX(fd, mnt->dst)==-1 || + writeStrX(fd, " ")==-1 || + writeStrX(fd, getType(mnt))==-1 || + writeStrX(fd, " ")==-1 || + writeStrX(fd, mnt->data ? mnt->data : "defaults")==-1 || + writeStrX(fd, " 0 0\n")==-1) { + perror("secure-mount: write()"); + goto err1; + } + + res = 0; + + err1: close(fd); + err0: + restoreRoot(opt); + return res; +} + +static bool +callExternalMount(struct MountInfo const *mnt) +{ + char const * argv[10]; + size_t idx = 0; + pid_t pid; + int status; + char const * mount_prog = getenv("MOUNT"); + + if (mount_prog==0) mount_prog = MOUNT_PROG; + + argv[idx++] = mount_prog; + argv[idx++] = "-n"; + if (mnt->flag & MS_BIND) argv[idx++] = "--bind"; + else if (mnt->flag & MS_MOVE) argv[idx++] = "--move"; + + argv[idx++] = "-o"; + if (mnt->data && *mnt->data && + strcmp(mnt->data, "defaults")!=0) { + if (mnt->mask & MS_NODEV) + argv[idx++] = mnt->data; + else { + char * tmp = alloca(strlen(mnt->data) + sizeof("nodev,")); + strcpy(tmp, "nodev,"); + strcat(tmp, mnt->data); + argv[idx++] = tmp; + } + } + else + argv[idx++] = "nodev"; + + if (mnt->type) { + argv[idx++] = "-t"; + argv[idx++] = mnt->type; + } + + argv[idx++] = mnt->src; + argv[idx++] = "."; + argv[idx] = 0; + + pid = fork(); + if (pid==-1) { + perror("secure-mount: fork()"); + return false; + } + + if (pid==0) { + execv(mount_prog, const_cast(char **)(argv)); + PERROR_Q("secure-mount: execv", mount_prog); + exit(1); + } + + if (wait4(pid, &status, 0, 0)==-1) { + perror("secure-mount: wait4()"); + return false; + } + + return (WIFEXITED(status)) && (WEXITSTATUS(status)==0); +} + +inline static bool +secureChdir(char const *dir, struct Options const *opt) +{ + int dir_fd; + bool res = false; + + if (opt->do_chroot!=0 && fchroot(opt->cur_dir_fd)==-1) { + perror("secure-mount: fchroot(\".\")"); + return false; + } + + if (chdir(dir)==-1) { + PERROR_Q("secure-mount: chdir", dir); + goto err; + } + + dir_fd = open(".", O_RDONLY|O_DIRECTORY); + if (dir_fd==-1) { + perror("secure-mount: open(\".\")"); + goto err; + } + + restoreRoot(opt); + if (fchdir(dir_fd)==-1) + PERROR_Q("secure-mount: fchdir", dir); + else + res = true; + + close(dir_fd); + return res; + + err: + restoreRoot(opt); + return false; +} + +static bool +mountSingle(struct MountInfo const *mnt, struct Options const *opt) +{ + assert(mnt->dst!=0); + + if (!secureChdir(mnt->dst, opt)) + return false; + + if (mnt->flag & (MS_BIND|MS_MOVE)) { + unsigned long flag = mnt->flag; + if ((flag & MS_NODEV)==0) flag |= MS_NODEV; + + if (mount(mnt->src, ".", + mnt->type ? mnt->type : "", + flag, mnt->data)==-1) { + perror("secure-mount: mount()"); + return false; + } + } + else if (!callExternalMount(mnt)) + return false; + + if (!opt->ignore_mtab && + updateMtab(mnt, opt)==-1) { + WRITE_MSG(2, "Failed to update mtab-file\n"); + // no error + } + + return true; +} + +static struct FstabOption const * +searchOption(char const *opt, size_t len) +{ + struct FstabOption const * i; + for (i=FSTAB_OPTIONS+0; iopt, opt, len)==0) return i; + + return 0; +} + +static bool +transformOptionList(struct MountInfo *info, size_t UNUSED *col) +{ + char const * ptr = info->data; + + do { + char const * pos = strchr(ptr, ','); + struct FstabOption const * opt; + + if (pos==0) pos = ptr+strlen(ptr); + opt = searchOption(ptr, pos-ptr); + + if (opt!=0) { + info->flag &= ~opt->mask; + info->flag |= opt->flag; + info->mask |= opt->mask; + info->xflag |= opt->xflag; + } + + if (*pos!='\0') + ptr = pos+1; + else + ptr = pos; + + } while (*ptr!='\0'); + + return true; +} + +#define MOVE_TO_NEXT_FIELD(PTR,ALLOW_EOL) \ + while (!isspace(*PTR) && *PTR!='\0') ++PTR; \ + if (col) *col = buf-start_buf+1; \ + if (!(ALLOW_EOL) && *PTR=='\0') return prFAIL; \ + *PTR++ = '\0'; \ + while (isspace(*PTR)) ++PTR + +static enum {prDOIT, prFAIL, prIGNORE} + parseFstabLine(struct MountInfo *info, char *buf, size_t *col) +{ + char const * const start_buf = buf; + size_t err_col; + + while (isspace(*buf)) ++buf; + if (*buf=='#' || *buf=='\0') return prIGNORE; + + info->src = buf; + MOVE_TO_NEXT_FIELD(buf, false); + info->dst = buf; + MOVE_TO_NEXT_FIELD(buf, false); + info->type = buf; + MOVE_TO_NEXT_FIELD(buf, false); + err_col = buf-start_buf+1; + info->data = buf; + MOVE_TO_NEXT_FIELD(buf, true); + + info->flag = MS_NODEV; + info->mask = 0; + info->xflag = 0; + + if (strcmp(info->type, "swap") ==0) return prIGNORE; + else if (strcmp(info->type, "none") ==0) info->type = 0; + else if (strcmp(info->type, "devpts")==0) info->mask |= MS_NODEV; + + if (col) *col = err_col; + if (!transformOptionList(info,col)) return prFAIL; + if (info->xflag & XFLAG_NOAUTO) return prIGNORE; + + return prDOIT; +} + +#undef MOVE_TO_NEXT_FIELD + +static void +showFstabPosition(int fd, char const *fname, size_t line_nr, size_t col_nr) +{ + char buf[3*sizeof(line_nr)*2 + 4]; + size_t len = utilvserver_fmt_uint(buf+1, line_nr)+1; + + buf[0] = ':'; + buf[len++] = ':'; + len += utilvserver_fmt_uint(buf+len, col_nr); + WRITE_STR(fd, fname); + Vwrite(fd, buf, len); +} + + +static bool +mountFstab(struct Options const *opt) +{ + bool res = false; + int fd; + off_t len; + + assert(opt->fstab!=0); + fd = open(opt->fstab, O_RDONLY); + if (fd==-1) { + perror("secure-mount: open()"); + goto err0; + } + + len = lseek(fd, 0, SEEK_END); + if (len==-1 || + lseek(fd, 0, SEEK_SET)==-1) { + perror("secure-mount: lseek()"); + goto err1; + } + + { + char buf[len+2]; + char *ptr, *ptrptr; + size_t line_nr=0, col_nr; + + if (read(fd, buf, len+1)!=len) { + perror("secure-mount: read()"); + goto err1; + } + buf[len] = '#'; // workaround for broken dietlibc strtok_r() + // implementation + buf[len+1] = '\0'; + ptrptr = buf; + + while ((ptr=strsep(&ptrptr, "\n")) != 0) { + struct MountInfo mnt; + ++line_nr; + + switch (parseFstabLine(&mnt, ptr, &col_nr)) { + case prFAIL : + showFstabPosition(2, opt->fstab, line_nr, col_nr); + WRITE_MSG(2, ": syntax error\n"); + goto err1; + + case prIGNORE : break; + case prDOIT : { + bool is_rootfs = (strcmp(mnt.dst, "/")==0); + Echdir("/"); + if (( is_rootfs && opt->rootfs==rfsNO) || + (!is_rootfs && opt->rootfs==rfsONLY)) { /* ignore the entry */ } + else if (!mountSingle(&mnt, opt)) { + showFstabPosition(2, opt->fstab, line_nr, 1); + WRITE_MSG(2, ": failed to mount fstab-entry\n"); + } + break; + } + default : + assert(false); + } + } + } + + res = true; + + err1: close(fd); + err0: return res; +} + +static void +initFDs(struct Options *opt) +{ + opt->cur_dir_fd = Eopen(".", O_RDONLY|O_DIRECTORY, 0); + opt->cur_rootdir_fd = Eopen("/", O_RDONLY|O_DIRECTORY, 0); + + Efcntl(opt->cur_dir_fd, F_SETFD, FD_CLOEXEC); + Efcntl(opt->cur_rootdir_fd, F_SETFD, FD_CLOEXEC); +} + +static RootFsOption +parseRootFS(char const *str) +{ + if (strcasecmp(str, "yes")==0) return rfsYES; + else if (strcasecmp(str, "no")==0) return rfsNO; + else if (strcasecmp(str, "only")==0) return rfsONLY; + else { + WRITE_MSG(2, "secure-mount: invalid option for '--rootfs': '"); + WRITE_STR(2, str); + WRITE_MSG(2, "'\n"); + exit(wrapper_exit_code); + } +} + +int main(int argc, char *argv[]) +{ + struct MountInfo mnt = { + .src = 0, + .dst = 0, + .type = 0, + .flag = 0, + .xflag = 0, + .data = 0, + }; + + struct Options opt = { + .mtab = "/etc/mtab", + .fstab = "/etc/fstab", + .do_chroot = 0, + .ignore_mtab = false, + .mount_all = false, + .cur_dir_fd = -1, + .cur_rootdir_fd = -1, + .rootfs = rfsYES + }; + + while (1) { + int c = getopt_long(argc, argv, "ht:nao:", CMDLINE_OPTIONS, 0); + if (c==-1) break; + + switch (c) { + case 'h' : showHelp(1, argv[0], 0); + case 'v' : showVersion(); + case 't' : mnt.type = optarg; break; + case 'n' : opt.ignore_mtab = true; break; + case 'a' : opt.mount_all = true; break; + case 'o' : mnt.data = optarg; break; + case OPTION_RBIND : mnt.flag |= MS_REC; /*@fallthrough@*/ + case OPTION_BIND : mnt.flag |= MS_BIND; break; + case OPTION_MOVE : mnt.flag |= MS_MOVE; break; + case OPTION_MTAB : opt.mtab = optarg; break; + case OPTION_FSTAB : opt.fstab = optarg; break; + case OPTION_CHROOT: opt.do_chroot = true; break; + case OPTION_ROOTFS: opt.rootfs = parseRootFS(optarg); break; + case OPTION_SECURE: + WRITE_MSG(2, "secure-mount: The '--secure' option is deprecated...\n"); + break; + default : + WRITE_MSG(2, "Try '"); + WRITE_STR(2, argv[0]); + WRITE_MSG(2, " --help\" for more information.\n"); + return EXIT_FAILURE; + break; + } + } + + + if (opt.mount_all && optind and '-a' at the same time\n"); + return EXIT_FAILURE; + } + + initFDs(&opt); + signal(SIGCHLD, SIG_DFL); + + if (opt.mount_all) { + if (!mountFstab(&opt)) return EXIT_FAILURE; + else return EXIT_SUCCESS; + } + + if (optind+2!=argc) { + WRITE_MSG(2, "Invalid pair specified\n"); + return EXIT_FAILURE; + } + + if (mnt.data) { + mnt.data = strdup(mnt.data); + if (!transformOptionList(&mnt, 0)) { + WRITE_MSG(2, "Invalid options specified\n"); + return EXIT_FAILURE; + } + } + + mnt.src = argv[optind++]; + mnt.dst = argv[optind++]; + + if (!mountSingle(&mnt, &opt)) return EXIT_FAILURE; + + return EXIT_SUCCESS; +}