diff --git a/cmdline.c b/cmdline.c index cc68354..541a578 100644 --- a/cmdline.c +++ b/cmdline.c @@ -225,10 +225,11 @@ void cmdlineLogParams(struct nsjconf_t *nsjconf) struct mounts_t *p; TAILQ_FOREACH(p, &nsjconf->mountpts, pointers) { LOG_I - ("Mount point: src:'%s' dst:'%s' type:'%s' flags:%s options:'%s' isDir:%s mandatory:%s", + ("Mount point: src:'%s' dst:'%s' type:'%s' flags:%s options:'%s' isDir:%s mandatory:%s src_content:%s (size:%zu)", p->src ? p->src : "[NULL]", p->dst, p->fs_type ? p->fs_type : "[NULL]", mountFlagsToStr(p->flags), p->options ? p->options : "[NULL]", - p->isDir ? "true" : "false", p->mandatory ? "true" : "false"); + p->isDir ? "true" : "false", p->mandatory ? "true" : "false", + p->src_content ? "true" : "false", p->src_content_len); } } { @@ -606,6 +607,8 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) case 'R':{ struct mounts_t *p = utilMalloc(sizeof(struct mounts_t)); p->src = optarg; + p->src_content = NULL; + p->src_content_len = 0; const char *dst = cmdlineSplitStrByColon(optarg); p->dst = dst ? dst : optarg; p->flags = MS_BIND | MS_REC | MS_RDONLY; @@ -618,6 +621,8 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) case 'B':{ struct mounts_t *p = utilMalloc(sizeof(struct mounts_t)); p->src = optarg; + p->src_content = NULL; + p->src_content_len = 0; const char *dst = cmdlineSplitStrByColon(optarg); p->dst = dst ? dst : optarg; p->flags = MS_BIND | MS_REC; @@ -630,6 +635,8 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) case 'T':{ struct mounts_t *p = utilMalloc(sizeof(struct mounts_t)); p->src = NULL; + p->src_content = NULL; + p->src_content_len = 0; p->dst = optarg; p->flags = 0; p->options = cmdlineTmpfsSz; @@ -713,6 +720,8 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) if (nsjconf->mount_proc == true) { struct mounts_t *p = utilMalloc(sizeof(struct mounts_t)); p->src = NULL; + p->src_content = NULL; + p->src_content_len = 0; p->dst = "/proc"; p->flags = 0; if (nsjconf->is_root_rw == false) { @@ -727,6 +736,8 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) if (nsjconf->chroot != NULL) { struct mounts_t *p = utilMalloc(sizeof(struct mounts_t)); p->src = nsjconf->chroot; + p->src_content = NULL; + p->src_content_len = 0; p->dst = "/"; p->flags = MS_BIND | MS_REC; if (nsjconf->is_root_rw == false) { @@ -740,6 +751,8 @@ bool cmdlineParse(int argc, char *argv[], struct nsjconf_t * nsjconf) } else { struct mounts_t *p = utilMalloc(sizeof(struct mounts_t)); p->src = NULL; + p->src_content = NULL; + p->src_content_len = 0; p->dst = "/"; p->flags = 0; if (nsjconf->is_root_rw == false) { diff --git a/common.h b/common.h index a03fe5c..c5c69be 100644 --- a/common.h +++ b/common.h @@ -66,6 +66,8 @@ struct pids_t { struct mounts_t { const char *src; + const uint8_t *src_content; + size_t src_content_len; const char *dst; const char *fs_type; const char *options; diff --git a/config.c b/config.c index 69721a0..3028911 100644 --- a/config.c +++ b/config.c @@ -187,9 +187,16 @@ static bool configParseInternal(struct nsjconf_t *nsjconf, Nsjail__NsJailConfig const bool *isDir = (njc->mount[i]->has_is_dir) ? (const bool *)&njc->mount[i]->is_dir : NULL; + uint8_t *src_content = NULL; + size_t src_content_len = 0; + if (njc->mount[i]->has_src_content) { + src_content = njc->mount[i]->src_content.data; + src_content_len = njc->mount[i]->src_content.len; + } + if (mountAddMountPt (nsjconf, src, dst, fstype, options, flags, isDir, mandatory, src_env, - dst_env) == false) { + dst_env, src_content, src_content_len) == false) { LOG_E("Couldn't add mountpoint for src:'%s' dst:'%s'", src, dst); return false; } diff --git a/config.pb-c.c b/config.pb-c.c index 53e5e93..c896cea 100644 --- a/config.pb-c.c +++ b/config.pb-c.c @@ -238,7 +238,7 @@ char nsjail__mount_pt__options__default_value[] = ""; static const protobuf_c_boolean nsjail__mount_pt__is_bind__default_value = 0; static const protobuf_c_boolean nsjail__mount_pt__rw__default_value = 0; static const protobuf_c_boolean nsjail__mount_pt__mandatory__default_value = 1; -static const ProtobufCFieldDescriptor nsjail__mount_pt__field_descriptors[10] = { +static const ProtobufCFieldDescriptor nsjail__mount_pt__field_descriptors[11] = { { "src", 1, @@ -264,8 +264,20 @@ static const ProtobufCFieldDescriptor nsjail__mount_pt__field_descriptors[10] = 0, NULL, NULL /* reserved1,reserved2, etc */ }, { - "dst", + "src_content", 3, + PROTOBUF_C_LABEL_OPTIONAL, + PROTOBUF_C_TYPE_BYTES, + offsetof(Nsjail__MountPt, has_src_content), + offsetof(Nsjail__MountPt, src_content), + NULL, + NULL, + 0, /* flags */ + 0, NULL, NULL /* reserved1,reserved2, etc */ + }, + { + "dst", + 4, PROTOBUF_C_LABEL_REQUIRED, PROTOBUF_C_TYPE_STRING, 0, /* quantifier_offset */ @@ -277,7 +289,7 @@ static const ProtobufCFieldDescriptor nsjail__mount_pt__field_descriptors[10] = }, { "prefix_dst_env", - 4, + 5, PROTOBUF_C_LABEL_OPTIONAL, PROTOBUF_C_TYPE_STRING, 0, /* quantifier_offset */ @@ -289,7 +301,7 @@ static const ProtobufCFieldDescriptor nsjail__mount_pt__field_descriptors[10] = }, { "fstype", - 5, + 6, PROTOBUF_C_LABEL_OPTIONAL, PROTOBUF_C_TYPE_STRING, 0, /* quantifier_offset */ @@ -301,7 +313,7 @@ static const ProtobufCFieldDescriptor nsjail__mount_pt__field_descriptors[10] = }, { "options", - 6, + 7, PROTOBUF_C_LABEL_REQUIRED, PROTOBUF_C_TYPE_STRING, 0, /* quantifier_offset */ @@ -313,7 +325,7 @@ static const ProtobufCFieldDescriptor nsjail__mount_pt__field_descriptors[10] = }, { "is_bind", - 7, + 8, PROTOBUF_C_LABEL_REQUIRED, PROTOBUF_C_TYPE_BOOL, 0, /* quantifier_offset */ @@ -325,7 +337,7 @@ static const ProtobufCFieldDescriptor nsjail__mount_pt__field_descriptors[10] = }, { "rw", - 8, + 9, PROTOBUF_C_LABEL_REQUIRED, PROTOBUF_C_TYPE_BOOL, 0, /* quantifier_offset */ @@ -337,7 +349,7 @@ static const ProtobufCFieldDescriptor nsjail__mount_pt__field_descriptors[10] = }, { "is_dir", - 9, + 10, PROTOBUF_C_LABEL_OPTIONAL, PROTOBUF_C_TYPE_BOOL, offsetof(Nsjail__MountPt, has_is_dir), @@ -349,7 +361,7 @@ static const ProtobufCFieldDescriptor nsjail__mount_pt__field_descriptors[10] = }, { "mandatory", - 10, + 11, PROTOBUF_C_LABEL_REQUIRED, PROTOBUF_C_TYPE_BOOL, 0, /* quantifier_offset */ @@ -362,21 +374,22 @@ static const ProtobufCFieldDescriptor nsjail__mount_pt__field_descriptors[10] = }; static const unsigned nsjail__mount_pt__field_indices_by_name[] = { - 2, /* field[2] = dst */ - 4, /* field[4] = fstype */ - 6, /* field[6] = is_bind */ - 8, /* field[8] = is_dir */ - 9, /* field[9] = mandatory */ - 5, /* field[5] = options */ - 3, /* field[3] = prefix_dst_env */ + 3, /* field[3] = dst */ + 5, /* field[5] = fstype */ + 7, /* field[7] = is_bind */ + 9, /* field[9] = is_dir */ + 10, /* field[10] = mandatory */ + 6, /* field[6] = options */ + 4, /* field[4] = prefix_dst_env */ 1, /* field[1] = prefix_src_env */ - 7, /* field[7] = rw */ + 8, /* field[8] = rw */ 0, /* field[0] = src */ + 2, /* field[2] = src_content */ }; static const ProtobufCIntRange nsjail__mount_pt__number_ranges[1 + 1] = { {1, 0}, - {0, 10} + {0, 11} }; const ProtobufCMessageDescriptor nsjail__mount_pt__descriptor = { @@ -386,7 +399,7 @@ const ProtobufCMessageDescriptor nsjail__mount_pt__descriptor = { "Nsjail__MountPt", "nsjail", sizeof(Nsjail__MountPt), - 10, + 11, nsjail__mount_pt__field_descriptors, nsjail__mount_pt__field_indices_by_name, 1, nsjail__mount_pt__number_ranges, diff --git a/config.pb-c.h b/config.pb-c.h index 0c9e215..ef4ede2 100644 --- a/config.pb-c.h +++ b/config.pb-c.h @@ -91,6 +91,11 @@ struct _Nsjail__MountPt { * Should 'src' path be prefixed with this envvar? */ char *prefix_src_env; + /* + * If specified, contains buffer that will be written to the dst file + */ + protobuf_c_boolean has_src_content; + ProtobufCBinaryData src_content; /* * Mount point inside jail */ @@ -130,7 +135,7 @@ extern char nsjail__mount_pt__fstype__default_value[]; extern char nsjail__mount_pt__options__default_value[]; #define NSJAIL__MOUNT_PT__INIT \ { PROTOBUF_C_MESSAGE_INIT (&nsjail__mount_pt__descriptor) \ - , NULL, NULL, NULL, NULL, nsjail__mount_pt__fstype__default_value, nsjail__mount_pt__options__default_value, 0, 0, 0,0, 1 } + , NULL, NULL, 0,{0,NULL}, NULL, NULL, nsjail__mount_pt__fstype__default_value, nsjail__mount_pt__options__default_value, 0, 0, 0,0, 1 } struct _Nsjail__Exe { ProtobufCMessage base; diff --git a/config.proto b/config.proto index 29e4aa8..e681e15 100644 --- a/config.proto +++ b/config.proto @@ -32,23 +32,25 @@ message MountPt optional string src = 1; /* Should 'src' path be prefixed with this envvar? */ optional string prefix_src_env = 2; + /* If specified, contains buffer that will be written to the dst file */ + optional bytes src_content = 3; /* Mount point inside jail */ - required string dst = 3; + required string dst = 4; /* Should 'dst' path be prefixed with this envvar? */ - optional string prefix_dst_env = 4; + optional string prefix_dst_env = 5; /* Can be empty for mount --bind mounts */ - optional string fstype = 5 [ default = "" ]; + optional string fstype = 6 [ default = "" ]; /* E.g. size=5000000 for 'tmpfs' */ - required string options = 6 [ default = "" ]; + required string options = 7 [ default = "" ]; /* Is it 'mount --bind src dst' type of mount */ - required bool is_bind = 7 [ default = false ]; + required bool is_bind = 8 [ default = false ]; /* It it R/W mount */ - required bool rw = 8 [ default = false ]; + required bool rw = 9 [ default = false ]; /* Is it directory? If not specified an internal heuristics will be used to determine that */ - optional bool is_dir = 9; + optional bool is_dir = 10; /* Should the sandboxing fail if we cannot mount this resource? */ - required bool mandatory = 10 [ default = true ]; + required bool mandatory = 11 [ default = true ]; } message Exe { diff --git a/configs/firefox-with-cloned-net.cfg b/configs/firefox-with-cloned-net.cfg new file mode 100644 index 0000000..58ae9ad --- /dev/null +++ b/configs/firefox-with-cloned-net.cfg @@ -0,0 +1,166 @@ +name: "firefox-with-cloned-net" +description: " +This policy allows to run firefox inside a jail on a separate eth interface. +A separate networking context separates process from the global \"lo\", and +from global abstract socket namespace. + +The only permitted home directory is $HOME/.mozilla and $HOME/Documents. +The rest of available on the FS files/dires are libs and X-related files/dirs. + +As this needs to be run as root, you will have to set-up correct uid&gid +mappings (here: 'jagger'), name of your local interface (here: 'enp0s31f6'), +and correct IPv4 addresses. + +IPv6 should work out-of-the-box, given that your local IPv6 discovery is set +up correctly. + +Run as: + +sudo ./nsjail --config configs/firefox-with-cloned-net.cfg + +You can then go to https://uploadfiles.io/ and try to upload a file in order +to see how your local directory (also, all system directories) look like. +" + +mode: ONCE +hostname: "FF-MACVTAP" +cwd: "/user" + +time_limit: 0 + +envar: "HOME=/user" +envar: "DISPLAY=:0" +envar: "TMP=/tmp" + +rlimit_as: 4096 +rlimit_cpu: 1000 +rlimit_fsize: 1024 +rlimit_nofile: 128 + +uidmap { + inside_id: "9999999" + outside_id: "jagger" +} + +gidmap { + inside_id: "9999999" + outside_id: "jagger" +} + +mount { + dst: "/proc" + fstype: "proc" +} + +mount { + src: "/lib" + dst: "/lib" + is_bind: true +} + +mount { + src: "/usr/lib" + dst: "/usr/lib" + is_bind: true +} + +mount { + src: "/lib64" + dst: "/lib64" + is_bind: true + mandatory: false +} + +mount { + src: "/lib32" + dst: "/lib32" + is_bind: true + mandatory: false +} + +mount { + src: "/usr/lib/firefox" + dst: "/usr/lib/firefox" + is_bind: true +} + +mount { + src: "/usr/bin/firefox" + dst: "/usr/bin/firefox" + is_bind: true +} + +mount { + src: "/usr/share" + dst: "/usr/share" + is_bind: true +} + +mount { + src: "/dev/urandom" + dst: "/dev/urandom" + is_bind: true + rw: true +} + +mount { + src_content: "nameserver 8.8.8.8" + dst: "/etc/resolv.conf" +} + +mount { + dst: "/tmp" + fstype: "tmpfs" + rw: true + is_bind: false +} + +mount { + dst: "/user" + fstype: "tmpfs" + rw: true +} + +mount { + prefix_src_env: "HOME" + src: "/Documents" + dst: "/user/Documents" + rw: true + is_bind: true + mandatory: false +} + +mount { + prefix_src_env: "HOME" + src: "/.mozilla" + dst: "/user/.mozilla" + is_bind: true + rw: true + mandatory: false +} + +mount { + src: "/tmp/.X11-unix/X0" + dst: "/tmp/.X11-unix/X0" + is_bind: true +} + +seccomp_string: " + POLICY example { + KILL { + ptrace, + process_vm_readv, + process_vm_writev + } + } + USE example DEFAULT ALLOW +" + +macvlan_iface: "enp0s31f6" +macvlan_vs_ip: "192.168.10.223" +macvlan_vs_nm: "255.255.255.0" +macvlan_vs_gw: "192.168.10.1" + +exec_bin { + path: "/usr/lib/firefox/firefox" +} diff --git a/mount.c b/mount.c index cfd2ed2..4c5f693 100644 --- a/mount.c +++ b/mount.c @@ -152,6 +152,23 @@ static bool mountMount(struct mounts_t *mpt, const char *oldroot, const char *ds } } + if (mpt->src_content) { + snprintf(srcpath, sizeof(srcpath), "/file.XXXXXX"); + int fd = mkostemp(srcpath, O_CLOEXEC); + if (fd < 0) { + PLOG_W("mkostemp('%s')", srcpath); + close(fd); + return false; + } + if (utilWriteToFd(fd, mpt->src_content, mpt->src_content_len) == false) { + LOG_W("Writting %zu bytes to '%s' failed", mpt->src_content_len, srcpath); + close(fd); + return false; + } + close(fd); + mpt->flags |= (MS_BIND | MS_REC); + } + /* * Initially mount it as RW, it will be remounted later on if needed */ @@ -343,7 +360,8 @@ bool mountInitNs(struct nsjconf_t * nsjconf) bool mountAddMountPt(struct nsjconf_t * nsjconf, const char *src, const char *dst, const char *fstype, const char *options, uintptr_t flags, const bool * isDir, - bool mandatory, const char *src_env, const char *dst_env) + bool mandatory, const char *src_env, const char *dst_env, + const uint8_t * src_content, size_t src_content_len) { struct mounts_t *p = utilCalloc(sizeof(struct mounts_t)); @@ -393,13 +411,15 @@ bool mountAddMountPt(struct nsjconf_t * nsjconf, const char *src, const char *ds p->fs_type = utilStrDup(fstype); p->options = utilStrDup(options); p->flags = flags; - p->isDir = isDir; + p->isDir = true; p->mandatory = mandatory; if (isDir) { p->isDir = *isDir; } else { - if (p->src == NULL) { + if (src_content) { + p->isDir = false; + } else if (p->src == NULL) { p->isDir = true; } else if (p->flags & MS_BIND) { p->isDir = mountIsDir(p->src); @@ -408,6 +428,9 @@ bool mountAddMountPt(struct nsjconf_t * nsjconf, const char *src, const char *ds } } + p->src_content = utilMemDup(src_content, src_content_len); + p->src_content_len = src_content_len; + TAILQ_INSERT_TAIL(&nsjconf->mountpts, p, pointers); return true; diff --git a/mount.h b/mount.h index 993df19..2cfcc5a 100644 --- a/mount.h +++ b/mount.h @@ -31,6 +31,7 @@ bool mountIsDir(const char *path); bool mountInitNs(struct nsjconf_t *nsjconf); bool mountAddMountPt(struct nsjconf_t *nsjconf, const char *src, const char *dst, const char *fstype, const char *options, uintptr_t flags, const bool * isDir, - bool mandatory, const char *src_env, const char *dst_env); + bool mandatory, const char *src_env, const char *dst_env, + const uint8_t * src_content, size_t src_content_len); #endif /* NS_MOUNT_H */ diff --git a/util.c b/util.c index 5e0cc2f..a226d1c 100644 --- a/util.c +++ b/util.c @@ -70,6 +70,16 @@ char *utilStrDupLen(const char *str, size_t len) return ret; } +uint8_t *utilMemDup(const uint8_t * src, size_t len) +{ + if (src == NULL) { + return NULL; + } + uint8_t *ret = utilMalloc(len); + memcpy(ret, src, len); + return ret; +} + ssize_t utilReadFromFd(int fd, void *buf, size_t len) { uint8_t *charbuf = (uint8_t *) buf; diff --git a/util.h b/util.h index d438bcd..2015f21 100644 --- a/util.h +++ b/util.h @@ -30,6 +30,7 @@ void *utilMalloc(size_t sz); void *utilCalloc(size_t sz); char *utilStrDup(const char *str); +uint8_t *utilMemDup(const uint8_t * src, size_t len); ssize_t utilReadFromFd(int fd, void *buf, size_t len); ssize_t utilReadFromFile(const char *fname, void *buf, size_t len); ssize_t utilWriteToFd(int fd, const void *buf, size_t len);