Coverage Report

Created: 2025-11-16 07:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/suricata7/src/util-landlock.c
Line
Count
Source
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 "feature.h"
26
#include "util-conf.h"
27
#include "util-file.h"
28
#include "util-landlock.h"
29
#include "util-mem.h"
30
#include "util-path.h"
31
32
#ifndef HAVE_LINUX_LANDLOCK_H
33
34
void LandlockSandboxing(SCInstance *suri)
35
0
{
36
0
    return;
37
0
}
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
    return syscall(__NR_landlock_create_ruleset, attr, size, flags);
48
}
49
#endif
50
51
#ifndef landlock_add_rule
52
static inline int landlock_add_rule(const int ruleset_fd, const enum landlock_rule_type rule_type,
53
        const void *const rule_attr, const __u32 flags)
54
{
55
    return syscall(__NR_landlock_add_rule, ruleset_fd, rule_type, rule_attr, flags);
56
}
57
#endif
58
59
#ifndef landlock_restrict_self
60
static inline int landlock_restrict_self(const int ruleset_fd, const __u32 flags)
61
{
62
    return syscall(__NR_landlock_restrict_self, ruleset_fd, flags);
63
}
64
#endif
65
66
#ifndef LANDLOCK_ACCESS_FS_REFER
67
#define LANDLOCK_ACCESS_FS_REFER (1ULL << 13)
68
#endif
69
70
#define _LANDLOCK_ACCESS_FS_WRITE                                                                  \
71
    (LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_REMOVE_DIR |                               \
72
            LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_CHAR |                        \
73
            LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG |                            \
74
            LANDLOCK_ACCESS_FS_MAKE_SOCK | LANDLOCK_ACCESS_FS_MAKE_FIFO |                          \
75
            LANDLOCK_ACCESS_FS_MAKE_BLOCK | LANDLOCK_ACCESS_FS_MAKE_SYM |                          \
76
            LANDLOCK_ACCESS_FS_REFER)
77
78
#define _LANDLOCK_ACCESS_FS_READ (LANDLOCK_ACCESS_FS_READ_FILE | LANDLOCK_ACCESS_FS_READ_DIR)
79
80
#define _LANDLOCK_SURI_ACCESS_FS_WRITE                                                             \
81
    (LANDLOCK_ACCESS_FS_WRITE_FILE | LANDLOCK_ACCESS_FS_MAKE_DIR | LANDLOCK_ACCESS_FS_MAKE_REG |   \
82
            LANDLOCK_ACCESS_FS_REMOVE_FILE | LANDLOCK_ACCESS_FS_MAKE_SOCK)
83
84
struct landlock_ruleset {
85
    int fd;
86
    struct landlock_ruleset_attr attr;
87
};
88
89
static inline struct landlock_ruleset *LandlockCreateRuleset(void)
90
{
91
    struct landlock_ruleset *ruleset = SCCalloc(1, sizeof(struct landlock_ruleset));
92
    if (ruleset == NULL) {
93
        SCLogError("Can't alloc landlock ruleset");
94
        return NULL;
95
    }
96
97
    ruleset->attr.handled_access_fs =
98
            _LANDLOCK_ACCESS_FS_READ | _LANDLOCK_ACCESS_FS_WRITE | LANDLOCK_ACCESS_FS_EXECUTE;
99
100
    int abi = landlock_create_ruleset(NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);
101
    if (abi < 0) {
102
        SCFree(ruleset);
103
        return NULL;
104
    }
105
    if (abi < 2) {
106
        if (RequiresFeature(FEATURE_OUTPUT_FILESTORE)) {
107
            SCLogError("Landlock disabled: need Linux 5.19+ for file store support");
108
            SCFree(ruleset);
109
            return NULL;
110
        } else {
111
            ruleset->attr.handled_access_fs &= ~LANDLOCK_ACCESS_FS_REFER;
112
        }
113
    }
114
115
    ruleset->fd = landlock_create_ruleset(&ruleset->attr, sizeof(ruleset->attr), 0);
116
    if (ruleset->fd < 0) {
117
        SCFree(ruleset);
118
        SCLogError("Can't create landlock ruleset");
119
        return NULL;
120
    }
121
    return ruleset;
122
}
123
124
static inline void LandlockEnforceRuleset(struct landlock_ruleset *ruleset)
125
{
126
    if (prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0) == -1) {
127
        SCLogError("Can't self restrict (prctl phase): %s", strerror(errno));
128
        return;
129
    }
130
    if (landlock_restrict_self(ruleset->fd, 0)) {
131
        SCLogError("Can't self restrict (landlock phase): %s", strerror(errno));
132
    }
133
}
134
135
static int LandlockSandboxingAddRule(
136
        struct landlock_ruleset *ruleset, const char *directory, uint64_t permission)
137
{
138
    struct landlock_path_beneath_attr path_beneath = {
139
        .allowed_access = permission & ruleset->attr.handled_access_fs,
140
    };
141
142
    int dir_fd = open(directory, O_PATH | O_CLOEXEC | O_DIRECTORY);
143
    if (dir_fd == -1) {
144
        SCLogError("Can't open %s", directory);
145
        return -1;
146
    }
147
    path_beneath.parent_fd = dir_fd;
148
149
    if (landlock_add_rule(ruleset->fd, LANDLOCK_RULE_PATH_BENEATH, &path_beneath, 0)) {
150
        SCLogError("Can't add write rule: %s", strerror(errno));
151
        close(dir_fd);
152
        return -1;
153
    }
154
155
    close(dir_fd);
156
    return 0;
157
}
158
159
static inline void LandlockSandboxingWritePath(
160
        struct landlock_ruleset *ruleset, const char *directory)
