aboutsummaryrefslogtreecommitdiff
path: root/src/xz/sandbox.c
blob: 5bd2273707519b7f638a2d158e28ea6848f7fcfb (plain) (blame)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
// SPDX-License-Identifier: 0BSD

///////////////////////////////////////////////////////////////////////////////
//
/// \file       sandbox.c
/// \brief      Sandbox support
//
//  Author:     Lasse Collin
//
///////////////////////////////////////////////////////////////////////////////

#include "private.h"


#ifndef ENABLE_SANDBOX

// Prevent an empty translation unit when no sandboxing is supported.
typedef int dummy;

#else

/// If the conditions for strict sandboxing (described in main())
/// have been met, sandbox_allow_strict() can be called to set this
/// variable to true.
static bool strict_sandbox_allowed = false;


extern void
sandbox_allow_strict(void)
{
	strict_sandbox_allowed = true;
	return;
}


// Strict sandboxing prevents opening any files. This *tries* to ensure
// that any auxiliary files that might be required are already open.
//
// Returns true if strict sandboxing is allowed, false otherwise.
static bool
prepare_for_strict_sandbox(void)
{
	if (!strict_sandbox_allowed)
		return false;

	const char dummy_str[] = "x";

	// Try to ensure that both libc and xz locale files have been
	// loaded when NLS is enabled.
	snprintf(NULL, 0, "%s%s", _(dummy_str), strerror(EINVAL));

	// Try to ensure that iconv data files needed for handling multibyte
	// characters have been loaded. This is needed at least with glibc.
	tuklib_mbstr_width(dummy_str, NULL);

	return true;
}

#endif


#if defined(HAVE_PLEDGE)

///////////////
// pledge(2) //
///////////////

#include <unistd.h>


extern void
sandbox_init(void)
{
	if (pledge("stdio rpath wpath cpath fattr", "")) {
		// gettext hasn't been initialized yet so
		// there's no point to call it here.
		message_fatal("Failed to enable the sandbox");
	}

	return;
}


extern void
sandbox_enable_read_only(void)
{
	// We will be opening files for reading but
	// won't create or remove any files.
	if (pledge("stdio rpath", ""))
		message_fatal(_("Failed to enable the sandbox"));

	return;
}


extern void
sandbox_enable_strict_if_allowed(int src_fd lzma_attribute((__unused__)),
		int pipe_event_fd lzma_attribute((__unused__)),
		int pipe_write_fd lzma_attribute((__unused__)))
{
	if (!prepare_for_strict_sandbox())
		return;

	// All files that need to be opened have already been opened.
	if (pledge("stdio", ""))
		message_fatal(_("Failed to enable the sandbox"));

	return;
}


#elif defined(HAVE_LINUX_LANDLOCK)

//////////////
// Landlock //
//////////////

#include <linux/landlock.h>
#include <sys/syscall.h>
#include <sys/prctl.h>


// Highest Landlock ABI version supported by this file:
//   - For ABI versions 1-3 we don't need anything from <linux/landlock.h>
//     that isn't part of version 1.
//   - For ABI version 4 we need the larger struct landlock_ruleset_attr
//     with the handled_access_net member. That is bundled with the macros
//     LANDLOCK_ACCESS_NET_BIND_TCP and LANDLOCK_ACCESS_NET_CONNECT_TCP.
#ifdef LANDLOCK_ACCESS_NET_BIND_TCP
#	define LANDLOCK_ABI_MAX 4
#else
#	define LANDLOCK_ABI_MAX 3
#endif


/// Landlock ABI version supported by the kernel
static int landlock_abi;


// The required_rights should have those bits set that must not be restricted.
// This function will then bitwise-and ~required_rights with a mask matching
// the Landlock ABI version, leaving only those bits set that are supported
// by the ABI and allowed to be restricted by the function argument.
static void
enable_landlock(uint64_t required_rights)
{
	assert(landlock_abi <= LANDLOCK_ABI_MAX);

	if (landlock_abi <= 0)
		return;

	// We want to set all supported flags in handled_access_fs.
	// This way the ruleset will initially forbid access to all
	// actions that the available Landlock ABI version supports.
	// Exceptions can be added using landlock_add_rule(2) to
	// allow certain actions on certain files or directories.
	//
	// The same flag values are used on all archs. ABI v2 and v3
	// both add one new flag.
	//
	// First in ABI v1: LANDLOCK_ACCESS_FS_EXECUTE = 1ULL << 0
	// Last in ABI v1: LANDLOCK_ACCESS_FS_MAKE_SYM = 1ULL << 12
	// Last in ABI v2: LANDLOCK_ACCESS_FS_REFER = 1ULL << 13
	// Last in ABI v3: LANDLOCK_ACCESS_FS_TRUNCATE = 1ULL << 14
	//
	// This makes it simple to set the mask based on the ABI
	// version and we don't need to care which flags are #defined
	// in the installed <linux/landlock.h> for ABI versions 1-3.
	const struct landlock_ruleset_attr attr = {
		.handled_access_fs = ~required_rights
			& ((1ULL << (12 + my_min(3, landlock_abi))) - 1),
#if LANDLOCK_ABI_MAX >= 4
		.handled_access_net = landlock_abi < 4 ? 0 :
				(LANDLOCK_ACCESS_NET_BIND_TCP
				| LANDLOCK_ACCESS_NET_CONNECT_TCP),
#endif
	};

	const int ruleset_fd = syscall(SYS_landlock_create_ruleset,
			&attr, sizeof(attr), 0U);
	if (ruleset_fd < 0)
		message_fatal(_("Failed to enable the sandbox"));

	// All files we need should have already been opened. Thus,
	// we don't need to add any rules using landlock_add_rule(2)
	// before activating the sandbox.
	//
	// NOTE: It's possible that the hack prepare_for_strict_sandbox()
	// isn't be good enough. It tries to get translations and
	// libc-specific files loaded but if it's not good enough
	// then perhaps a Landlock rule to allow reading from /usr
	// and/or the xz installation prefix would be needed.
	//
	// prctl(PR_SET_NO_NEW_PRIVS, ...) was already called in
	// sandbox_init() so we don't do it here again.
	if (syscall(SYS_landlock_restrict_self, ruleset_fd, 0U) != 0)
		message_fatal(_("Failed to enable the sandbox"));

	return;
}


