Skip to content

Commit

Permalink
landlock: basic implementation
Browse files Browse the repository at this point in the history
This patch is adding support for Landlock, a Linux
Security Module available since Linux 5.13.

The concept is to prevent any file operation on directories where
Suricata is not supposed to access.

Landlock support is built by default if the header is present. The
feature is disabled by default and need to be activated in the YAML
to be active.

Landlock documentation: https://docs.kernel.org/userspace-api/landlock.html

Feature: OISF#5479
  • Loading branch information
regit authored and victorjulien committed Sep 13, 2022
1 parent fe91506 commit 485d5a4
Show file tree
Hide file tree
Showing 6 changed files with 334 additions and 0 deletions.
8 changes: 8 additions & 0 deletions configure.ac
Original file line number Diff line number Diff line change
Expand Up @@ -377,6 +377,13 @@
AC_SUBST(SECLDFLAGS)
])

#check for Landlock support
AC_CHECK_HEADERS([linux/landlock.h])
enable_landlock="no"
if test "$ac_cv_header_linux_landlock_h" = "yes"; then
enable_landlock="yes"
fi

#check for plugin support
AC_CHECK_HEADERS([dlfcn.h])
AC_MSG_CHECKING([for plugin support])
Expand Down Expand Up @@ -2513,6 +2520,7 @@ SURICATA_BUILD_CONF="Suricata Configuration:
Hyperscan support: ${enable_hyperscan}
Libnet support: ${enable_libnet}
liblz4 support: ${enable_liblz4}
Landlock support: ${enable_landlock}

Rust support: ${enable_rust}
Rust strict mode: ${enable_rust_strict}
Expand Down
2 changes: 2 additions & 0 deletions src/Makefile.am
Original file line number Diff line number Diff line change
Expand Up @@ -546,6 +546,7 @@ noinst_HEADERS = \
util-ioctl.h \
util-ip.h \
util-ja3.h \
util-landlock.h \
util-logopenfile.h \
util-log-redis.h \
util-lua-common.h \
Expand Down Expand Up @@ -1132,6 +1133,7 @@ libsuricata_c_a_SOURCES = \
util-ioctl.c \
util-ip.c \
util-ja3.c \
util-landlock.c \
util-logopenfile.c \
util-log-redis.c \
util-lua.c \
Expand Down
4 changes: 4 additions & 0 deletions src/suricata.c
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@
#include "util-daemon.h"
#include "util-byte.h"
#include "util-luajit.h"
#include "util-landlock.h"

#include "reputation.h"

#include "output.h"
Expand Down Expand Up @@ -2904,6 +2906,8 @@ int SuricataMain(int argc, char **argv)
exit(EXIT_FAILURE);
}

LandlockSandboxing(&suricata);

SCDropMainThreadCaps(suricata.userid, suricata.groupid);

/* Re-enable coredumps after privileges are dropped. */
Expand Down
275 changes: 275 additions & 0 deletions src/util-landlock.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,275 @@
/* Copyright (C) 2022 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

/**
* \file
*
* \author Eric Leblond <[email protected]>
*/

#include "suricata.h"
#include "util-conf.h"
#include "util-landlock.h"
#include "util-mem.h"

#ifndef HAVE_LINUX_LANDLOCK_H

void LandlockSandboxing(SCInstance *suri)
{
return;
}

#else /* HAVE_LINUX_LANDLOCK_H */

#include <linux/landlock.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

#ifndef LANDLOCK_ACCESS_FS_REFER
#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
#endif

#define _LANDLOCK_ACCESS_FS_WRITE \
(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)

#define _LANDLOCK_ACCESS_FS_READ (LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR)

#define _LANDLOCK_SURI_ACCESS_FS_WRITE \
(LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | \
LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_SOCK)

struct landlock_ruleset {
int fd;
struct landlock_ruleset_attr attr;
};

