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