]> icculus.org git repositories - icculus/xz.git/blob - src/lzmadec/lzmadec.c
Fix a buffer overflow in the LZMA encoder. It was due to my
[icculus/xz.git] / src / lzmadec / lzmadec.c
1 ///////////////////////////////////////////////////////////////////////////////
2 //
3 /// \file       lzmadec.c
4 /// \brief      Simple single-threaded tool to uncompress .lzma files
5 //
6 //  Copyright (C) 2007 Lasse Collin
7 //
8 //  This program is free software; you can redistribute it and/or
9 //  modify it under the terms of the GNU Lesser General Public
10 //  License as published by the Free Software Foundation; either
11 //  version 2.1 of the License, or (at your option) any later version.
12 //
13 //  This program is distributed in the hope that it will be useful,
14 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
15 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
16 //  Lesser General Public License for more details.
17 //
18 ///////////////////////////////////////////////////////////////////////////////
19
20 #include "sysdefs.h"
21
22 #ifdef HAVE_ERRNO_H
23 #       include <errno.h>
24 #else
25 extern int errno;
26 #endif
27
28 #include <stdio.h>
29 #include <unistd.h>
30
31 #ifdef WIN32
32 #       include <fcntl.h>
33 #endif
34
35 #include "getopt.h"
36 #include "physmem.h"
37
38
39 enum return_code {
40         SUCCESS,
41         ERROR,
42         WARNING,
43 };
44
45
46 enum format_type {
47         FORMAT_AUTO,
48         FORMAT_NATIVE,
49         FORMAT_ALONE,
50 };
51
52
53 enum {
54         OPTION_FORMAT = INT_MIN,
55 };
56
57
58 /// Input buffer
59 static uint8_t in_buf[BUFSIZ];
60
61 /// Output buffer
62 static uint8_t out_buf[BUFSIZ];
63
64 /// Decoder
65 static lzma_stream strm = LZMA_STREAM_INIT;
66
67 /// Number of bytes to use memory at maximum
68 static size_t mem_limit;
69
70 /// Memory allocation hooks
71 static lzma_allocator allocator = {
72         .alloc = (void *(*)(void *, size_t, size_t))(&lzma_memlimit_alloc),
73         .free = (void (*)(void *, void *))(&lzma_memlimit_free),
74         .opaque = NULL,
75 };
76
77 /// Program name to be shown in error messages
78 static const char *argv0;
79
80 /// File currently being processed
81 static FILE *file;
82
83 /// Name of the file currently being processed
84 static const char *filename;
85
86 static enum return_code exit_status = SUCCESS;
87
88 static enum format_type format_type = FORMAT_AUTO;
89
90 static bool force = false;
91
92
93 static void lzma_attribute((noreturn))
94 help(void)
95 {
96         printf(
97 "Usage: %s [OPTION]... [FILE]...\n"
98 "Uncompress files in the .lzma format to the standard output.\n"
99 "\n"
100 "  -c, --stdout       (ignored)\n"
101 "  -d, --decompress   (ignored)\n"
102 "  -k, --keep         (ignored)\n"
103 "  -f, --force        allow reading compressed data from a terminal\n"
104 "  -M, --memory=NUM   use NUM bytes of memory at maximum; the suffixes\n"
105 "                     k, M, G, Ki, Mi, and Gi are supported.\n"
106 "      --format=FMT   accept only files in the given file format;\n"
107 "                     possible FMTs are `auto', `native', `single',\n"
108 "                     `multi', and `alone', of which `single' and `multi'\n"
109 "                     are aliases for `native'\n"
110 "  -h, --help         display this help and exit\n"
111 "  -V, --version      display version and license information and exit\n"
112 "\n"
113 "With no FILE, or when FILE is -, read standard input.\n"
114 "\n"
115 "On this configuration, the tool will use about %" PRIu64
116                 " MiB of memory at maximum.\n"
117 "\n"
118 "Report bugs to <" PACKAGE_BUGREPORT "> (in English or Finnish).\n",
119                 argv0, (uint64_t)((mem_limit + 512 * 1024) / (1024 * 1024)));
120                 // Using PRIu64 above instead of %zu to support pre-C99 libc.
121         exit(0);
122 }
123
124
125 static void lzma_attribute((noreturn))
126 version(void)
127 {
128         printf(
129 "lzmadec (LZMA Utils) " PACKAGE_VERSION "\n"
130 "\n"
131 "Copyright (C) 1999-2006 Igor Pavlov\n"
132 "Copyright (C) 2007 Lasse Collin\n"
133 "\n"
134 "This program is free software; you can redistribute it and/or\n"
135 "modify it under the terms of the GNU Lesser General Public\n"
136 "License as published by the Free Software Foundation; either\n"
137 "version 2.1 of the License, or (at your option) any later version.\n"
138 "\n"
139 "This program is distributed in the hope that it will be useful,\n"
140 "but WITHOUT ANY WARRANTY; without even the implied warranty of\n"
141 "MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU\n"
142 "Lesser General Public License for more details.\n"
143 "\n");
144         exit(0);
145 }
146
147
148 /// Finds out the amount of physical memory in the system, and sets
149 /// a default memory usage limit.
150 static void
151 set_default_mem_limit(void)
152 {
153         uint64_t mem = physmem();
154         if (mem != 0) {
155                 mem /= 3;
156
157 #if UINT64_MAX > SIZE_MAX
158                 if (mem > SIZE_MAX)
159                         mem = SIZE_MAX;
160 #endif
161
162                 mem_limit = mem / 3;
163         } else {
164                 // Cannot autodetect, use 10 MiB as the default limit.
165                 mem_limit = (1U << 23) + (1U << 21);
166         }
167
168         return;
169 }
170
171
172 /// \brief      Converts a string to size_t
173 ///
174 /// This is rudely copied from src/lzma/util.c and modified a little. :-(
175 ///
176 static size_t
177 str_to_size(const char *value)
178 {
179         size_t result = 0;
180
181         if (*value < '0' || *value > '9') {
182                 fprintf(stderr, "%s: %s: Not a number", argv0, value);
183                 exit(ERROR);
184         }
185
186         do {
187                 // Don't overflow.
188                 if (result > (SIZE_MAX - 9) / 10)
189                         return SIZE_MAX;
190
191                 result *= 10;
192                 result += *value - '0';
193                 ++value;
194         } while (*value >= '0' && *value <= '9');
195
196         if (*value != '\0') {
197                 // Look for suffix.
198                 static const struct {
199                         const char *name;
200                         size_t multiplier;
201                 } suffixes[] = {
202                         { "k",  1000 },
203                         { "M",  1000000 },
204                         { "G",  1000000000 },
205                         { "Ki", 1024 },
206                         { "Mi", 1048576 },
207                         { "Gi", 1073741824 },
208                         { NULL, 0 }
209                 };
210
211                 size_t multiplier = 0;
212                 for (size_t i = 0; suffixes[i].name != NULL; ++i) {
213                         if (strcmp(value, suffixes[i].name) == 0) {
214                                 multiplier = suffixes[i].multiplier;
215                                 break;
216                         }
217                 }
218
219                 if (multiplier == 0) {
220                         fprintf(stderr, "%s: %s: Invalid suffix",
221                                         argv0, value);
222                         exit(ERROR);
223                 }
224
225                 // Don't overflow here either.
226                 if (result > SIZE_MAX / multiplier)
227                         return SIZE_MAX;
228
229                 result *= multiplier;
230         }
231
232         return result;
233 }
234
235
236 /// Parses command line options.
237 static void
238 parse_options(int argc, char **argv)
239 {
240         static const char short_opts[] = "cdkfM:hV";
241         static const struct option long_opts[] = {
242                 { "stdout",       no_argument,         NULL, 'c' },
243                 { "to-stdout",    no_argument,         NULL, 'c' },
244                 { "decompress",   no_argument,         NULL, 'd' },
245                 { "uncompress",   no_argument,         NULL, 'd' },
246                 { "force",        no_argument,         NULL, 'f' },
247                 { "keep",         no_argument,         NULL, 'k' },
248                 { "memory",       required_argument,   NULL, 'M' },
249                 { "format",       required_argument,   NULL, OPTION_FORMAT },
250                 { "help",         no_argument,         NULL, 'h' },
251                 { "version",      no_argument,         NULL, 'V' },
252                 { NULL,           0,                   NULL, 0   }
253         };
254
255         int c;
256
257         while ((c = getopt_long(argc, argv, short_opts, long_opts, NULL))
258                         != -1) {
259                 switch (c) {
260                 case 'c':
261                 case 'd':
262                 case 'k':
263                         break;
264
265                 case 'f':
266                         force = true;
267                         break;
268
269                 case 'M':
270                         mem_limit = str_to_size(optarg);
271                         break;
272
273                 case 'h':
274                         help();
275
276                 case 'V':
277                         version();
278
279                 case OPTION_FORMAT: {
280                         if (strcmp("auto", optarg) == 0) {
281                                 format_type = FORMAT_AUTO;
282                         } else if (strcmp("native", optarg) == 0
283                                         || strcmp("single", optarg) == 0
284                                         || strcmp("multi", optarg) == 0) {
285                                 format_type = FORMAT_NATIVE;
286                         } else if (strcmp("alone", optarg) == 0) {
287                                 format_type = FORMAT_ALONE;
288                         } else {
289                                 fprintf(stderr, "%s: %s: Unknown file format "
290                                                 "name\n", argv0, optarg);
291                                 exit(ERROR);
292                         }
293                         break;
294                 }
295
296                 default:
297                         exit(ERROR);
298                 }
299         }
300
301         return;
302 }
303
304
305 /// Initializes lzma_stream structure for decoding of a new Stream.
306 static void
307 init(void)
308 {
309         lzma_ret ret;
310
311         switch (format_type) {
312         case FORMAT_AUTO:
313                 ret = lzma_auto_decoder(&strm, NULL, NULL);
314                 break;
315
316         case FORMAT_NATIVE:
317                 ret = lzma_stream_decoder(&strm, NULL, NULL);
318                 break;
319
320         case FORMAT_ALONE:
321                 ret = lzma_alone_decoder(&strm);
322                 break;
323
324         default:
325                 assert(0);
326                 ret = LZMA_PROG_ERROR;
327         }
328
329         if (ret != LZMA_OK) {
330                 fprintf(stderr, "%s: ", argv0);
331
332                 if (ret == LZMA_MEM_ERROR)
333                         fprintf(stderr, "%s\n", strerror(ENOMEM));
334                 else
335                         fprintf(stderr, "Internal program error (bug)\n");
336
337                 exit(ERROR);
338         }
339
340         return;
341 }
342
343
344 static void
345 read_input(void)
346 {
347         strm.next_in = in_buf;
348         strm.avail_in = fread(in_buf, 1, BUFSIZ, file);
349
350         if (ferror(file)) {
351                 // POSIX says that fread() sets errno if an error occurred.
352                 // ferror() doesn't touch errno.
353                 fprintf(stderr, "%s: %s: Error reading input file: %s\n",
354                                 argv0, filename, strerror(errno));
355                 exit(ERROR);
356         }
357
358         return;
359 }
360
361
362 static bool
363 skip_padding(void)
364 {
365         // Handle concatenated Streams. There can be arbitrary number of
366         // nul-byte padding between the Streams, which must be ignored.
367         //
368         // NOTE: Concatenating LZMA_Alone files works only if at least
369         // one of lc, lp, and pb is non-zero. Using the concatenation
370         // on LZMA_Alone files is strongly discouraged.
371         while (true) {
372                 while (strm.avail_in > 0) {
373                         if (*strm.next_in != '\0')
374                                 return true;
375
376                         ++strm.next_in;
377                         --strm.avail_in;
378                 }
379
380                 if (feof(file))
381                         return false;
382
383                 read_input();
384         }
385 }
386
387
388 static void
389 uncompress(void)
390 {
391         if (file == stdin && !force && isatty(STDIN_FILENO)) {
392                 fprintf(stderr, "%s: Compressed data not read from "
393                                 "a terminal.\n%s: Use `-f' to force reading "
394                                 "from a terminal, or `-h' for help.\n",
395                                 argv0, argv0);
396                 exit(ERROR);
397         }
398
399         init();
400         strm.avail_in = 0;
401
402         while (true) {
403                 if (strm.avail_in == 0)
404                         read_input();
405
406                 strm.next_out = out_buf;
407                 strm.avail_out = BUFSIZ;
408
409                 const lzma_ret ret = lzma_code(&strm, LZMA_RUN);
410
411                 // Write and check write error before checking decoder error.
412                 // This way as much data as possible gets written to output
413                 // even if decoder detected an error. Checking write error
414                 // needs to be done before checking decoder error due to
415                 // how concatenated Streams are handled a few lines later.
416                 const size_t write_size = BUFSIZ - strm.avail_out;
417                 if (fwrite(out_buf, 1, write_size, stdout) != write_size) {
418                         // Wouldn't be a surprise if writing to stderr would
419                         // fail too but at least try to show an error message.
420                         fprintf(stderr, "%s: Cannot write to "
421                                         "standard output: %s\n", argv0,
422                                         strerror(errno));
423                         exit(ERROR);
424                 }
425
426                 if (ret != LZMA_OK) {
427                         if (ret == LZMA_STREAM_END) {
428                                 if (skip_padding()) {
429                                         init();
430                                         continue;
431                                 }
432
433                                 return;
434                         }
435
436                         fprintf(stderr, "%s: %s: ", argv0, filename);
437
438                         switch (ret) {
439                         case LZMA_DATA_ERROR:
440                                 fprintf(stderr, "File is corrupt\n");
441                                 exit(ERROR);
442
443                         case LZMA_HEADER_ERROR:
444                                 fprintf(stderr, "Unsupported file "
445                                                 "format or filters\n");
446                                 exit(ERROR);
447
448                         case LZMA_MEM_ERROR:
449                                 fprintf(stderr, "%s\n", strerror(ENOMEM));
450                                 exit(ERROR);
451
452                         case LZMA_BUF_ERROR:
453                                 fprintf(stderr, "Unexpected end of input\n");
454                                 exit(ERROR);
455
456                         case LZMA_UNSUPPORTED_CHECK:
457                                 fprintf(stderr, "Unsupported type of "
458                                                 "integrity check; not "
459                                                 "verifying file integrity\n");
460                                 exit_status = WARNING;
461                                 break;
462
463                         case LZMA_PROG_ERROR:
464                         default:
465                                 fprintf(stderr, "Internal program "
466                                                 "error (bug)\n");
467                                 exit(ERROR);
468                         }
469                 }
470         }
471 }
472
473
474 int
475 main(int argc, char **argv)
476 {
477         argv0 = argv[0];
478
479         set_default_mem_limit();
480
481         parse_options(argc, argv);
482
483         lzma_init_decoder();
484
485         lzma_memlimit *mem_limiter = lzma_memlimit_create(mem_limit);
486         if (mem_limiter == NULL) {
487                 fprintf(stderr, "%s: %s\n", argv0, strerror(ENOMEM));
488                 exit(ERROR);
489         }
490
491         assert(lzma_memlimit_count(mem_limiter) == 0);
492
493         allocator.opaque = mem_limiter;
494         strm.allocator = &allocator;
495
496 #ifdef WIN32
497         setmode(fileno(stdin), O_BINARY);
498         setmode(fileno(stdout), O_BINARY);
499 #endif
500
501         if (optind == argc) {
502                 file = stdin;
503                 filename = "(stdin)";
504                 uncompress();
505         } else {
506                 do {
507                         if (strcmp(argv[optind], "-") == 0) {
508                                 file = stdin;
509                                 filename = "(stdin)";
510                                 uncompress();
511                         } else {
512                                 filename = argv[optind];
513                                 file = fopen(filename, "rb");
514                                 if (file == NULL) {
515                                         fprintf(stderr, "%s: %s: %s\n",
516                                                         argv0, filename,
517                                                         strerror(errno));
518                                         exit(ERROR);
519                                 }
520
521                                 uncompress();
522                                 fclose(file);
523                         }
524                 } while (++optind < argc);
525         }
526
527 #ifndef NDEBUG
528         // Free the memory only when debugging. Freeing wastes some time,
529         // but allows detecting possible memory leaks with Valgrind.
530         lzma_end(&strm);
531         assert(lzma_memlimit_count(mem_limiter) == 0);
532         lzma_memlimit_end(mem_limiter, false);
533 #endif
534
535         return exit_status;
536 }