suricata
util-landlock.c
Go to the documentation of this file.
1 /* Copyright (C) 2022 Open Information Security Foundation
2  *
3  * You can copy, redistribute or modify this Program under the terms of
4  * the GNU General Public License version 2 as published by the Free
5  * Software Foundation.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * version 2 along with this program; if not, write to the Free Software
14  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
15  * 02110-1301, USA.
16  */
17 
18 /**
19  * \file
20  *
21  * \author Eric Leblond <el@stamus-networks.com>
22  */
23 
24 #include "suricata.h"
25 #include "detect-engine.h"
26 #include "feature.h"
27 #include "util-conf.h"
28 #include "util-file.h"
29 #include "util-landlock.h"
30 #include "util-mem.h"
31 #include "util-path.h"
32 #include "util-validate.h"
33 
34 #ifndef HAVE_LINUX_LANDLOCK_H
35 
37 {
38 }
39 
40 #else /* HAVE_LINUX_LANDLOCK_H */
41 
42 #include <linux/landlock.h>
43 
44 #ifndef landlock_create_ruleset
45 static inline int landlock_create_ruleset(
46  const struct landlock_ruleset_attr *const attr, const size_t size, const __u32 flags)
47 {
48  long r = syscall(__NR_landlock_create_ruleset, attr, size, flags);
49  DEBUG_VALIDATE_BUG_ON(r > INT_MAX);
50  return (int)r;
51 }
52 #endif
53 
54 #ifndef landlock_add_rule
55 static inline int landlock_add_rule(const int ruleset_fd, const enum landlock_rule_type rule_type,
56  const void *const rule_attr, const __u32 flags)
57 {
58  long r = syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr, flags);
59  DEBUG_VALIDATE_BUG_ON(r > INT_MAX);
60  return (int)r;
61 }
62 #endif
63 
64 #ifndef landlock_restrict_self
65 static inline int landlock_restrict_self(const int ruleset_fd, const __u32 flags)
66 {
67  long r = syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
68  DEBUG_VALIDATE_BUG_ON(r > INT_MAX);
69  return (int)r;
70 }
71 #endif
72 
73 #ifndef LANDLOCK_ACCESS_FS_REFER
74 #define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
75 #endif
76 
77 #define _LANDLOCK_ACCESS_FS_WRITE \
78  (LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_DIR | \
79  LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_CHAR | \
80  LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | \
81  LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO | \
82  LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM | \
83  LANDLOCK_ACCESS_FS_REFER)
84 
85 #define _LANDLOCK_ACCESS_FS_READ (LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR)
86 
87 #define _LANDLOCK_SURI_ACCESS_FS_WRITE \
88  (LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | \
89  LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_SOCK)
90 
91 struct landlock_ruleset {
92  int fd;
93  struct landlock_ruleset_attr attr;
94 };
95 
96 static inline struct landlock_ruleset *LandlockCreateRuleset(void)
97 {
98  struct landlock_ruleset *ruleset = SCCalloc(1, sizeof(struct landlock_ruleset));
99  if (ruleset == NULL) {
100  SCLogError("Can't alloc landlock ruleset");
101  return NULL;
102  }
103 
104  ruleset->attr.handled_access_fs =
105  _LANDLOCK_ACCESS_FS_READ | _LANDLOCK_ACCESS_FS_WRITE | LANDLOCK_ACCESS_FS_EXECUTE;
106 
107  int abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
108  if (abi < 0) {
109  SCFree(ruleset);
110  return NULL;
111  }
112  if (abi < 2) {
114  SCLogError("Landlock disabled: need Linux 5.19+ for file store support");
115  SCFree(ruleset);
116  return NULL;
117  } else {
118  ruleset->attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER;
119  }
120  }
121 
122  ruleset->fd = landlock_create_ruleset(&ruleset->attr, sizeof(ruleset->attr), 0);
123  if (ruleset->fd < 0) {
124  SCFree(ruleset);
125  SCLogError("Can't create landlock ruleset");
126  return NULL;
127  }
128  return ruleset;
129 }
130 
131 static inline void LandlockEnforceRuleset(struct landlock_ruleset *ruleset)
132 {
133  if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
134  SCLogError("Can't self restrict (prctl phase): %s", strerror(errno));
135  return;
136  }
137  if (landlock_restrict_self(ruleset->fd, 0)) {
138  SCLogError("Can't self restrict (landlock phase): %s", strerror(errno));
139  }
140 }
141 
142 static int LandlockSandboxingAddRule(
143  struct landlock_ruleset *ruleset, const char *directory, uint64_t permission)
144 {
145  struct landlock_path_beneath_attr path_beneath = {
146  .allowed_access = permission & ruleset->attr.handled_access_fs,
147  };
148 
149  int dir_fd = open(directory, O_PATH | O_CLOEXEC | O_DIRECTORY);
150  if (dir_fd == -1) {
151  SCLogError("Can't open %s", directory);
152  return -1;
153  }
154  path_beneath.parent_fd = dir_fd;
155 
156  if (landlock_add_rule(ruleset->fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0)) {
157  SCLogError("Can't add write rule: %s", strerror(errno));
158  close(dir_fd);
159  return -1;
160  }
161 
162  close(dir_fd);
163  return 0;
164 }
165 
166 static inline void LandlockSandboxingWritePath(
167  struct landlock_ruleset *ruleset, const char *directory)
168 {
169  if (LandlockSandboxingAddRule(ruleset, directory, _LANDLOCK_SURI_ACCESS_FS_WRITE) == 0) {
170  SCLogConfig("Added write permission to '%s'", directory);
171  }
172 }
173 
174 static inline void LandlockSandboxingReadPath(
175  struct landlock_ruleset *ruleset, const char *directory)
176 {
177  if (LandlockSandboxingAddRule(ruleset, directory, _LANDLOCK_ACCESS_FS_READ) == 0) {
178  SCLogConfig("Added read permission to '%s'", directory);
179  }
180 }
181 
182 void LandlockSandboxing(SCInstance *suri)
183 {
184  /* Read configuration variable and exit if no enforcement */
185  int conf_status;
186  if (ConfGetBool("security.landlock.enabled", &conf_status) == 0) {
187  conf_status = 0;
188  }
189  if (!conf_status) {
190  SCLogConfig("Landlock is not enabled in configuration");
191  return;
192  }
193  struct landlock_ruleset *ruleset = LandlockCreateRuleset();
194  if (ruleset == NULL) {
195  SCLogError("Kernel does not support Landlock");
196  return;
197  }
198 
199  LandlockSandboxingWritePath(ruleset, ConfigGetLogDirectory());
200  struct stat sb;
201  if (stat(ConfigGetDataDirectory(), &sb) == 0) {
202  LandlockSandboxingAddRule(ruleset, ConfigGetDataDirectory(),
203  _LANDLOCK_SURI_ACCESS_FS_WRITE | _LANDLOCK_ACCESS_FS_READ);
204  }
206  LandlockSandboxingAddRule(ruleset, DetectEngineMpmCachingGetPath(),
207  _LANDLOCK_SURI_ACCESS_FS_WRITE | _LANDLOCK_ACCESS_FS_READ);
208  }
209  if (suri->run_mode == RUNMODE_PCAP_FILE) {
210  const char *pcap_file;
211  if (ConfGet("pcap-file.file", &pcap_file) == 1) {
212  char *file_name = SCStrdup(pcap_file);
213  if (file_name != NULL) {
214  struct stat statbuf;
215  if (stat(file_name, &statbuf) != -1) {
216  if (S_ISDIR(statbuf.st_mode)) {
217  LandlockSandboxingReadPath(ruleset, file_name);
218  } else {
219  LandlockSandboxingReadPath(ruleset, dirname(file_name));
220  }
221  } else {
222  SCLogError("Can't open pcap file");
223  }
224  SCFree(file_name);
225  }
226  }
227  }
228  if (suri->sig_file) {
229  char *file_name = SCStrdup(suri->sig_file);
230  if (file_name != NULL) {
231  LandlockSandboxingReadPath(ruleset, dirname(file_name));
232  SCFree(file_name);
233  }
234  }
235  if (suri->pid_filename) {
236  char *file_name = SCStrdup(suri->pid_filename);
237  if (file_name != NULL) {
238  LandlockSandboxingWritePath(ruleset, dirname(file_name));
239  SCFree(file_name);
240  }
241  }
242  if (ConfUnixSocketIsEnable()) {
243  const char *socketname;
244  if (ConfGet("unix-command.filename", &socketname) == 1) {
245  if (PathIsAbsolute(socketname)) {
246  char *file_name = SCStrdup(socketname);
247  if (file_name != NULL) {
248  LandlockSandboxingWritePath(ruleset, dirname(file_name));
249  SCFree(file_name);
250  }
251  } else {
252  LandlockSandboxingWritePath(ruleset, LOCAL_STATE_DIR "/run/suricata/");
253  }
254  } else {
255  LandlockSandboxingWritePath(ruleset, LOCAL_STATE_DIR "/run/suricata/");
256  }
257  }
258  if (!suri->sig_file_exclusive) {
259  const char *rule_path;
260  if (ConfGet("default-rule-path", &rule_path) == 1 && rule_path) {
261  LandlockSandboxingReadPath(ruleset, rule_path);
262  }
263  }
264 
265  ConfNode *read_dirs = ConfGetNode("security.landlock.directories.read");
266  if (read_dirs) {
267  if (!ConfNodeIsSequence(read_dirs)) {
268  SCLogWarning("Invalid security.landlock.directories.read configuration section: "
269  "expected a list of directory names.");
270  } else {
271  ConfNode *directory;
272  TAILQ_FOREACH (directory, &read_dirs->head, next) {
273  LandlockSandboxingReadPath(ruleset, directory->val);
274  }
275  }
276  }
277  ConfNode *write_dirs = ConfGetNode("security.landlock.directories.write");
278  if (write_dirs) {
279  if (!ConfNodeIsSequence(write_dirs)) {
280  SCLogWarning("Invalid security.landlock.directories.write configuration section: "
281  "expected a list of directory names.");
282  } else {
283  ConfNode *directory;
284  TAILQ_FOREACH (directory, &write_dirs->head, next) {
285  LandlockSandboxingWritePath(ruleset, directory->val);
286  }
287  }
288  }
289  LandlockEnforceRuleset(ruleset);
290  SCFree(ruleset);
291 }
292 
293 #endif /* HAVE_LINUX_LANDLOCK_H */
SCInstance_::run_mode
enum RunModes run_mode
Definition: suricata.h:124
detect-engine.h
ConfNode_::val
char * val
Definition: conf.h:34
ConfGetBool
int ConfGetBool(const char *name, int *val)
Retrieve a configuration value as a boolean.
Definition: conf.c:482
next
struct HtpBodyChunk_ * next
Definition: app-layer-htp.h:0
ConfGetNode
ConfNode * ConfGetNode(const char *name)
Get a ConfNode by name.
Definition: conf.c:181
TAILQ_FOREACH
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:252
ConfigGetDataDirectory
const char * ConfigGetDataDirectory(void)
Definition: util-conf.c:80
DetectEngineMpmCachingEnabled
bool DetectEngineMpmCachingEnabled(void)
Definition: detect-engine.c:2559
ConfGet
int ConfGet(const char *name, const char **vptr)
Retrieve the value of a configuration node.
Definition: conf.c:335
RequiresFeature
bool RequiresFeature(const char *feature_name)
Definition: feature.c:126
feature.h
SCLogWarning
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition: util-debug.h:249
util-landlock.h
util-mem.h
util-file.h
util-conf.h
FEATURE_OUTPUT_FILESTORE
#define FEATURE_OUTPUT_FILESTORE
Definition: feature.h:28
flags
uint8_t flags
Definition: decode-gre.h:0
util-path.h
PathIsAbsolute
int PathIsAbsolute(const char *path)
Check if a path is absolute.
Definition: util-path.c:44
ConfNodeIsSequence
int ConfNodeIsSequence(const ConfNode *node)
Check if a node is a sequence or node.
Definition: conf.c:911
SCStrdup
#define SCStrdup(s)
Definition: util-mem.h:56
SCInstance_::sig_file
char * sig_file
Definition: suricata.h:128
util-validate.h
ConfUnixSocketIsEnable
int ConfUnixSocketIsEnable(void)
Definition: util-conf.c:136
DetectEngineMpmCachingGetPath
const char * DetectEngineMpmCachingGetPath(void)
Definition: detect-engine.c:2570
SCLogConfig
struct SCLogConfig_ SCLogConfig
Holds the config state used by the logging api.
ConfigGetLogDirectory
const char * ConfigGetLogDirectory(void)
Definition: util-conf.c:38
SCLogError
#define SCLogError(...)
Macro used to log ERROR messages.
Definition: util-debug.h:261
SCFree
#define SCFree(p)
Definition: util-mem.h:61
ConfNode_
Definition: conf.h:32
SCInstance_::pid_filename
char * pid_filename
Definition: suricata.h:130
suricata.h
SCInstance_
Definition: suricata.h:123
SCInstance_::sig_file_exclusive
bool sig_file_exclusive
Definition: suricata.h:129
LandlockSandboxing
void LandlockSandboxing(SCInstance *suri)
Definition: util-landlock.c:36
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
RUNMODE_PCAP_FILE
@ RUNMODE_PCAP_FILE
Definition: runmodes.h:30
DEBUG_VALIDATE_BUG_ON
#define DEBUG_VALIDATE_BUG_ON(exp)
Definition: util-validate.h:102