1 // SPDX-License-Identifier: 0BSD
3 ///////////////////////////////////////////////////////////////////////////////
6 /// \brief Simple single-threaded tool to uncompress .xz or .lzma files
8 // Author: Lasse Collin
10 ///////////////////////////////////////////////////////////////////////////////
23 #ifdef HAVE_CAP_RIGHTS_LIMIT
24 # include <sys/capsicum.h>
27 #ifdef HAVE_LINUX_LANDLOCK
28 # include <linux/landlock.h>
29 # include <sys/prctl.h>
30 # include <sys/syscall.h>
31 # ifdef LANDLOCK_ACCESS_NET_BIND_TCP
32 # define LANDLOCK_ABI_MAX 4
34 # define LANDLOCK_ABI_MAX 3
38 #if defined(HAVE_CAP_RIGHTS_LIMIT) || defined(HAVE_PLEDGE) \
39 || defined(HAVE_LINUX_LANDLOCK)
40 # define ENABLE_SANDBOX 1
44 #include "tuklib_progname.h"
45 #include "tuklib_exit.h"
51 # define fileno _fileno
52 # define setmode _setmode
58 # define TOOL_FORMAT "lzma"
60 # define TOOL_FORMAT "xz"
64 /// Error messages are suppressed if this is zero, which is the case when
65 /// --quiet has been given at least twice.
66 static int display_errors
= 2;
69 lzma_attribute((__format__(__printf__
, 1, 2)))
71 my_errorf(const char *fmt
, ...)
77 fprintf(stderr
, "%s: ", progname
);
78 vfprintf(stderr
, fmt
, ap
);
79 fprintf(stderr
, "\n");
92 "Usage: %s [OPTION]... [FILE]...\n"
93 "Decompress files in the ." TOOL_FORMAT
" format to standard output.\n"
95 " -d, --decompress (ignored, only decompression is supported)\n"
96 " -k, --keep (ignored, files are never deleted)\n"
97 " -c, --stdout (ignored, output is always written to standard output)\n"
98 " -q, --quiet specify *twice* to suppress errors\n"
99 " -Q, --no-warn (ignored, the exit status 2 is never used)\n"
100 " -h, --help display this help and exit\n"
101 " -V, --version display the version number and exit\n"
103 "With no FILE, or when FILE is -, read standard input.\n"
105 "Report bugs to <" PACKAGE_BUGREPORT
"> (in English or Finnish).\n"
106 PACKAGE_NAME
" home page: <" PACKAGE_URL
">\n", progname
);
108 tuklib_exit(EXIT_SUCCESS
, EXIT_FAILURE
, display_errors
);
116 printf(TOOL_FORMAT
"dec (" PACKAGE_NAME
") " LZMA_VERSION_STRING
"\n"
117 "liblzma %s\n", lzma_version_string());
119 tuklib_exit(EXIT_SUCCESS
, EXIT_FAILURE
, display_errors
);
123 /// Parses command line options.
125 parse_options(int argc
, char **argv
)
127 static const char short_opts
[] = "cdkM:hqQV";
128 static const struct option long_opts
[] = {
129 { "stdout", no_argument
, NULL
, 'c' },
130 { "to-stdout", no_argument
, NULL
, 'c' },
131 { "decompress", no_argument
, NULL
, 'd' },
132 { "uncompress", no_argument
, NULL
, 'd' },
133 { "keep", no_argument
, NULL
, 'k' },
134 { "quiet", no_argument
, NULL
, 'q' },
135 { "no-warn", no_argument
, NULL
, 'Q' },
136 { "help", no_argument
, NULL
, 'h' },
137 { "version", no_argument
, NULL
, 'V' },
143 while ((c
= getopt_long(argc
, argv
, short_opts
, long_opts
, NULL
))
153 if (display_errors
> 0)
174 uncompress(lzma_stream
*strm
, FILE *file
, const char *filename
)
178 // Initialize the decoder
180 ret
= lzma_alone_decoder(strm
, UINT64_MAX
);
182 ret
= lzma_stream_decoder(strm
, UINT64_MAX
, LZMA_CONCATENATED
);
185 // The only reasonable error here is LZMA_MEM_ERROR.
186 if (ret
!= LZMA_OK
) {
187 my_errorf("%s", ret
== LZMA_MEM_ERROR
? strerror(ENOMEM
)
188 : "Internal error (bug)");
192 // Input and output buffers
193 uint8_t in_buf
[BUFSIZ
];
194 uint8_t out_buf
[BUFSIZ
];
197 strm
->next_out
= out_buf
;
198 strm
->avail_out
= BUFSIZ
;
200 lzma_action action
= LZMA_RUN
;
203 if (strm
->avail_in
== 0) {
204 strm
->next_in
= in_buf
;
205 strm
->avail_in
= fread(in_buf
, 1, BUFSIZ
, file
);
208 // POSIX says that fread() sets errno if
209 // an error occurred. ferror() doesn't
211 my_errorf("%s: Error reading input file: %s",
212 filename
, strerror(errno
));
217 // When using LZMA_CONCATENATED, we need to tell
218 // liblzma when it has got all the input.
220 action
= LZMA_FINISH
;
224 ret
= lzma_code(strm
, action
);
226 // Write and check write error before checking decoder error.
227 // This way as much data as possible gets written to output
228 // even if decoder detected an error.
229 if (strm
->avail_out
== 0 || ret
!= LZMA_OK
) {
230 const size_t write_size
= BUFSIZ
- strm
->avail_out
;
232 if (fwrite(out_buf
, 1, write_size
, stdout
)
234 // Wouldn't be a surprise if writing to stderr
235 // would fail too but at least try to show an
237 my_errorf("Cannot write to standard output: "
238 "%s", strerror(errno
));
242 strm
->next_out
= out_buf
;
243 strm
->avail_out
= BUFSIZ
;
246 if (ret
!= LZMA_OK
) {
247 if (ret
== LZMA_STREAM_END
) {
249 // Check that there's no trailing garbage.
250 if (strm
->avail_in
!= 0
251 || fread(in_buf
, 1, 1, file
)
254 ret
= LZMA_DATA_ERROR
;
258 // lzma_stream_decoder() already guarantees
259 // that there's no trailing garbage.
260 assert(strm
->avail_in
== 0);
261 assert(action
== LZMA_FINISH
);
270 msg
= strerror(ENOMEM
);
273 case LZMA_FORMAT_ERROR
:
274 msg
= "File format not recognized";
277 case LZMA_OPTIONS_ERROR
:
278 // FIXME: Better message?
279 msg
= "Unsupported compression options";
282 case LZMA_DATA_ERROR
:
283 msg
= "File is corrupt";
287 msg
= "Unexpected end of input";
291 msg
= "Internal error (bug)";
295 my_errorf("%s: %s", filename
, msg
);
302 #ifdef ENABLE_SANDBOX
304 sandbox_enter(int src_fd
)
306 #if defined(HAVE_CAP_RIGHTS_LIMIT)
307 // Capsicum needs FreeBSD 10.2 or later.
313 if (cap_rights_limit(src_fd
, cap_rights_init(&rights
, CAP_READ
)))
316 // If not reading from stdin, remove all capabilities from it.
317 if (src_fd
!= STDIN_FILENO
&& cap_rights_limit(
318 STDIN_FILENO
, cap_rights_clear(&rights
)))
321 if (cap_rights_limit(STDOUT_FILENO
, cap_rights_init(&rights
,
325 if (cap_rights_limit(STDERR_FILENO
, cap_rights_init(&rights
,
329 #elif defined(HAVE_PLEDGE)
330 // pledge() was introduced in OpenBSD 5.9.
331 if (pledge("stdio", ""))
336 #elif defined(HAVE_LINUX_LANDLOCK)
337 int landlock_abi
= syscall(SYS_landlock_create_ruleset
,
338 (void *)NULL
, 0, LANDLOCK_CREATE_RULESET_VERSION
);
340 if (landlock_abi
> 0) {
341 if (landlock_abi
> LANDLOCK_ABI_MAX
)
342 landlock_abi
= LANDLOCK_ABI_MAX
;
344 const struct landlock_ruleset_attr attr
= {
345 .handled_access_fs
= (1ULL
346 << (12 + my_min(3, landlock_abi
))) - 1,
347 # if LANDLOCK_ABI_MAX >= 4
348 .handled_access_net
= landlock_abi
< 4 ? 0 :
349 (LANDLOCK_ACCESS_NET_BIND_TCP
350 | LANDLOCK_ACCESS_NET_CONNECT_TCP
),
354 const int ruleset_fd
= syscall(SYS_landlock_create_ruleset
,
355 &attr
, sizeof(attr
), 0U);
359 // All files we need should have already been opened. Thus,
360 // we don't need to add any rules using landlock_add_rule(2)
361 // before activating the sandbox.
362 if (syscall(SYS_landlock_restrict_self
, ruleset_fd
, 0U) != 0)
369 # error ENABLE_SANDBOX is defined but no sandboxing method was found.
375 #ifdef HAVE_CAP_RIGHTS_LIMIT
376 // If a kernel is configured without capability mode support or
377 // used in an emulator that does not implement the capability
378 // system calls, then the Capsicum system calls will fail and set
379 // errno to ENOSYS. In that case xzdec will silently run without
385 my_errorf("Failed to enable the sandbox");
392 main(int argc
, char **argv
)
395 // OpenBSD's pledge(2) sandbox.
396 // Initially enable the sandbox slightly more relaxed so that
397 // the process can still open files. This allows the sandbox to
398 // be enabled when parsing command line arguments and decompressing
399 // all files (the more strict sandbox only restricts the last file
400 // that is decompressed).
401 if (pledge("stdio rpath", "")) {
402 my_errorf("Failed to enable the sandbox");
407 #ifdef HAVE_LINUX_LANDLOCK
408 // Prevent the process from gaining new privileges. This must be done
409 // before landlock_restrict_self(2) but since we will never need new
410 // privileges, this call can be done here already.
412 // This is supported since Linux 3.5. Ignore the return value to
413 // keep compatibility with old kernels. landlock_restrict_self(2)
414 // will fail if the no_new_privs attribute isn't set, thus if prctl()
415 // fails here the error will still be detected when it matters.
416 (void)prctl(PR_SET_NO_NEW_PRIVS
, 1, 0, 0, 0);
419 // Initialize progname which we will be used in error messages.
420 tuklib_progname_init(argv
);
422 // Parse the command line options.
423 parse_options(argc
, argv
);
425 // The same lzma_stream is used for all files that we decode. This way
426 // we don't need to reallocate memory for every file if they use same
427 // compression settings.
428 lzma_stream strm
= LZMA_STREAM_INIT
;
430 // Some systems require setting stdin and stdout to binary mode.
431 #ifdef TUKLIB_DOSLIKE
432 setmode(fileno(stdin
), O_BINARY
);
433 setmode(fileno(stdout
), O_BINARY
);
436 if (optind
== argc
) {
437 // No filenames given, decode from stdin.
438 #ifdef ENABLE_SANDBOX
439 sandbox_enter(STDIN_FILENO
);
441 uncompress(&strm
, stdin
, "(stdin)");
443 // Loop through the filenames given on the command line.
446 const char *src_name
;
448 // "-" indicates stdin.
449 if (strcmp(argv
[optind
], "-") == 0) {
451 src_name
= "(stdin)";
453 src_name
= argv
[optind
];
454 src_file
= fopen(src_name
, "rb");
455 if (src_file
== NULL
) {
456 my_errorf("%s: %s", src_name
,
461 #ifdef ENABLE_SANDBOX
462 // Enable the strict sandbox for the last file.
463 // Then the process can no longer open additional
464 // files. The typical xzdec use case is to decompress
465 // a single file so this way the strictest sandboxing
466 // is used in most cases.
467 if (optind
== argc
- 1)
468 sandbox_enter(fileno(src_file
));
470 uncompress(&strm
, src_file
, src_name
);
472 if (src_file
!= stdin
)
473 (void)fclose(src_file
);
474 } while (++optind
< argc
);
478 // Free the memory only when debugging. Freeing wastes some time,
479 // but allows detecting possible memory leaks with Valgrind.
483 tuklib_exit(EXIT_SUCCESS
, EXIT_FAILURE
, display_errors
);