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-path.h"
31 #include "util-validate.h"
32 
33 #ifndef HAVE_LINUX_LANDLOCK_H
34 
36 {
37 }
38 
39 #else /* HAVE_LINUX_LANDLOCK_H */
40 
41 #include <linux/landlock.h>
42 
43 #ifndef landlock_create_ruleset
44 static inline int landlock_create_ruleset(
45  const struct landlock_ruleset_attr *const attr, const size_t size, const __u32 flags)
46 {
47  long r = syscall(__NR_landlock_create_ruleset, attr, size, flags);
48  DEBUG_VALIDATE_BUG_ON(r > INT_MAX);
49  return (int)r;
50 }
51 #endif
52 
53 #ifndef landlock_add_rule
54 static inline int landlock_add_rule(const int ruleset_fd, const enum landlock_rule_type rule_type,
55  const void *const rule_attr, const __u32 flags)
56 {
57  long r = syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr, flags);
58  DEBUG_VALIDATE_BUG_ON(r > INT_MAX);
59  return (int)r;
60 }
61 #endif
62 
63 #ifndef landlock_restrict_self
64 static inline int landlock_restrict_self(const int ruleset_fd, const __u32 flags)
65 {
66  long r = syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
67  DEBUG_VALIDATE_BUG_ON(r > INT_MAX);
68  return (int)r;
69 }
70 #endif
71 
72 #ifndef LANDLOCK_ACCESS_FS_REFER
73 #define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
74 #endif
75 
76 #define _LANDLOCK_ACCESS_FS_WRITE \
77  (LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_DIR | \
78  LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_CHAR | \
79  LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | \
80  LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO | \
81  LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM | \
82  LANDLOCK_ACCESS_FS_REFER)
83 
84 #define _LANDLOCK_ACCESS_FS_READ (LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR)
85 
86 #define _LANDLOCK_SURI_ACCESS_FS_WRITE \
87  (LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG | \
88  LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_SOCK)
89 
90 struct landlock_ruleset {
91  int fd;
92  struct landlock_ruleset_attr attr;
93 };
94 
95 static inline struct landlock_ruleset *LandlockCreateRuleset(void)
96 {
97  struct landlock_ruleset *ruleset = SCCalloc(1, sizeof(struct landlock_ruleset));
98  if (ruleset == NULL) {
99  SCLogError("Can't alloc landlock ruleset");
100  return NULL;
101  }
102 
103  ruleset->attr.handled_access_fs =
104  _LANDLOCK_ACCESS_FS_READ | _LANDLOCK_ACCESS_FS_WRITE | LANDLOCK_ACCESS_FS_EXECUTE;
105 
106  int abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
107  if (abi < 0) {
108  SCFree(ruleset);
109  return NULL;
110  }
111  if (abi < 2) {
113  SCLogError("Landlock disabled: need Linux 5.19+ for file store support");
114  SCFree(ruleset);
115  return NULL;
116  } else {
117  ruleset->attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER;
118  }
119  }
120 
121  ruleset->fd = landlock_create_ruleset(&ruleset->attr, sizeof(ruleset->attr), 0);
122  if (ruleset->fd < 0) {
123  SCFree(ruleset);
124  SCLogError("Can't create landlock ruleset");
125  return NULL;
126  }
127  return ruleset;
128 }
129 
130 static inline void LandlockEnforceRuleset(struct landlock_ruleset *ruleset)
131 {
132  if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
133  SCLogError("Can't self restrict (prctl phase): %s", strerror(errno));
134  return;
135  }
136  if (landlock_restrict_self(ruleset->fd, 0)) {
137  SCLogError("Can't self restrict (landlock phase): %s", strerror(errno));
138  }
139 }
140 
141 static int LandlockSandboxingAddRule(
142  struct landlock_ruleset *ruleset, const char *directory, uint64_t permission)
143 {
144  struct landlock_path_beneath_attr path_beneath = {
145  .allowed_access = permission & ruleset->attr.handled_access_fs,
146  };
147 
148  int dir_fd = open(directory, O_PATH | O_CLOEXEC | O_DIRECTORY);
149  if (dir_fd == -1) {
150  SCLogError("Can't open %s", directory);
151  return -1;
152  }
153  path_beneath.parent_fd = dir_fd;
154 
155  if (landlock_add_rule(ruleset->fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0)) {
156  SCLogError("Can't add write rule: %s", strerror(errno));
157  close(dir_fd);
158  return -1;
159  }
160 
161  close(dir_fd);
162  return 0;
163 }
164 
165 static inline void LandlockSandboxingWritePath(
166  struct landlock_ruleset *ruleset, const char *directory)
167 {
168  if (LandlockSandboxingAddRule(ruleset, directory, _LANDLOCK_SURI_ACCESS_FS_WRITE) == 0) {
169  SCLogConfig("Added write permission to '%s'", directory);
170  }
171 }
172 
173 static inline void LandlockSandboxingReadPath(
174  struct landlock_ruleset *ruleset, const char *directory)
175 {
176  if (LandlockSandboxingAddRule(ruleset, directory, _LANDLOCK_ACCESS_FS_READ) == 0) {
177  SCLogConfig("Added read permission to '%s'", directory);
178  }
179 }
180 
181 void LandlockSandboxing(SCInstance *suri)
182 {
183  /* Read configuration variable and exit if no enforcement */
184  int conf_status;
185  if (SCConfGetBool("security.landlock.enabled", &conf_status) == 0) {
186  conf_status = 0;
187  }
188  if (!conf_status) {
189  SCLogConfig("Landlock is not enabled in configuration");
190  return;
191  }
192  struct landlock_ruleset *ruleset = LandlockCreateRuleset();
193  if (ruleset == NULL) {
194  SCLogError("Kernel does not support Landlock");
195  return;
196  }
197 
198  LandlockSandboxingWritePath(ruleset, SCConfigGetLogDirectory());
199  struct stat sb;
200  if (stat(ConfigGetDataDirectory(), &sb) == 0) {
201  LandlockSandboxingAddRule(ruleset, ConfigGetDataDirectory(),
202  _LANDLOCK_SURI_ACCESS_FS_WRITE | _LANDLOCK_ACCESS_FS_READ);
203  }
205  LandlockSandboxingAddRule(ruleset, DetectEngineMpmCachingGetPath(),
206  _LANDLOCK_SURI_ACCESS_FS_WRITE | _LANDLOCK_ACCESS_FS_READ);
207  }
208  if (suri->run_mode == RUNMODE_PCAP_FILE) {
209  const char *pcap_file;
210  if (SCConfGetNonNull("pcap-file.file", &pcap_file) == 1) {
211  char *file_name = SCStrdup(pcap_file);
212  if (file_name != NULL) {
213  struct stat statbuf;
214  if (stat(file_name, &statbuf) != -1) {
215  if (S_ISDIR(statbuf.st_mode)) {
216  LandlockSandboxingReadPath(ruleset, file_name);
217  } else {
218  LandlockSandboxingReadPath(ruleset, dirname(file_name));
219  }
220  } else {
221  SCLogError("Can't open pcap file");
222  }
223  SCFree(file_name);
224  }
225  }
226  }
227  if (suri->sig_file) {
228  char *file_name = SCStrdup(suri->sig_file);
229  if (file_name != NULL) {
230  LandlockSandboxingReadPath(ruleset, dirname(file_name));
231  SCFree(file_name);
232  }
233  }
234  if (suri->pid_filename) {
235  char *file_name = SCStrdup(suri->pid_filename);
236  if (file_name != NULL) {
237  LandlockSandboxingWritePath(ruleset, dirname(file_name));
238  SCFree(file_name);
239  }
240  }
241  if (ConfUnixSocketIsEnable()) {
242  const char *socketname;
243  if (SCConfGetNonNull("unix-command.filename", &socketname) == 1) {
244  if (PathIsAbsolute(socketname)) {
245  char *file_name = SCStrdup(socketname);
246  if (file_name != NULL) {
247  LandlockSandboxingWritePath(ruleset, dirname(file_name));
248  SCFree(file_name);
249  }
250  } else {
251  LandlockSandboxingWritePath(ruleset, LOCAL_STATE_DIR "/run/suricata/");
252  }
253  } else {
254  LandlockSandboxingWritePath(ruleset, LOCAL_STATE_DIR "/run/suricata/");
255  }
256  }
257  if (!suri->sig_file_exclusive) {
258  const char *rule_path;
259  if (SCConfGetNonNull("default-rule-path", &rule_path) == 1 && rule_path) {
260  LandlockSandboxingReadPath(ruleset, rule_path);
261  }
262  }
263 
264  SCConfNode *read_dirs = SCConfGetNode("security.landlock.directories.read");
265  if (read_dirs) {
266  if (!SCConfNodeIsSequence(read_dirs)) {
267  SCLogWarning("Invalid security.landlock.directories.read configuration section: "
268  "expected a list of directory names.");
269  } else {
270  SCConfNode *directory;
271  TAILQ_FOREACH (directory, &read_dirs->head, next) {
272  LandlockSandboxingReadPath(ruleset, directory->val);
273  }
274  }
275  }
276  SCConfNode *write_dirs = SCConfGetNode("security.landlock.directories.write");
277  if (write_dirs) {
278  if (!SCConfNodeIsSequence(write_dirs)) {
279  SCLogWarning("Invalid security.landlock.directories.write configuration section: "
280  "expected a list of directory names.");
281  } else {
282  SCConfNode *directory;
283  TAILQ_FOREACH (directory, &write_dirs->head, next) {
284  LandlockSandboxingWritePath(ruleset, directory->val);
285  }
286  }
287  }
288  LandlockEnforceRuleset(ruleset);
289  SCFree(ruleset);
290 }
291 
292 #endif /* HAVE_LINUX_LANDLOCK_H */
SCInstance_::run_mode
enum SCRunModes run_mode
Definition: suricata.h:135
detect-engine.h
next
struct HtpBodyChunk_ * next
Definition: app-layer-htp.h:0
TAILQ_FOREACH
#define TAILQ_FOREACH(var, head, field)
Definition: queue.h:252
SCConfGetBool
int SCConfGetBool(const char *name, int *val)
Retrieve a configuration value as a boolean.
Definition: conf.c:523
ConfigGetDataDirectory
const char * ConfigGetDataDirectory(void)
Definition: util-conf.c:85
DetectEngineMpmCachingEnabled
bool DetectEngineMpmCachingEnabled(void)
Definition: detect-engine.c:2683
SCConfNodeIsSequence
int SCConfNodeIsSequence(const SCConfNode *node)
Check if a node is a sequence or node.
Definition: conf.c:998
feature.h
SCConfGetNonNull
int SCConfGetNonNull(const char *name, const char **vptr)
Retrieve the non-null value of a configuration node.
Definition: conf.c:380
SCLogWarning
#define SCLogWarning(...)
Macro used to log WARNING messages.
Definition: util-debug.h:262
SCConfigGetLogDirectory
const char * SCConfigGetLogDirectory(void)
Definition: util-conf.c:38
util-landlock.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
SCStrdup
#define SCStrdup(s)
Definition: util-mem.h:56
SCInstance_::sig_file
char * sig_file
Definition: suricata.h:139
util-validate.h
ConfUnixSocketIsEnable
int ConfUnixSocketIsEnable(void)
Definition: util-conf.c:141
DetectEngineMpmCachingGetPath
const char * DetectEngineMpmCachingGetPath(void)
Definition: detect-engine.c:2692
SCLogConfig
struct SCLogConfig_ SCLogConfig
Holds the config state used by the logging api.
SCConfGetNode
SCConfNode * SCConfGetNode(const char *name)
Get a SCConfNode by name.
Definition: conf.c:183
SCLogError
#define SCLogError(...)
Macro used to log ERROR messages.
Definition: util-debug.h:274
SCFree
#define SCFree(p)
Definition: util-mem.h:61
SCInstance_::pid_filename
char * pid_filename
Definition: suricata.h:141
SCRequiresFeature
bool SCRequiresFeature(const char *feature_name)
Definition: feature.c:121
suricata.h
SCInstance_
Definition: suricata.h:134
SCInstance_::sig_file_exclusive
bool sig_file_exclusive
Definition: suricata.h:140
LandlockSandboxing
void LandlockSandboxing(SCInstance *suri)
Definition: util-landlock.c:35
RUNMODE_PCAP_FILE
@ RUNMODE_PCAP_FILE
Definition: runmodes.h:30
SCCalloc
#define SCCalloc(nm, sz)
Definition: util-mem.h:53
SCConfNode_
Definition: conf.h:37
SCConfNode_::val
char * val
Definition: conf.h:39
DEBUG_VALIDATE_BUG_ON
#define DEBUG_VALIDATE_BUG_ON(exp)
Definition: util-validate.h:109