Discussion:
FreeBSD sandboxing support via capsicum
Loganaden Velvindron
2014-07-05 20:15:06 UTC
Permalink
Hi All,

FreeBSD has designed capsicum which is a sandboxing mechanism.

Please find below a patch against FreeBSD 10 Release:

(Patch based on FreeBSD's port of tcpdump done by ***@freebsd.org)

diff --git a/tcpdump.c b/tcpdump.c
index 739f9c8..aaac4b8 100644
--- a/tcpdump.c
+++ b/tcpdump.c
@@ -66,6 +66,13 @@ extern int SIZE_BUF;
#include <stdlib.h>
#include <string.h>
#include <limits.h>
+#ifdef __FreeBSD__
+#include <sys/capability.h>
+#include <sys/ioccom.h>
+#include <net/bpf.h>
+#include <fcntl.h>
+#include <libgen.h>
+#endif /* __FreeBSD__ */
#ifndef WIN32
#include <sys/wait.h>
#include <sys/resource.h>
@@ -388,6 +395,9 @@ struct dump_info {
char *CurrentFileName;
pcap_t *pd;
pcap_dumper_t *p;
+#ifdef __FreeBSD__
+ int dirfd;
+#endif
};

#ifdef HAVE_PCAP_SET_TSTAMP_TYPE
@@ -712,6 +722,11 @@ main(int argc, char **argv)
#endif
int status;
FILE *VFile;
+#ifdef __FreeBSD__
+ cap_rights_t rights;
+ int cansandbox;
+#endif /* __FreeBSD__ */
+
#ifdef WIN32
if(wsockinit() != 0) return 1;
#endif /* WIN32 */
@@ -1216,6 +1231,13 @@ main(int argc, char **argv)
pd = pcap_open_offline(RFileName, ebuf);
if (pd == NULL)
error("%s", ebuf);
+#ifdef __FreeBSD__
+ cap_rights_init(&rights, CAP_READ);
+ if (cap_rights_limit(fileno(pcap_file(pd)), &rights) < 0 &&
+ errno != ENOSYS) {
+ error("unable to limit pcap descriptor");
+ }
+#endif
dlt = pcap_datalink(pd);
dlt_name = pcap_datalink_val_to_name(dlt);
if (dlt_name == NULL) {
@@ -1472,6 +1494,21 @@ main(int argc, char **argv)

if (pcap_setfilter(pd, &fcode) < 0)
error("%s", pcap_geterr(pd));
+#ifdef __FreeBSD__
+ if (RFileName == NULL && VFileName == NULL) {
+ static const unsigned long cmds[] = { BIOCGSTATS };
+
+ cap_rights_init(&rights, CAP_IOCTL, CAP_READ);
+ if (cap_rights_limit(pcap_fileno(pd), &rights) < 0 &&
+ errno != ENOSYS) {
+ error("unable to limit pcap descriptor");
+ }
+ if (cap_ioctls_limit(pcap_fileno(pd), cmds,
+ sizeof(cmds) / sizeof(cmds[0])) < 0 && errno != ENOSYS) {
+ error("unable to limit ioctls on pcap descriptor");
+ }
+ }
+#endif
if (WFileName) {
pcap_dumper_t *p;
/* Do not exceed the default PATH_MAX for files. */
@@ -1493,9 +1530,32 @@ main(int argc, char **argv)
#endif
if (p == NULL)
error("%s", pcap_geterr(pd));
+#ifdef __FreeBSD__
+ cap_rights_init(&rights, CAP_SEEK, CAP_WRITE);
+ if (cap_rights_limit(fileno(pcap_dump_file(p)), &rights) < 0 &&
+ errno != ENOSYS) {
+ error("unable to limit dump descriptor");
+ }
+#endif
if (Cflag != 0 || Gflag != 0) {
- callback = dump_packet_and_trunc;
+#ifdef __FreeBSD__
+ dumpinfo.WFileName = strdup(basename(WFileName));
+ dumpinfo.dirfd = open(dirname(WFileName),
+ O_DIRECTORY | O_RDONLY);
+ if (dumpinfo.dirfd < 0) {
+ error("unable to open directory %s",
+ dirname(WFileName));
+ }
+ cap_rights_init(&rights, CAP_CREATE, CAP_FCNTL,
+ CAP_FTRUNCATE, CAP_LOOKUP, CAP_SEEK, CAP_WRITE);
+ if (cap_rights_limit(dumpinfo.dirfd, &rights) < 0 &&
+ errno != ENOSYS) {
+ error("unable to limit directory rights");
+ }
+#else /* !__FreeBSD__ */
dumpinfo.WFileName = WFileName;
+#endif
+ callback = dump_packet_and_trunc;
dumpinfo.pd = pd;
dumpinfo.p = p;
pcap_userdata = (u_char *)&dumpinfo;
@@ -1565,6 +1625,15 @@ main(int argc, char **argv)
(void)fflush(stderr);
}
#endif /* WIN32 */
+
+#ifdef __FreeBSD__
+ cansandbox = (nflag && VFileName == NULL && zflag == NULL);
+ if (cansandbox && cap_enter() < 0 && errno != ENOSYS)
+ error("unable to enter the capability mode");
+ if (cap_sandboxed())
+ fprintf(stderr, "capability mode sandbox enabled\n");
+#endif /* __FreeBSD__ */
+
do {
status = pcap_loop(pd, cnt, callback, pcap_userdata);
if (WFileName == NULL) {
@@ -1612,6 +1681,13 @@ main(int argc, char **argv)
pd = pcap_open_offline(RFileName, ebuf);
if (pd == NULL)
error("%s", ebuf);
+#ifdef __FreeBSD__
+ cap_rights_init(&rights, CAP_READ);
+ if (cap_rights_limit(fileno(pcap_file(pd)),
+ &rights) < 0 && errno != ENOSYS) {
+ error("unable to limit pcap descriptor");
+ }
+#endif
new_dlt = pcap_datalink(pd);
if (WFileName && new_dlt != dlt)
error("%s: new dlt does not match original", RFileName);
@@ -1780,6 +1856,9 @@ static void
dump_packet_and_trunc(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
{
struct dump_info *dump_info;
+#ifdef __FreeBSD__
+ cap_rights_t rights;
+#endif

++packets_captured;

@@ -1808,6 +1887,11 @@ dump_packet_and_trunc(u_char *user, const struct pcap_pkthdr *h, const u_char *s

/* If the time is greater than the specified window, rotate */
if (t - Gflag_time >= Gflag) {
+#ifdef __FreeBSD__
+ FILE *fp;
+ int fd;
+#endif
+
/* Update the Gflag_time */
Gflag_time = t;
/* Update Gflag_count */
@@ -1854,13 +1938,36 @@ dump_packet_and_trunc(u_char *user, const struct pcap_pkthdr *h, const u_char *s
capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, CAP_DAC_OVERRIDE);
capng_apply(CAPNG_EFFECTIVE);
#endif /* HAVE_CAP_NG_H */
+#ifdef __FreeBSD__
+ fd = openat(dump_info->dirfd,
+ dump_info->CurrentFileName,
+ O_CREAT | O_WRONLY | O_TRUNC, 0644);
+ if (fd < 0) {
+ error("unable to open file %s",
+ dump_info->CurrentFileName);
+ }
+ fp = fdopen(fd, "w");
+ if (fp == NULL) {
+ error("unable to fdopen file %s",
+ dump_info->CurrentFileName);
+ }
+ dump_info->p = pcap_dump_fopen(dump_info->pd, fp);
+#else /* !__FreeBSD__ */
dump_info->p = pcap_dump_open(dump_info->pd, dump_info->CurrentFileName);
+#endif
#ifdef HAVE_CAP_NG_H
capng_update(CAPNG_DROP, CAPNG_EFFECTIVE, CAP_DAC_OVERRIDE);
capng_apply(CAPNG_EFFECTIVE);
#endif /* HAVE_CAP_NG_H */
if (dump_info->p == NULL)
error("%s", pcap_geterr(pd));
+#ifdef __FreeBSD__
+ cap_rights_init(&rights, CAP_SEEK, CAP_WRITE);
+ if (cap_rights_limit(fileno(pcap_dump_file(dump_info->p)),
+ &rights) < 0 && errno != ENOSYS) {
+ error("unable to limit dump descriptor");
+ }
+#endif
}
}

@@ -1870,6 +1977,11 @@ dump_packet_and_trunc(u_char *user, const struct pcap_pkthdr *h, const u_char *s
* file could put it over Cflag.
*/
if (Cflag != 0 && pcap_dump_ftell(dump_info->p) > Cflag) {
+#ifdef __FreeBSD__
+ FILE *fp;
+ int fd;
+#endif
+
/*
* Close the current file and open a new one.
*/
@@ -1892,9 +2004,31 @@ dump_packet_and_trunc(u_char *user, const struct pcap_pkthdr *h, const u_char *s
if (dump_info->CurrentFileName == NULL)
error("dump_packet_and_trunc: malloc");
MakeFilename(dump_info->CurrentFileName, dump_info->WFileName, Cflag_count, WflagChars);
+#ifdef __FreeBSD__
+ fd = openat(dump_info->dirfd, dump_info->CurrentFileName,
+ O_CREAT | O_WRONLY | O_TRUNC, 0644);
+ if (fd < 0) {
+ error("unable to open file %s",
+ dump_info->CurrentFileName);
+ }
+ fp = fdopen(fd, "w");
+ if (fp == NULL) {
+ error("unable to fdopen file %s",
+ dump_info->CurrentFileName);
+ }
+ dump_info->p = pcap_dump_fopen(dump_info->pd, fp);
+#else /* !__FreeBSD__ */
dump_info->p = pcap_dump_open(dump_info->pd, dump_info->CurrentFileName);
+#endif
if (dump_info->p == NULL)
error("%s", pcap_geterr(pd));
+#ifdef __FreeBSD__
+ cap_rights_init(&rights, CAP_SEEK, CAP_WRITE);
+ if (cap_rights_limit(fileno(pcap_dump_file(dump_info->p)),
+ &rights) < 0 && errno != ENOSYS) {
+ error("unable to limit dump descriptor");
+ }
+#endif
}

pcap_dump((u_char *)dump_info->p, h, sp);
Guy Harris
2014-07-05 20:34:15 UTC
Permalink
Post by Loganaden Velvindron
FreeBSD has designed capsicum which is a sandboxing mechanism.
So this will compile on all versions of FreeBSD, correct?

If not, please update the configure script to test for the relevant APIs, and enable the new code only on systems with those APIs.
Loganaden Velvindron
2014-07-09 12:14:15 UTC
Permalink
Post by Guy Harris
Post by Loganaden Velvindron
FreeBSD has designed capsicum which is a sandboxing mechanism.
So this will compile on all versions of FreeBSD, correct?
If not, please update the configure script to test for the relevant APIs, and enable the new code only on systems with those APIs.
Thank you for your feeeback.

Updated diff. Feedback welcomed.

diff --git a/configure.in b/configure.in
index c88f470..8488653 100644
--- a/configure.in
+++ b/configure.in
@@ -177,6 +177,16 @@ else
AC_MSG_RESULT(no)
fi

+AC_ARG_WITH(sandbox-capsicum, [ --with-sandbox-capsicum ])
+AC_MSG_CHECKING([whether to sandbox using capsicum])
+if test ! -z "$with_sandbox-capsicum" && test "$with_sandbox-capsicum" != "no" ; then
+ AC_CHECK_FUNCS(cap_enter cap_rights_init cap_rights_limit cap_ioctls_limit openat,
+ AC_DEFINE(HAVE_CAPSICUM, 1, [capsicum support available]))
+ AC_MSG_RESULT(yes)
+else
+ AC_MSG_RESULT(no)
+fi
+
#
# We must check this before checking whether to enable IPv6, because,
# on some platforms (such as SunOS 5.x), the test program requires
diff --git a/tcpdump.c b/tcpdump.c
index 7db319a..40b930d 100644
--- a/tcpdump.c
+++ b/tcpdump.c
@@ -72,6 +72,13 @@ extern int SIZE_BUF;
#include <stdlib.h>
#include <string.h>
#include <limits.h>
+#ifdef HAVE_CAPSICUM
+#include <sys/capability.h>
+#include <sys/ioccom.h>
+#include <net/bpf.h>
+#include <fcntl.h>
+#include <libgen.h>
+#endif /* HAVE_CAPSICUM */
#ifndef WIN32
#include <sys/wait.h>
#include <sys/resource.h>
@@ -441,6 +448,9 @@ struct dump_info {
char *CurrentFileName;
pcap_t *pd;
pcap_dumper_t *p;
+#ifdef HAVE_CAPSICUM
+ int dirfd;
+#endif
};

#ifdef HAVE_PCAP_SET_TSTAMP_TYPE
@@ -910,6 +920,11 @@ main(int argc, char **argv)
#endif
int status;
FILE *VFile;
+#ifdef HAVE_CAPSICUM
+ cap_rights_t rights;
+ int cansandbox;
+#endif /* HAVE_CAPSICUM */
+
#ifdef WIN32
if(wsockinit() != 0) return 1;
#endif /* WIN32 */
@@ -1423,6 +1438,13 @@ main(int argc, char **argv)

if (pd == NULL)
error("%s", ebuf);
+#ifdef HAVE_CAPSICUM
+ cap_rights_init(&rights, CAP_READ);
+ if (cap_rights_limit(fileno(pcap_file(pd)), &rights) < 0 &&
+ errno != ENOSYS) {
+ error("unable to limit pcap descriptor");
+ }
+#endif
dlt = pcap_datalink(pd);
dlt_name = pcap_datalink_val_to_name(dlt);
if (dlt_name == NULL) {
@@ -1687,6 +1709,21 @@ main(int argc, char **argv)

if (pcap_setfilter(pd, &fcode) < 0)
error("%s", pcap_geterr(pd));
+#ifdef HAVE_CAPSICUM
+ if (RFileName == NULL && VFileName == NULL) {
+ static const unsigned long cmds[] = { BIOCGSTATS };
+
+ cap_rights_init(&rights, CAP_IOCTL, CAP_READ);
+ if (cap_rights_limit(pcap_fileno(pd), &rights) < 0 &&
+ errno != ENOSYS) {
+ error("unable to limit pcap descriptor");
+ }
+ if (cap_ioctls_limit(pcap_fileno(pd), cmds,
+ sizeof(cmds) / sizeof(cmds[0])) < 0 && errno != ENOSYS) {
+ error("unable to limit ioctls on pcap descriptor");
+ }
+ }
+#endif
if (WFileName) {
pcap_dumper_t *p;
/* Do not exceed the default PATH_MAX for files. */
@@ -1708,9 +1745,32 @@ main(int argc, char **argv)
#endif
if (p == NULL)
error("%s", pcap_geterr(pd));
+#ifdef HAVE_CAPSICUM
+ cap_rights_init(&rights, CAP_SEEK, CAP_WRITE);
+ if (cap_rights_limit(fileno(pcap_dump_file(p)), &rights) < 0 &&
+ errno != ENOSYS) {
+ error("unable to limit dump descriptor");
+ }
+#endif
if (Cflag != 0 || Gflag != 0) {
- callback = dump_packet_and_trunc;
+#ifdef HAVE_CAPSICUM
+ dumpinfo.WFileName = strdup(basename(WFileName));
+ dumpinfo.dirfd = open(dirname(WFileName),
+ O_DIRECTORY | O_RDONLY);
+ if (dumpinfo.dirfd < 0) {
+ error("unable to open directory %s",
+ dirname(WFileName));
+ }
+ cap_rights_init(&rights, CAP_CREATE, CAP_FCNTL,
+ CAP_FTRUNCATE, CAP_LOOKUP, CAP_SEEK, CAP_WRITE);
+ if (cap_rights_limit(dumpinfo.dirfd, &rights) < 0 &&
+ errno != ENOSYS) {
+ error("unable to limit directory rights");
+ }
+#else /* !HAVE_CAPSICUM */
dumpinfo.WFileName = WFileName;
+#endif
+ callback = dump_packet_and_trunc;
dumpinfo.pd = pd;
dumpinfo.p = p;
pcap_userdata = (u_char *)&dumpinfo;
@@ -1780,6 +1840,15 @@ main(int argc, char **argv)
(void)fflush(stderr);
}
#endif /* WIN32 */
+
+#ifdef HAVE_CAPSICUM
+ cansandbox = (nflag && VFileName == NULL && zflag == NULL);
+ if (cansandbox && cap_enter() < 0 && errno != ENOSYS)
+ error("unable to enter the capability mode");
+ if (cap_sandboxed())
+ fprintf(stderr, "capability mode sandbox enabled\n");
+#endif /* HAVE_CAPSICUM */
+
do {
status = pcap_loop(pd, cnt, callback, pcap_userdata);
if (WFileName == NULL) {
@@ -1827,6 +1896,13 @@ main(int argc, char **argv)
pd = pcap_open_offline(RFileName, ebuf);
if (pd == NULL)
error("%s", ebuf);
+#ifdef HAVE_CAPSICUM
+ cap_rights_init(&rights, CAP_READ);
+ if (cap_rights_limit(fileno(pcap_file(pd)),
+ &rights) < 0 && errno != ENOSYS) {
+ error("unable to limit pcap descriptor");
+ }
+#endif
new_dlt = pcap_datalink(pd);
if (WFileName && new_dlt != dlt)
error("%s: new dlt does not match original", RFileName);
@@ -1995,6 +2071,9 @@ static void
dump_packet_and_trunc(u_char *user, const struct pcap_pkthdr *h, const u_char *sp)
{
struct dump_info *dump_info;
+#ifdef HAVE_CAPSICUM
+ cap_rights_t rights;
+#endif

++packets_captured;

@@ -2023,6 +2102,11 @@ dump_packet_and_trunc(u_char *user, const struct pcap_pkthdr *h, const u_char *s

/* If the time is greater than the specified window, rotate */
if (t - Gflag_time >= Gflag) {
+#ifdef HAVE_CAPSICUM
+ FILE *fp;
+ int fd;
+#endif
+
/* Update the Gflag_time */
Gflag_time = t;
/* Update Gflag_count */
@@ -2076,13 +2160,36 @@ dump_packet_and_trunc(u_char *user, const struct pcap_pkthdr *h, const u_char *s
capng_update(CAPNG_ADD, CAPNG_EFFECTIVE, CAP_DAC_OVERRIDE);
capng_apply(CAPNG_EFFECTIVE);
#endif /* HAVE_CAP_NG_H */
+#ifdef HAVE_CAPSICUM
+ fd = openat(dump_info->dirfd,
+ dump_info->CurrentFileName,
+ O_CREAT | O_WRONLY | O_TRUNC, 0644);
+ if (fd < 0) {
+ error("unable to open file %s",
+ dump_info->CurrentFileName);
+ }
+ fp = fdopen(fd, "w");
+ if (fp == NULL) {
+ error("unable to fdopen file %s",
+ dump_info->CurrentFileName);
+ }
+ dump_info->p = pcap_dump_fopen(dump_info->pd, fp);
+#else /* !HAVE_CAPSICUM */
dump_info->p = pcap_dump_open(dump_info->pd, dump_info->CurrentFileName);
+#endif
#ifdef HAVE_CAP_NG_H
capng_update(CAPNG_DROP, CAPNG_EFFECTIVE, CAP_DAC_OVERRIDE);
capng_apply(CAPNG_EFFECTIVE);
#endif /* HAVE_CAP_NG_H */
if (dump_info->p == NULL)
error("%s", pcap_geterr(pd));
+#ifdef HAVE_CAPSICUM
+ cap_rights_init(&rights, CAP_SEEK, CAP_WRITE);
+ if (cap_rights_limit(fileno(pcap_dump_file(dump_info->p)),
+ &rights) < 0 && errno != ENOSYS) {
+ error("unable to limit dump descriptor");
+ }
+#endif
}
}

@@ -2092,6 +2199,11 @@ dump_packet_and_trunc(u_char *user, const struct pcap_pkthdr *h, const u_char *s
* file could put it over Cflag.
*/
if (Cflag != 0 && pcap_dump_ftell(dump_info->p) > Cflag) {
+#ifdef HAVE_CAPSICUM
+ FILE *fp;
+ int fd;
+#endif
+
/*
* Close the current file and open a new one.
*/
@@ -2114,9 +2226,31 @@ dump_packet_and_trunc(u_char *user, const struct pcap_pkthdr *h, const u_char *s
if (dump_info->CurrentFileName == NULL)
error("dump_packet_and_trunc: malloc");
MakeFilename(dump_info->CurrentFileName, dump_info->WFileName, Cflag_count, WflagChars);
+#ifdef HAVE_CAPSICUM
+ fd = openat(dump_info->dirfd, dump_info->CurrentFileName,
+ O_CREAT | O_WRONLY | O_TRUNC, 0644);
+ if (fd < 0) {
+ error("unable to open file %s",
+ dump_info->CurrentFileName);
+ }
+ fp = fdopen(fd, "w");
+ if (fp == NULL) {
+ error("unable to fdopen file %s",
+ dump_info->CurrentFileName);
+ }
+ dump_info->p = pcap_dump_fopen(dump_info->pd, fp);
+#else /* !HAVE_CAPSICUM */
dump_info->p = pcap_dump_open(dump_info->pd, dump_info->CurrentFileName);
+#endif
if (dump_info->p == NULL)
error("%s", pcap_geterr(pd));
+#ifdef HAVE_CAPSICUM
+ cap_rights_init(&rights, CAP_SEEK, CAP_WRITE);
+ if (cap_rights_limit(fileno(pcap_dump_file(dump_info->p)),
+ &rights) < 0 && errno != ENOSYS) {
+ error("unable to limit dump descriptor");
+ }
+#endif
}

pcap_dump((u_char *)dump_info->p, h, sp);
Guy Harris
2014-07-10 19:22:28 UTC
Permalink
Post by Loganaden Velvindron
Post by Guy Harris
Post by Loganaden Velvindron
FreeBSD has designed capsicum which is a sandboxing mechanism.
So this will compile on all versions of FreeBSD, correct?
If not, please update the configure script to test for the relevant APIs, and enable the new code only on systems with those APIs.
Thank you for your feeeback.
Updated diff. Feedback welcomed.
Checked in (with some fixes to the configure script - there shouldn't be anything between AC_MSG_CHECKING and AC_MSG_RESULT that would print any output, and capsicum should be enabled only if none of the functions were not found).

Michael, should this go into 4.6 or should we wait for the next tcpdump release?
Loganaden Velvindron
2014-07-11 08:45:55 UTC
Permalink
Post by Guy Harris
Post by Loganaden Velvindron
Post by Guy Harris
Post by Loganaden Velvindron
FreeBSD has designed capsicum which is a sandboxing mechanism.
So this will compile on all versions of FreeBSD, correct?
If not, please update the configure script to test for the relevant APIs, and enable the new code only on systems with those APIs.
Thank you for your feeeback.
Updated diff. Feedback welcomed.
Checked in (with some fixes to the configure script - there shouldn't be anything between AC_MSG_CHECKING and AC_MSG_RESULT that would print any output, and capsicum should be enabled only if none of the functions were not found).
Thanks. I'll test it.

Please note that the code is adapted from code written by Pawel J Dawidek (***@freebsd.org), and that I simply
rewrote it for tcpdump. I think that he also deserves credit in the CREDITS file.
Post by Guy Harris
Michael, should this go into 4.6 or should we wait for the next tcpdump release?
Loading...