From 8930ec395e771f9e4ffef0530915feee776558ea Mon Sep 17 00:00:00 2001
From: =?UTF-8?q?Yves=20R=C5=B1tschl=C3=A9?= <git1@rutschle.net>
Date: Tue, 29 Aug 2023 17:05:41 +0200
Subject: [PATCH] Initial support for the landlock LSM

---
 ChangeLog   |   4 ++
 Makefile    |   7 +++-
 common.h    |   4 ++
 landlock.c  | 111 ++++++++++++++++++++++++++++++++++++++++++++++++++++
 sslh-main.c |   1 +
 5 files changed, 126 insertions(+), 1 deletion(-)
 create mode 100644 landlock.c

diff --git a/ChangeLog b/ChangeLog
index 1d24e02..3c200f4 100644
--- a/ChangeLog
+++ b/ChangeLog
@@ -1,3 +1,7 @@
+vNEXT:
+	Support for the Landlock LSM. After initial setup,
+	sslh gives up all local file access rights.
+
 v2.0:
 	New sslh-ev: this is functionally equivalent to
 	sslh-select (mono-process, only forks for specified
diff --git a/Makefile b/Makefile
index ec6b46b..49ee89e 100644
--- a/Makefile
+++ b/Makefile
@@ -8,6 +8,7 @@ ENABLE_REGEX=1  # Enable regex probes
 USELIBCONFIG=1	# Use libconfig? (necessary to use configuration files)
 USELIBWRAP?=	# Use libwrap?
 USELIBCAP=	# Use libcap?
+USELANDLOCK=1	# Use the landlock LSM?
 USESYSTEMD=     # Make use of systemd socket activation
 USELIBBSD?=     # Use libbsd (needed to update process name in `ps`)
 COV_TEST= 	# Perform test coverage?
@@ -33,7 +34,7 @@ AR ?= ar
 CFLAGS ?=-Wall -O2 -DLIBPCRE -g $(CFLAGS_COV) $(CFLAGS_SAN)
 
 LIBS=-lm -lpcre2-8
-OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o
+OBJS=sslh-conf.o common.o log.o sslh-main.o probe.o tls.o argtable3.o collection.o gap.o tcp-probe.o landlock.o
 OBJS_A=libsslh.a
 FORK_OBJS=sslh-fork.o $(OBJS_A)
 SELECT_OBJS=processes.o udp-listener.o sslh-select.o hash.o tcp-listener.o $(OBJS_A)
@@ -71,6 +72,10 @@ ifneq ($(strip $(USELIBBSD)),)
         CPPFLAGS+=-DLIBBSD
 endif
 
+ifneq ($(strip $(USELANDLOCK)),)
+        CPPFLAGS+=-DLANDLOCK
+endif
+
 
 all: sslh $(MAN) echosrv $(CONDITIONAL_TARGETS)
 
diff --git a/common.h b/common.h
index 6f6e76c..4b457ab 100644
--- a/common.h
+++ b/common.h
@@ -183,4 +183,8 @@ void start_shoveler(int);
 
 void main_loop(struct listen_endpoint *listen_sockets, int num_addr_listen);
 
+/* landlock.c */
+void setup_landlock(void);
+
+
 #endif
diff --git a/landlock.c b/landlock.c
new file mode 100644
index 0000000..1e53b9c
--- /dev/null
+++ b/landlock.c
@@ -0,0 +1,111 @@
+/*
+ * Setup a sandbox using the Landlock LSM, if available.
+
+# Copyright (C) 2023  Yves Rutschle
+# 
+# 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; either
+# version 2 of the License, or (at your option) any later
+# version.
+# 
+# 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.
+# 
+# The full text for the General Public License is here:
+# http://www.gnu.org/licenses/gpl.html
+#
+*/
+
+#ifdef LANDLOCK
+
+#define _GNU_SOURCE
+#include <linux/landlock.h>
+#include <sys/prctl.h>
+#include <sys/syscall.h>
+
+#include "log.h"
+
+#ifndef landlock_create_ruleset
+static inline int
+landlock_create_ruleset(const struct landlock_ruleset_attr *const attr,
+			const size_t size, const __u32 flags)
+{
+	return syscall(__NR_landlock_create_ruleset, attr, size, flags);
+}
+#endif
+
+#ifndef landlock_add_rule
+static inline int landlock_add_rule(const int ruleset_fd,
+				    const enum landlock_rule_type rule_type,
+				    const void *const rule_attr,
+				    const __u32 flags)
+{
+	return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr,
+		       flags);
+}
+#endif
+
+#ifndef landlock_restrict_self
+static inline int landlock_restrict_self(const int ruleset_fd,
+					 const __u32 flags)
+{
+	return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
+}
+#endif
+
+
+void setup_landlock(void)
+{
+    __u64 restrict_rules = 
+        LANDLOCK_ACCESS_FS_EXECUTE |
+        LANDLOCK_ACCESS_FS_READ_FILE |
+        LANDLOCK_ACCESS_FS_READ_DIR |
+        LANDLOCK_ACCESS_FS_WRITE_FILE |
+        LANDLOCK_ACCESS_FS_REMOVE_DIR |
+        LANDLOCK_ACCESS_FS_REMOVE_FILE |
+        LANDLOCK_ACCESS_FS_MAKE_CHAR |
+        LANDLOCK_ACCESS_FS_MAKE_DIR |
+        LANDLOCK_ACCESS_FS_MAKE_REG |
+        LANDLOCK_ACCESS_FS_MAKE_SOCK |
+        LANDLOCK_ACCESS_FS_MAKE_FIFO |
+        LANDLOCK_ACCESS_FS_MAKE_BLOCK |
+        LANDLOCK_ACCESS_FS_MAKE_SYM |
+        LANDLOCK_ACCESS_FS_REFER;
+
+    struct landlock_ruleset_attr ruleset_attr = {
+        .handled_access_fs = restrict_rules
+    };
+
+    /* ruleset_addr.handled_access_fs contains all rights that will be restricted
+     * unless explicitly added */
+    int ruleset_fd = landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), 0);
+    if (ruleset_fd < 0) {
+        print_message(msg_config_error, "Landlock: Failed to create a ruleset");
+        return;
+    }
+
+    /* No call to landlock_add_rule: we retain no rights whatsoever */
+
+    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0)) {
+        print_message(msg_config_error, "Landlock: Failed to restrict privileges");
+        return;
+    }
+    if (landlock_restrict_self(ruleset_fd, 0)) {
+        print_message(msg_config_error, "Landlock: Failed to enforce ruleset");
+        return;
+    }
+    close(ruleset_fd);
+
+    print_message(msg_config, "Landlock: all restricted\n");
+}
+
+#else
+void setup_landlock(void)
+{
+    return;
+}
+#endif
diff --git a/sslh-main.c b/sslh-main.c
index b5ead6d..1ec6221 100644
--- a/sslh-main.c
+++ b/sslh-main.c
@@ -277,6 +277,7 @@ int main(int argc, char *argv[], char* envp[])
 
    if (cfg.user || cfg.chroot)
        drop_privileges(cfg.user, cfg.chroot);
+   setup_landlock();
 
    printcaps();