static inline struct landlock_ruleset *LandlockCreateRuleset(void)
{
struct landlock_ruleset *ruleset = SCCalloc(1, sizeof(struct landlock_ruleset));
if (ruleset == NULL) {
SCLogError(SC_ERR_MEM_ALLOC, "Can't alloc landlock ruleset");
return NULL;
}

ruleset->attr.handled_access_fs =
_LANDLOCK_ACCESS_FS_READ | _LANDLOCK_ACCESS_FS_WRITE | LANDLOCK_ACCESS_FS_EXECUTE;

int abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
if (abi < 0) {
SCFree(ruleset);
return NULL;
}
if (abi < 2) {
ruleset->attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER;
}

ruleset->fd = landlock_create_ruleset(&ruleset->attr, sizeof(ruleset->attr), 0);
if (ruleset->fd < 0) {
SCFree(ruleset);
SCLogError(SC_ERR_CONF_YAML_ERROR, "Can't create landlock ruleset");
return NULL;
}
return ruleset;
}

static inline void LandlockEnforceRuleset(struct landlock_ruleset *ruleset)
{
if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
SCLogError(
SC_ERR_CONF_YAML_ERROR, "Can't self restrict (prctl phase): %s", strerror(errno));
return;
}
if (landlock_restrict_self(ruleset->fd, 0)) {
SCLogError(SC_ERR_CONF_YAML_ERROR, "Can't self restrict (landlock phase): %s",
strerror(errno));
}
}

static int LandlockSandboxingAddRule(
struct landlock_ruleset *ruleset, const char *directory, uint64_t permission)
{
struct landlock_path_beneath_attr path_beneath = {
.allowed_access = permission & ruleset->attr.handled_access_fs,
};

int dir_fd = open(directory, O_PATH | O_CLOEXEC | O_DIRECTORY);
if (dir_fd == -1) {
SCLogError(SC_ERR_CONF_YAML_ERROR, "Can't open %s", directory);
return -1;
}
path_beneath.parent_fd = dir_fd;

if (landlock_add_rule(ruleset->fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0)) {
SCLogError(SC_ERR_CONF_YAML_ERROR, "Can't add write rule: %s", strerror(errno));
close(dir_fd);
return -1;
}

close(dir_fd);
return 0;
}

static inline void LandlockSandboxingWritePath(
struct landlock_ruleset *ruleset, const char *directory)
{
if (LandlockSandboxingAddRule(ruleset, directory, _LANDLOCK_SURI_ACCESS_FS_WRITE) == 0) {
SCLogConfig("Added write permission to '%s'", directory);
}
}

static inline void LandlockSandboxingReadPath(
struct landlock_ruleset *ruleset, const char *directory)
{
if (LandlockSandboxingAddRule(ruleset, directory, _LANDLOCK_ACCESS_FS_READ) == 0) {
SCLogConfig("Added read permission to '%s'", directory);
}
}

