// Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
- // secure-mount <chroot-base> <src> <dst>
+ // secure-mount <secure-base> <src> <dst>
//
- // mounts <src> into the chroot <chroot-base> at destination <dst> in a way
+ // mounts <src> into the chroot <secure-base> at destination <dst> in a way
// that prevents symlink attacks
- // algorithm:
- // enter chroot
- // mount tmpfs-directory
- // leave chroot
- // mount --bind <src> tmpfs
- // enter chroot
- // mount --move tmpfs <dst>
#ifdef HAVE_CONFIG_H
# include <config.h>
#define VERSION_COPYRIGHT_AUTHOR "Enrico Scholz"
#include "util.h"
-#include <unistd.h>
-#include <sys/mount.h>
-#include <linux/fs.h>
-#include <sys/types.h>
-#include <sys/stat.h>
#include <fcntl.h>
-#include <string.h>
+#include <errno.h>
#include <stdio.h>
#include <stdlib.h>
-
+#include <string.h>
+#include <unistd.h>
+#include <stdbool.h>
+#include <sys/mount.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <linux/fs.h>
#define MNTPOINT "/etc"
WRITE_MSG(fd, "Usage: ");
WRITE_STR(fd, cmd);
WRITE_MSG(fd,
- " <chroot-base> <src> <dst>\n\n"
+ " <secure-base> <src> <dst>\n\n"
+ "Mounts <src> to <secure-base>/<dst> and assumes <secure-base>\n"
+ "as trustworthy but <dst> as hostile. Therefore, <dst> will be\n"
+ "traversed in a secure way and must not contain symlinks.\n\n"
"Please report bugs to " PACKAGE_BUGREPORT "\n");
exit(res);
showVersion()
{
WRITE_MSG(1,
- "secure-mount " VERSION " -- secure mounting of directories in chroots\n"
+ "secure-mount " VERSION " -- secure mounting of directories\n"
"This program is part of " PACKAGE_STRING "\n"
"Copyright (C) 2003 Enrico Scholz\n"
VERSION_COPYRIGHT_DISCLAIMER);
exit(0);
}
-int main(int argc, char *argv[])
+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
+chdirSecure(char const *dir)
+{
+ char tmp[strlen(dir)+1], *ptr;
+ char const *cur;
+
+ strcpy(tmp, dir);
+ cur = strtok_r(tmp, "/", &ptr);
+ while (cur) {
+ struct stat pre_stat, post_stat;
+
+ if (lstat(cur, &pre_stat)==-1) return -1;
+
+ if (!S_ISDIR(pre_stat.st_mode)) {
+ errno = ENOENT;
+ return -1;
+ }
+ if (S_ISLNK(pre_stat.st_mode)) {
+ errno = EINVAL;
+ return -1;
+ }
+
+ if (chdir(cur)==-1) return -1;
+ if (stat(".", &post_stat)==-1) return -1;
+
+ if (!isSameObject(&pre_stat, &post_stat)) {
+ char dir[PATH_MAX];
+
+ WRITE_MSG(2, "Possible symlink race ATTACK at '");
+ WRITE_STR(2, getcwd(dir, sizeof(dir)));
+ WRITE_MSG(2, "'\n");
+
+ errno = EINVAL;
+ return -1;
+ }
+
+ cur = strtok_r(0, "/", &ptr);
+ }
+
+ return 0;
+}
+
+static int
+verifyPosition(char const *mntpoint, char const *dir1, char const *dir2)
{
- int fd = open("/", O_RDONLY);
- // HACK: sanity checks should be done before declaration, but this does
- // not work with C89 compilers
- char path[strlen(argc==1 ? "" : argv[1]) + sizeof(MNTPOINT) + 3];
+ struct stat pre_stat, post_stat;
+
+ if (stat(mntpoint, &pre_stat)==-1) return -1;
+ if (chroot(dir1)==-1 || chdir(dir2)==-1) return -1;
+ if (stat(".", &post_stat)==-1) return -1;
+
+ if (!isSameObject(&pre_stat, &post_stat)) {
+ char dir[PATH_MAX];
+
+ WRITE_MSG(2, "Possible symlink race ATTACK at '");
+ WRITE_STR(2, getcwd(dir, sizeof(dir)));
+ WRITE_MSG(2, "' within '");
+ WRITE_MSG(2, dir1);
+ WRITE_STR(2, "'\n");
+
+ errno = EINVAL;
+ return -1;
+ }
+ return 0;
+}
+
+int main(int argc, char *argv[])
+{
if (argc>=2) {
if (!strcmp(argv[1], "--help")) showHelp(1, argv[0], 0);
if (!strcmp(argv[1], "--version")) showVersion();
exit(255);
}
- if (chroot(argv[1]) == -1 ||
- chdir("/") == -1) {
- perror("chroot()/chdir()");
- exit(1);
- }
-
- if (mount("none", MNTPOINT, "tmpfs", 0, "size=4k")==-1) {
- perror("mount()");
- exit(1);
+ if (chdir(argv[1])==-1) {
+ perror("chdir()");
+ return 1;
}
- if (mkdir(MNTPOINT "/x",0600) == -1) {
- perror("mkdir()/fchdir()");
- goto err1;
- }
-
- if (fchdir(fd) == -1 ||
- chroot(".") == -1) {
- perror("chroot()");
- goto err2;
+ if (chdirSecure(argv[3])==-1) {
+ perror("chdirSecure()");
+ return 1;
}
- strcpy(path, argv[1]);
- strcat(path, MNTPOINT "/x");
-
- if (mount(argv[2], path, "", MS_BIND, 0)==-1) {
+ if (mount(argv[2], ".", "", MS_BIND, 0)==-1) {
perror("mount()");
- goto err3;
- }
-
- if (chroot(argv[1]) == -1) {
- perror("chroot()");
- goto err4;
- }
-
- if (chdir("/") == -1 ||
- mount(MNTPOINT "/x", argv[3], "", MS_MOVE, 0) == -1) {
- perror("chdir()/mount()");
- goto err5;
- }
-
- if (umount(MNTPOINT) == -1) {
- perror("umount()");
+ return 1;
}
- return 0;
-
- err5:
- err4:
- if (umount(path)==-1) perror("umount(path)");
-
- err3:
- if (chdir(argv[1])==-1) {
- perror("chdir()");
+ // Check if directories were moved between the chdirSecure() and mount(2)
+ if (verifyPosition(argv[2], argv[1], argv[3])==-1) {
+ // TODO: what is with unmounting?
return 1;
}
- err2:
- err1:
- if (umount("mnt")==-1) perror("umount(\"mnt\")");
-
- return 1;
+ return 0;
}