161
{
162
    if (LandlockSandboxingAddRule(ruleset, directory, _LANDLOCK_SURI_ACCESS_FS_WRITE) == 0) {
163
        SCLogConfig("Added write permission to '%s'", directory);
164
    }
165
}
166
167
static inline void LandlockSandboxingReadPath(
168
        struct landlock_ruleset *ruleset, const char *directory)
169
{
170
    if (LandlockSandboxingAddRule(ruleset, directory, _LANDLOCK_ACCESS_FS_READ) == 0) {
171
        SCLogConfig("Added read permission to '%s'", directory);
172
    }
173
}
174
175
void LandlockSandboxing(SCInstance *suri)
176
{
177
    /* Read configuration variable and exit if no enforcement */
178
    int conf_status;
179
    if (ConfGetBool("security.landlock.enabled", &conf_status) == 0) {
180
        conf_status = 0;
181
    }
182
    if (!conf_status) {
183
        SCLogConfig("Landlock is not enabled in configuration");
184
        return;
185
    }
186
    struct landlock_ruleset *ruleset = LandlockCreateRuleset();
187
    if (ruleset == NULL) {
188
        SCLogError("Kernel does not support Landlock");
189
        return;
190
    }
191
192
    LandlockSandboxingWritePath(ruleset, ConfigGetLogDirectory());
193
    struct stat sb;
194
    if (stat(ConfigGetDataDirectory(), &sb) == 0) {
195
        LandlockSandboxingAddRule(ruleset, ConfigGetDataDirectory(),
196
                _LANDLOCK_SURI_ACCESS_FS_WRITE | _LANDLOCK_ACCESS_FS_READ);
197
    }
198
    if (suri->run_mode == RUNMODE_PCAP_FILE) {
199
        const char *pcap_file;
200
        if (ConfGet("pcap-file.file", &pcap_file) == 1) {
201
            char *file_name = SCStrdup(pcap_file);
202
            if (file_name != NULL) {
203
                struct stat statbuf;
204
                if (stat(file_name, &statbuf) != -1) {
205
                    if (S_ISDIR(statbuf.st_mode)) {
206
                        LandlockSandboxingReadPath(ruleset, file_name);
207
                    } else {
208
                        LandlockSandboxingReadPath(ruleset, dirname(file_name));
209
                    }
210
                } else {
211
                    SCLogError("Can't open pcap file");
212
                }
213
                SCFree(file_name);
214
            }
215
        }
216
    }
217
    if (suri->sig_file) {
218
        char *file_name = SCStrdup(suri->sig_file);
219
        if (file_name != NULL) {
220
            LandlockSandboxingReadPath(ruleset, dirname(file_name));
221
            SCFree(file_name);
222
        }
223
    }
224
    if (suri->pid_filename) {
225
        char *file_name = SCStrdup(suri->pid_filename);
226
        if (file_name != NULL) {
227
            LandlockSandboxingWritePath(ruleset, dirname(file_name));
228
            SCFree(file_name);
229
        }
230
    }
231
    if (ConfUnixSocketIsEnable()) {
232
        const char *socketname;
233
        if (ConfGet("unix-command.filename", &socketname) == 1) {
234
            if (PathIsAbsolute(socketname)) {
235
                char *file_name = SCStrdup(socketname);
236
                if (file_name != NULL) {
237
                    LandlockSandboxingWritePath(ruleset, dirname(file_name));
238
                    SCFree(file_name);
239
                }
240
            } else {
241
                LandlockSandboxingWritePath(ruleset, LOCAL_STATE_DIR "/run/suricata/");
242
            }
243
        } else {
244
            LandlockSandboxingWritePath(ruleset, LOCAL_STATE_DIR "/run/suricata/");
245
        }
246
    }
247
    if (suri->sig_file_exclusive == FALSE) {
248
        const char *rule_path;
249
        if (ConfGet("default-rule-path", &rule_path) == 1 && rule_path) {
250
            LandlockSandboxingReadPath(ruleset, rule_path);
251
        }
252
    }
253
254
    ConfNode *read_dirs = ConfGetNode("security.landlock.directories.read");
255
    if (read_dirs) {
256
        if (!ConfNodeIsSequence(read_dirs)) {
257
            SCLogWarning("Invalid security.landlock.directories.read configuration section: "
258
                         "expected a list of directory names.");
259
        } else {
260
            ConfNode *directory;
261
            TAILQ_FOREACH (directory, &read_dirs->head, next) {
262
                LandlockSandboxingReadPath(ruleset, directory->val);
263
            }
264
        }
265
    }
266
    ConfNode *write_dirs = ConfGetNode("security.landlock.directories.write");
267
    if (write_dirs) {
268
        if (!ConfNodeIsSequence(write_dirs)) {
269
            SCLogWarning("Invalid security.landlock.directories.write configuration section: "
270
                         "expected a list of directory names.");
271
        } else {
272
            ConfNode *directory;
273
            TAILQ_FOREACH (directory, &write_dirs->head, next) {
274
                LandlockSandboxingWritePath(ruleset, directory->val);
275
            }
276
        }
277
    }
278
    LandlockEnforceRuleset(ruleset);
279
    SCFree(ruleset);
280
}
281
282
#endif /* HAVE_LINUX_LANDLOCK_H */