extern void
sandbox_init(void)
{
	// Prevent the process from gaining new privileges. This must be done
	// before landlock_restrict_self(2) but since we will never need new
	// privileges, this call can be done here already.
	//
	// This is supported since Linux 3.5. Ignore the return value to
	// keep compatibility with old kernels. landlock_restrict_self(2)
	// will fail if the no_new_privs attribute isn't set, thus if prctl()
	// fails here the error will still be detected when it matters.
	(void)prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);

	// Get the highest Landlock ABI version supported by the kernel.
	landlock_abi = syscall(SYS_landlock_create_ruleset,
			(void *)NULL, 0, LANDLOCK_CREATE_RULESET_VERSION);

	// The kernel might support a newer ABI than this file.
	if (landlock_abi > LANDLOCK_ABI_MAX)
		landlock_abi = LANDLOCK_ABI_MAX;

	// These are all in ABI version 1 already. We don't need truncate
	// rights because files are created with open() using O_EXCL and
	// without O_TRUNC.
	//
	// LANDLOCK_ACCESS_FS_READ_DIR is included here to get a clear error
	// message if xz is given a directory name. Without this permission
	// the message would be "Permission denied" but with this permission
	// it's "Is a directory, skipping". It could be worked around with
	// stat()/lstat() but just giving this permission is simpler and
	// shouldn't make the sandbox much weaker in practice.
	const uint64_t required_rights
			= LANDLOCK_ACCESS_FS_WRITE_FILE
			| LANDLOCK_ACCESS_FS_READ_FILE
			| LANDLOCK_ACCESS_FS_READ_DIR
			| LANDLOCK_ACCESS_FS_REMOVE_FILE
			| LANDLOCK_ACCESS_FS_MAKE_REG;

	enable_landlock(required_rights);
	return;
}


extern void
sandbox_enable_read_only(void)
{
	// We will be opening files for reading but
	// won't create or remove any files.
	const uint64_t required_rights
			= LANDLOCK_ACCESS_FS_READ_FILE
			| LANDLOCK_ACCESS_FS_READ_DIR;
	enable_landlock(required_rights);
	return;
}


extern void
sandbox_enable_strict_if_allowed(int src_fd lzma_attribute((__unused__)),
		int pipe_event_fd lzma_attribute((__unused__)),
		int pipe_write_fd lzma_attribute((__unused__)))
{
	if (!prepare_for_strict_sandbox())
		return;

	// Allow all restrictions that the kernel supports with the
	// highest Landlock ABI version that the kernel or xz supports.
	//
	// NOTE: LANDLOCK_ACCESS_FS_READ_DIR isn't needed here because
	// the only input file has already been opened.
	enable_landlock(0);
	return;
}


#elif defined(HAVE_CAP_RIGHTS_LIMIT)

//////////////
// Capsicum //
//////////////

#include <sys/capsicum.h>


extern void
sandbox_init(void)
{
	// Nothing to do.
	return;
}


extern void
sandbox_enable_read_only(void)
{
	// Nothing to do.
	return;
}


extern void
sandbox_enable_strict_if_allowed(
		int src_fd, int pipe_event_fd, int pipe_write_fd)
{
	if (!prepare_for_strict_sandbox())
		return;

	// Capsicum needs FreeBSD 10.2 or later.
	cap_rights_t rights;

	if (cap_enter())
		goto error;

	if (cap_rights_limit(src_fd, cap_rights_init(&rights,
			CAP_EVENT, CAP_FCNTL, CAP_LOOKUP, CAP_READ, CAP_SEEK)))
		goto error;

	// If not reading from stdin, remove all capabilities from it.
	if (src_fd != STDIN_FILENO && cap_rights_limit(
			STDIN_FILENO, cap_rights_clear(&rights)))
		goto error;

	if (cap_rights_limit(STDOUT_FILENO, cap_rights_init(&rights,
			CAP_EVENT, CAP_FCNTL, CAP_FSTAT, CAP_LOOKUP,
			CAP_WRITE, CAP_SEEK)))
		goto error;

	if (cap_rights_limit(STDERR_FILENO, cap_rights_init(&rights,
			CAP_WRITE)))
		goto error;

	if (cap_rights_limit(pipe_event_fd, cap_rights_init(&rights,
			CAP_EVENT)))
		goto error;

	if (cap_rights_limit(pipe_write_fd, cap_rights_init(&rights,
			CAP_WRITE)))
		goto error;

	return;

error:
	// If a kernel is configured without capability mode support or
	// used in an emulator that does not implement the capability
	// system calls, then the Capsicum system calls will fail and set
	// errno to ENOSYS. In that case xz will silently run without
	// the sandbox.
	if (errno == ENOSYS)
		return;

	message_fatal(_("Failed to enable the sandbox"));
}

#endif