void LandlockSandboxing(SCInstance *suri)
{
/* Read configuration variable and exit if no enforcement */
int conf_status;
ConfGetBool("security.landlock.enabled", &conf_status);
if (!conf_status) {
SCLogConfig("Landlock is not enabled in configuration");
return;
}
struct landlock_ruleset *ruleset = LandlockCreateRuleset();
if (ruleset == NULL) {
SCLogError(SC_ERR_NOT_SUPPORTED, "Kernel does not support Landlock");
return;
}

LandlockSandboxingWritePath(ruleset, ConfigGetLogDirectory());
struct stat sb;
if (stat(ConfigGetDataDirectory(), &sb) == 0) {
LandlockSandboxingAddRule(ruleset, ConfigGetDataDirectory(),
_LANDLOCK_SURI_ACCESS_FS_WRITE | _LANDLOCK_ACCESS_FS_READ);
}
if (suri->run_mode == RUNMODE_PCAP_FILE) {
const char *pcap_file;
ConfGet("pcap-file.file", &pcap_file);
char *file_name = SCStrdup(pcap_file);
if (file_name != NULL) {
struct stat statbuf;
if (stat(file_name, &statbuf) != -1) {
if (S_ISDIR(statbuf.st_mode)) {
LandlockSandboxingReadPath(ruleset, file_name);
} else {
LandlockSandboxingReadPath(ruleset, dirname(file_name));
}
} else {
SCLogError(SC_ERR_OPENING_FILE, "Can't open pcap file");
}
SCFree(file_name);
}
}
if (suri->sig_file) {
char *file_name = SCStrdup(suri->sig_file);
if (file_name != NULL) {
LandlockSandboxingReadPath(ruleset, dirname(file_name));
SCFree(file_name);
}
}
if (suri->pid_filename) {
char *file_name = SCStrdup(suri->pid_filename);
if (file_name != NULL) {
LandlockSandboxingWritePath(ruleset, dirname(file_name));
SCFree(file_name);
}
}
if (ConfUnixSocketIsEnable()) {
const char *socketname;
if (ConfGet("unix-command.filename", &socketname) == 1) {
if (PathIsAbsolute(socketname)) {
char *file_name = SCStrdup(socketname);
if (file_name != NULL) {
LandlockSandboxingWritePath(ruleset, dirname(file_name));
SCFree(file_name);
}
} else {
LandlockSandboxingWritePath(ruleset, LOCAL_STATE_DIR "/run/suricata/");
}
} else {
LandlockSandboxingWritePath(ruleset, LOCAL_STATE_DIR "/run/suricata/");
}
}
if (suri->sig_file_exclusive == FALSE) {
const char *rule_path;
ConfGet("default-rule-path", &rule_path);
if (rule_path) {
LandlockSandboxingReadPath(ruleset, rule_path);
}
}

ConfNode *read_dirs = ConfGetNode("security.landlock.directories.read");
if (read_dirs) {
if (!ConfNodeIsSequence(read_dirs)) {
SCLogWarning(SC_ERR_INVALID_ARGUMENT,
"Invalid security.landlock.directories.read configuration section: "
"expected a list of directory names.");
} else {
ConfNode *directory;
TAILQ_FOREACH (directory, &read_dirs->head, next) {
LandlockSandboxingReadPath(ruleset, directory->val);
}
}
}
ConfNode *write_dirs = ConfGetNode("security.landlock.directories.write");
if (write_dirs) {
if (!ConfNodeIsSequence(write_dirs)) {
SCLogWarning(SC_ERR_INVALID_ARGUMENT,
"Invalid security.landlock.directories.write configuration section: "
"expected a list of directory names.");
} else {
ConfNode *directory;
TAILQ_FOREACH (directory, &write_dirs->head, next) {
LandlockSandboxingWritePath(ruleset, directory->val);
}
}
}
LandlockEnforceRuleset(ruleset);
SCFree(ruleset);
}

#endif /* HAVE_LINUX_LANDLOCK_H */
31 changes: 31 additions & 0 deletions src/util-landlock.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
/* Copyright (C) 2022 Open Information Security Foundation
*
* You can copy, redistribute or modify this Program under the terms of
* the GNU General Public License version 2 as published by the Free
* Software Foundation.
*
* 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.
*
* You should have received a copy of the GNU General Public License
* version 2 along with this program; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
* 02110-1301, USA.
*/

/**
* \file
*
* \author Eric Leblond <[email protected]>
*/

#ifndef __UTIL_LANDLOCK_H__
#define __UTIL_LANDLOCK_H__

#include "suricata.h"

void LandlockSandboxing(SCInstance *suri);

#endif /* __UTIL_LANDLOCK_H__ */
14 changes: 14 additions & 0 deletions suricata.yaml.in
Original file line number Diff line number Diff line change
Expand Up @@ -1097,6 +1097,20 @@ asn1-max-frames: 256
# user: suri
# group: suri

security:
# Use landlock security module under Linux
landlock:
enabled: no
directories:
#write:
# - @e_rundir@
# /usr and /etc folders are added to read list to allow
# file magic to be used.
read:
- /usr/
- /etc/
- @e_sysconfdir@

# Some logging modules will use that name in event as identifier. The default
# value is the hostname
#sensor-name: suricata
Expand Down

0 comments on commit 485d5a4

Please sign in to comment.