xref: /freebsd/contrib/xz/src/xz/sandbox.c (revision 128836d304d93f2d00eb14069c27089ab46c38d4)
1 // SPDX-License-Identifier: 0BSD
2 
3 ///////////////////////////////////////////////////////////////////////////////
4 //
5 /// \file       sandbox.c
6 /// \brief      Sandbox support
7 //
8 //  Author:     Lasse Collin
9 //
10 ///////////////////////////////////////////////////////////////////////////////
11 
12 #include "private.h"
13 
14 
15 #ifndef ENABLE_SANDBOX
16 
17 // Prevent an empty translation unit when no sandboxing is supported.
18 typedef int dummy;
19 
20 #else
21 
22 /// If the conditions for strict sandboxing (described in main())
23 /// have been met, sandbox_allow_strict() can be called to set this
24 /// variable to true.
25 static bool strict_sandbox_allowed = false;
26 
27 
28 extern void
sandbox_allow_strict(void)29 sandbox_allow_strict(void)
30 {
31 	strict_sandbox_allowed = true;
32 	return;
33 }
34 
35 
36 // Strict sandboxing prevents opening any files. This *tries* to ensure
37 // that any auxiliary files that might be required are already open.
38 //
39 // Returns true if strict sandboxing is allowed, false otherwise.
40 static bool
prepare_for_strict_sandbox(void)41 prepare_for_strict_sandbox(void)
42 {
43 	if (!strict_sandbox_allowed)
44 		return false;
45 
46 	const char dummy_str[] = "x";
47 
48 	// Try to ensure that both libc and xz locale files have been
49 	// loaded when NLS is enabled.
50 	snprintf(NULL, 0, "%s%s", _(dummy_str), strerror(EINVAL));
51 
52 	// Try to ensure that iconv data files needed for handling multibyte
53 	// characters have been loaded. This is needed at least with glibc.
54 	tuklib_mbstr_width(dummy_str, NULL);
55 
56 	return true;
57 }
58 
59 #endif
60 
61 
62 #if defined(HAVE_PLEDGE)
63 
64 ///////////////
65 // pledge(2) //
66 ///////////////
67 
68 #include <unistd.h>
69 
70 
71 extern void
sandbox_init(void)72 sandbox_init(void)
73 {
74 	if (pledge("stdio rpath wpath cpath fattr", "")) {
75 		// gettext hasn't been initialized yet so
76 		// there's no point to call it here.
77 		message_fatal("Failed to enable the sandbox");
78 	}
79 
80 	return;
81 }
82 
83 
84 extern void
sandbox_enable_read_only(void)85 sandbox_enable_read_only(void)
86 {
87 	// We will be opening files for reading but
88 	// won't create or remove any files.
89 	if (pledge("stdio rpath", ""))
90 		message_fatal(_("Failed to enable the sandbox"));
91 
92 	return;
93 }
94 
95 
96 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__)))97 sandbox_enable_strict_if_allowed(int src_fd lzma_attribute((__unused__)),
98 		int pipe_event_fd lzma_attribute((__unused__)),
99 		int pipe_write_fd lzma_attribute((__unused__)))
100 {
101 	if (!prepare_for_strict_sandbox())
102 		return;
103 
104 	// All files that need to be opened have already been opened.
105 	if (pledge("stdio", ""))
106 		message_fatal(_("Failed to enable the sandbox"));
107 
108 	return;
109 }
110 
111 
112 #elif defined(HAVE_LINUX_LANDLOCK)
113 
114 //////////////
115 // Landlock //
116 //////////////
117 
118 #include "my_landlock.h"
119 
120 
121 // The required_rights should have those bits set that must not be restricted.
122 // This function will then bitwise-and ~required_rights with a mask matching
123 // the Landlock ABI version, leaving only those bits set that are supported
124 // by the ABI and allowed to be restricted by the function argument.
125 static void
enable_landlock(uint64_t required_rights)126 enable_landlock(uint64_t required_rights)
127 {
128 	// Initialize the ruleset to forbid all actions that the available
129 	// Landlock ABI version supports. Return if Landlock isn't supported
130 	// at all.
131 	struct landlock_ruleset_attr attr;
132 	if (my_landlock_ruleset_attr_forbid_all(&attr) == -1)
133 		return;
134 
135 	// Allow the required rights.
136 	attr.handled_access_fs &= ~required_rights;
137 
138 	// Create the ruleset in the kernel. This shouldn't fail.
139 	const int ruleset_fd = my_landlock_create_ruleset(
140 			&attr, sizeof(attr), 0);
141 	if (ruleset_fd < 0)
142 		message_fatal(_("Failed to enable the sandbox"));
143 
144 	// All files we need should have already been opened. Thus,
145 	// we don't need to add any rules using landlock_add_rule(2)
146 	// before activating the sandbox.
147 	//
148 	// NOTE: It's possible that the hack prepare_for_strict_sandbox()
149 	// isn't be good enough. It tries to get translations and
150 	// libc-specific files loaded but if it's not good enough
151 	// then perhaps a Landlock rule to allow reading from /usr
152 	// and/or the xz installation prefix would be needed.
153 	//
154 	// prctl(PR_SET_NO_NEW_PRIVS, ...) was already called in
155 	// sandbox_init() so we don't do it here again.
156 	if (my_landlock_restrict_self(ruleset_fd, 0) != 0)
157 		message_fatal(_("Failed to enable the sandbox"));
158 
159 	(void)close(ruleset_fd);
160 	return;
161 }
162 
163 
164 extern void
sandbox_init(void)165 sandbox_init(void)
166 {
167 	// Prevent the process from gaining new privileges. This must be done
168 	// before landlock_restrict_self(2) but since we will never need new
169 	// privileges, this call can be done here already.
170 	//
171 	// This is supported since Linux 3.5. Ignore the return value to
172 	// keep compatibility with old kernels. landlock_restrict_self(2)
173 	// will fail if the no_new_privs attribute isn't set, thus if prctl()
174 	// fails here the error will still be detected when it matters.
175 	(void)prctl(PR_SET_NO_NEW_PRIVS, 1, 0, 0, 0);
176 
177 	// These are all in ABI version 1 already. We don't need truncate
178 	// rights because files are created with open() using O_EXCL and
179 	// without O_TRUNC.
180 	//
181 	// LANDLOCK_ACCESS_FS_READ_DIR is required to synchronize the
182 	// directory before removing the source file.
183 	//
184 	// LANDLOCK_ACCESS_FS_READ_DIR is also helpful to show a clear error
185 	// message if xz is given a directory name. Without this permission
186 	// the message would be "Permission denied" but with this permission
187 	// it's "Is a directory, skipping". It could be worked around with
188 	// stat()/lstat() but just giving this permission is simpler and
189 	// shouldn't make the sandbox much weaker in practice.
190 	const uint64_t required_rights
191 			= LANDLOCK_ACCESS_FS_WRITE_FILE
192 			| LANDLOCK_ACCESS_FS_READ_FILE
193 			| LANDLOCK_ACCESS_FS_READ_DIR
194 			| LANDLOCK_ACCESS_FS_REMOVE_FILE
195 			| LANDLOCK_ACCESS_FS_MAKE_REG;
196 
197 	enable_landlock(required_rights);
198 	return;
199 }
200 
201 
202 extern void
sandbox_enable_read_only(void)203 sandbox_enable_read_only(void)
204 {
205 	// We will be opening files for reading but
206 	// won't create or remove any files.
207 	const uint64_t required_rights
208 			= LANDLOCK_ACCESS_FS_READ_FILE
209 			| LANDLOCK_ACCESS_FS_READ_DIR;
210 	enable_landlock(required_rights);
211 	return;
212 }
213 
214 
215 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__)))216 sandbox_enable_strict_if_allowed(int src_fd lzma_attribute((__unused__)),
217 		int pipe_event_fd lzma_attribute((__unused__)),
218 		int pipe_write_fd lzma_attribute((__unused__)))
219 {
220 	if (!prepare_for_strict_sandbox())
221 		return;
222 
223 	// Allow all restrictions that the kernel supports with the
224 	// highest Landlock ABI version that the kernel or xz supports.
225 	//
226 	// NOTE: LANDLOCK_ACCESS_FS_READ_DIR isn't needed here because
227 	// the only input file has already been opened.
228 	enable_landlock(0);
229 	return;
230 }
231 
232 
233 #elif defined(HAVE_CAP_RIGHTS_LIMIT)
234 
235 //////////////
236 // Capsicum //
237 //////////////
238 
239 #include <sys/capsicum.h>
240 
241 
242 extern void
sandbox_init(void)243 sandbox_init(void)
244 {
245 	// Nothing to do.
246 	return;
247 }
248 
249 
250 extern void
sandbox_enable_read_only(void)251 sandbox_enable_read_only(void)
252 {
253 	// Nothing to do.
254 	return;
255 }
256 
257 
258 extern void
sandbox_enable_strict_if_allowed(int src_fd,int pipe_event_fd,int pipe_write_fd)259 sandbox_enable_strict_if_allowed(
260 		int src_fd, int pipe_event_fd, int pipe_write_fd)
261 {
262 	if (!prepare_for_strict_sandbox())
263 		return;
264 
265 	// Capsicum needs FreeBSD 10.2 or later.
266 	cap_rights_t rights;
267 
268 	if (cap_enter())
269 		goto error;
270 
271 	if (cap_rights_limit(src_fd, cap_rights_init(&rights,
272 			CAP_EVENT, CAP_FCNTL, CAP_LOOKUP, CAP_READ, CAP_SEEK)))
273 		goto error;
274 
275 	// If not reading from stdin, remove all capabilities from it.
276 	if (src_fd != STDIN_FILENO && cap_rights_limit(
277 			STDIN_FILENO, cap_rights_clear(&rights)))
278 		goto error;
279 
280 	if (cap_rights_limit(STDOUT_FILENO, cap_rights_init(&rights,
281 			CAP_EVENT, CAP_FCNTL, CAP_FSTAT, CAP_LOOKUP,
282 			CAP_WRITE, CAP_SEEK)))
283 		goto error;
284 
285 	if (cap_rights_limit(STDERR_FILENO, cap_rights_init(&rights,
286 			CAP_WRITE)))
287 		goto error;
288 
289 	if (cap_rights_limit(pipe_event_fd, cap_rights_init(&rights,
290 			CAP_EVENT)))
291 		goto error;
292 
293 	if (cap_rights_limit(pipe_write_fd, cap_rights_init(&rights,
294 			CAP_WRITE)))
295 		goto error;
296 
297 	return;
298 
299 error:
300 	// If a kernel is configured without capability mode support or
301 	// used in an emulator that does not implement the capability
302 	// system calls, then the Capsicum system calls will fail and set
303 	// errno to ENOSYS. In that case xz will silently run without
304 	// the sandbox.
305 	if (errno == ENOSYS)
306 		return;
307 
308 	message_fatal(_("Failed to enable the sandbox"));
309 }
310 
311 #endif
312