aboutsummaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorAndrew G. Morgan <morgan@kernel.org>2021-11-07 09:38:47 -0800
committerAndrew G. Morgan <morgan@kernel.org>2021-11-07 09:59:35 -0800
commit2b763ab706891255e8cef9f36ca023e32f5d5991 (patch)
treed2015ea40ffbc91359ff7cf5fe0685fabceb1c65
parent9c4997d6592e5daf046a6968ac83cf615c51fbe1 (diff)
downloadlibcap-2b763ab706891255e8cef9f36ca023e32f5d5991.tar.gz
An example of a shared library object with its own file capability.
I've been exploring the idea of how to create limited use privileged binaries that can be linked into otherwise unprivileged binaries. This is a worked example of the bootstrapping process for a webserver. I intend to provide a more complete writeup of what is going on with this example here: https://sites.google.com/site/fullycapable/capable-shared-objects For this present example to work you have to be using a libcap that includes cap_launch support (ie., libcap 2.33+, but this code will be included with libcap-2.61 and might inadvertently actually require something that new to work robustly). This code appears to be very fragile at present. It works on my Chromebook's linux container, but not under Fedora 34 - segfaulting. Signed-off-by: Andrew G. Morgan <morgan@kernel.org>
-rw-r--r--contrib/capso/Makefile23
-rw-r--r--contrib/capso/README.md20
-rw-r--r--contrib/capso/bind.c29
-rw-r--r--contrib/capso/capso.c265
-rw-r--r--contrib/capso/capso.h16
5 files changed, 353 insertions, 0 deletions
diff --git a/contrib/capso/Makefile b/contrib/capso/Makefile
new file mode 100644
index 0000000..87ed34b
--- /dev/null
+++ b/contrib/capso/Makefile
@@ -0,0 +1,23 @@
+topdir=$(shell pwd)/../..
+include ../../Make.Rules
+
+# Always build sources this way:
+CFLAGS += -fPIC
+
+all: bind
+
+bind: bind.c capso.so
+ $(CC) $(CFLAGS) $(CPPFLAGS) -o $@ bind.c capso.so -L../../libcap -lcap
+
+../../libcap/loader.txt:
+ $(MAKE) -C ../../libcap loader.txt
+
+capso.o: capso.c capso.h ../../libcap/execable.h ../../libcap/loader.txt
+ $(CC) $(CFLAGS) $(CPPFLAGS) -DLIBCAP_VERSION=\"libcap-$(VERSION).$(MINOR)\" -DSHARED_LOADER=\"$(shell cat ../../libcap/loader.txt)\" -c capso.c -o $@
+
+capso.so: capso.o
+ $(LD) $(LDFLAGS) -o $@ $< $(LIBCAPLIB) -ldl -Wl,-e,__so_start
+ sudo setcap cap_net_bind_service=p $@
+
+clean:
+ rm -f bind capso.o capso.so *~
diff --git a/contrib/capso/README.md b/contrib/capso/README.md
new file mode 100644
index 0000000..64e9b43
--- /dev/null
+++ b/contrib/capso/README.md
@@ -0,0 +1,20 @@
+# Leveraging file capabilities on shared libraries
+
+This directory contains an example of a shared library (capso.so) that
+can be installed with file capabilities. When the library is linked
+against an unprivileged program, it includes internal support for
+re-invoking itself as a child subprocess to execute a privileged
+operation on bahalf of the parent.
+
+The idea for doing this was evolved from the way pam_unix.so is able
+to leverage a separate program, and libcap's recently added support
+for supporting binary execution of all the .so files built by the
+package.
+
+The actual program example 'bind' leverages the
+"cap_net_bind_service=p" ./capso.so file to bind to the privileged
+port 80.
+
+A writeup of how to explore this example is provided here:
+
+https://sites.google.com/site/fullycapable/capable-shared-objects
diff --git a/contrib/capso/bind.c b/contrib/capso/bind.c
new file mode 100644
index 0000000..609e4e4
--- /dev/null
+++ b/contrib/capso/bind.c
@@ -0,0 +1,29 @@
+/*
+ * Unprivileged program that binds to port 80. It does this by
+ * leveraging a file capable shared library.
+ */
+#include <stdio.h>
+#include <stdlib.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <unistd.h>
+
+#include "capso.h"
+
+int main(int argc, char **argv) {
+ int f = bind80("127.0.0.1");
+ if (f < 0) {
+ perror("unable to bind to port 80");
+ exit(1);
+ }
+ if (listen(f, 10) == -1) {
+ perror("unable to listen to port 80");
+ exit(1);
+ }
+ printf("Webserver code to use filedes = %d goes here.\n"
+ "(Sleeping for 60s... Try 'netstat -tlnp|grep :80')\n", f);
+ fflush(stdout);
+ sleep(60);
+ close(f);
+ printf("Done.\n");
+}
diff --git a/contrib/capso/capso.c b/contrib/capso/capso.c
new file mode 100644
index 0000000..13710e2
--- /dev/null
+++ b/contrib/capso/capso.c
@@ -0,0 +1,265 @@
+/*
+ * Worked example for a shared object with a file capability on it
+ * leveraging itself for preprogrammed functionality.
+ *
+ * This example implements a shared library that can bind to
+ * the privileged port. ":80".
+ *
+ * The shared library needs to be installed with
+ * cap_net_bind_service=p. As a shared library, it provides the
+ * function bind80().
+ */
+
+#define _GNU_SOURCE
+
+#include <dlfcn.h>
+#include <netdb.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/capability.h>
+#include <sys/socket.h>
+#include <sys/types.h>
+#include <sys/un.h>
+#include <sys/wait.h>
+#include <unistd.h>
+
+#include "capso.h"
+
+/*
+ * where_am_i determines the full path for the shared libary that
+ * contains this function. It allocates the path in strdup()d memory
+ * that should be free()d by the caller. If it can't find itself, it
+ * returns NULL.
+ */
+static char *where_am_i(void)
+{
+ Dl_info info;
+ if (dladdr(where_am_i, &info) == 0) {
+ return NULL;
+ }
+ return strdup(info.dli_fname);
+}
+
+/*
+ * try_bind80 attempts to reuseably bind to port 80 with the given
+ * hostname. It returns a bound filedescriptor or -1 on error.
+ */
+static int try_bind80(const char *hostname)
+{
+ struct addrinfo conf, *detail = NULL;
+ int err, ret = -1, one = 1;
+
+ memset(&conf, 0, sizeof(conf));
+ conf.ai_family = PF_UNSPEC;
+ conf.ai_socktype = SOCK_STREAM;
+ conf.ai_protocol = 0;
+ conf.ai_flags = AI_PASSIVE | AI_ADDRCONFIG;
+
+ err = getaddrinfo(hostname, "80", &conf, &detail);
+ if (err != 0) {
+ goto done;
+ }
+
+ ret = socket(detail->ai_family, detail->ai_socktype, detail->ai_protocol);
+ if (ret == -1) {
+ goto done;
+ }
+
+ if (setsockopt(ret, SOL_SOCKET, SO_REUSEADDR, &one, sizeof(one))) {
+ close(ret);
+ ret = -1;
+ goto done;
+ }
+
+ if (bind(ret, detail->ai_addr, detail->ai_addrlen)) {
+ close(ret);
+ ret = -1;
+ goto done;
+ }
+
+done:
+ if (detail != NULL) {
+ freeaddrinfo(detail);
+ }
+
+ return ret;
+}
+
+/*
+ * set_fd3 forces file descriptor 3 to be associated with a unix
+ * socket that can be used to send a file descriptor back to the
+ * parent program.
+ */
+static int set_fd3(void *detail)
+{
+ int *sp = detail;
+
+ close(sp[0]);
+ if (dup2(sp[1], 3) != 3) {
+ return -1;
+ }
+ close(sp[1]);
+
+ return 0;
+}
+
+/*
+ * bind80 returns a socket filedescriptor that is bound to port 80 of
+ * the provided service address.
+ *
+ * Example:
+ *
+ * int fd = bind80("localhost");
+ *
+ * fd < 0 in the case of error.
+ */
+int bind80(const char *hostname)
+{
+ char const *args[3];
+ char *path;
+ int fd, ignored;
+ int sp[2];
+ struct iovec iov[1];
+ char junk[1];
+ const int rec_buf_len = CMSG_SPACE(sizeof(int));
+ char *rec_buf[CMSG_SPACE(sizeof(int))];
+ struct msghdr msg;
+ cap_launch_t helper;
+ pid_t child;
+
+ fd = try_bind80(hostname);
+ if (fd >= 0) {
+ return fd;
+ }
+
+ /*
+ * Initial attempt didn't work, so try launching the shared
+ * library as an executable and getting it to yield a bound
+ * filedescriptor for us via a unix socket pair.
+ */
+ path = where_am_i();
+ if (path == NULL) {
+ perror("unable to find self");
+ return -1;
+ }
+
+ args[0] = "bind80-helper";
+ args[1] = hostname;
+ args[2] = NULL;
+
+ helper = cap_new_launcher(path, args, NULL);
+ if (helper == NULL) {
+ goto drop_path;
+ }
+
+ if (socketpair(AF_UNIX, SOCK_DGRAM, 0, sp)) {
+ goto drop_helper;
+ }
+
+ cap_launcher_callback(helper, set_fd3);
+ child = cap_launch(helper, sp);
+ close(sp[1]);
+
+ if (child <= 0) {
+ goto drop_sp;
+ }
+
+ iov[0].iov_base = junk;
+ iov[0].iov_len = 1;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = iov;
+ msg.msg_iovlen = 1;
+ msg.msg_control = rec_buf;
+ msg.msg_controllen = rec_buf_len;
+
+ if (recvmsg(sp[0], &msg, 0) != -1) {
+ fd = * (int *) CMSG_DATA(CMSG_FIRSTHDR(&msg));
+ }
+ waitpid(child, &ignored, 0);
+
+drop_sp:
+ close(sp[0]);
+
+drop_helper:
+ cap_free(helper);
+
+drop_path:
+ free(path);
+
+ return fd;
+}
+
+#include "../../libcap/execable.h"
+//#define SO_MAIN int main
+
+SO_MAIN(int argc, char **argv)
+{
+ const char *cmd = "<capso.so>";
+ const cap_value_t cap_net_bind_service = CAP_NET_BIND_SERVICE;
+ cap_t working;
+ int fd;
+ struct msghdr msg;
+ struct cmsghdr *ctrl;
+ struct iovec payload;
+ char data[CMSG_SPACE(sizeof(fd))];
+ char junk[1];
+
+ if (argv != NULL) {
+ cmd = argv[0];
+ }
+
+ if (argc != 2 || argv[1] == NULL || !strcmp(argv[1], "--help")) {
+ fprintf(stderr, "usage: %s <hostname>\n", cmd);
+ exit(1);
+ }
+
+ working = cap_get_proc();
+ if (working == NULL) {
+ perror("unable to read capabilities");
+ exit(1);
+ }
+
+ if (cap_set_flag(working, CAP_EFFECTIVE, 1,
+ &cap_net_bind_service, CAP_SET) != 0) {
+ perror("unable to raise CAP_NET_BIND_SERVICE");
+ exit(1);
+ }
+
+ if (cap_set_proc(working) != 0) {
+ perror("cap_set_proc problem");
+ fprintf(stderr, "try: sudo setcap cap_net_bind_service=p %s\n",
+ argv[0]);
+ exit(1);
+ }
+
+ fd = try_bind80(argv[1]);
+
+ memset(data, 0, sizeof(data));
+ memset(&payload, 0, sizeof(payload));
+
+ payload.iov_base = junk;
+ payload.iov_len = 1;
+
+ msg.msg_name = NULL;
+ msg.msg_namelen = 0;
+ msg.msg_iov = &payload;
+ msg.msg_iovlen = 1;
+ msg.msg_control = data;
+ msg.msg_controllen = sizeof(data);
+
+ ctrl = CMSG_FIRSTHDR(&msg);
+ ctrl->cmsg_level = SOL_SOCKET;
+ ctrl->cmsg_type = SCM_RIGHTS;
+ ctrl->cmsg_len = CMSG_LEN(sizeof(fd));
+
+ *((int *) CMSG_DATA(ctrl)) = fd;
+
+ if (sendmsg(3, &msg, 0) < 0) {
+ perror("failed to write fd");
+ }
+
+ exit(0);
+}
diff --git a/contrib/capso/capso.h b/contrib/capso/capso.h
new file mode 100644
index 0000000..ae18f3a
--- /dev/null
+++ b/contrib/capso/capso.h
@@ -0,0 +1,16 @@
+#ifndef CAPSO_H
+#define CAPSO_H
+
+/*
+ * bind80 returns a socket filedescriptor that is bound to port 80 of
+ * the provided service address.
+ *
+ * Example:
+ *
+ * int fd = bind80("localhost");
+ *
+ * fd < 0 in the case of error.
+ */
+extern int bind80(const char *hostname);
+
+#endif /* ndef CAPSO_H */