]> Dogcows Code - chaz/tar/blob - src/compare.c
Carefully crafted invalid headers can cause buffer overrun.
[chaz/tar] / src / compare.c
1 /* Diff files from a tar archive.
2
3 Copyright (C) 1988, 1992, 1993, 1994, 1996, 1997, 1999, 2000, 2001,
4 2003, 2004, 2005 Free Software Foundation, Inc.
5
6 Written by John Gilmore, on 1987-04-30.
7
8 This program is free software; you can redistribute it and/or modify it
9 under the terms of the GNU General Public License as published by the
10 Free Software Foundation; either version 2, or (at your option) any later
11 version.
12
13 This program is distributed in the hope that it will be useful, but
14 WITHOUT ANY WARRANTY; without even the implied warranty of
15 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
16 Public License for more details.
17
18 You should have received a copy of the GNU General Public License along
19 with this program; if not, write to the Free Software Foundation, Inc.,
20 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */
21
22 #include <system.h>
23
24 #if HAVE_LINUX_FD_H
25 # include <linux/fd.h>
26 #endif
27
28 #include <quotearg.h>
29 #include <utimens.h>
30
31 #include "common.h"
32 #include <rmt.h>
33 #include <stdarg.h>
34
35 /* Nonzero if we are verifying at the moment. */
36 bool now_verifying;
37
38 /* File descriptor for the file we are diffing. */
39 static int diff_handle;
40
41 /* Area for reading file contents into. */
42 static char *diff_buffer;
43
44 /* Initialize for a diff operation. */
45 void
46 diff_init (void)
47 {
48 void *ptr;
49 diff_buffer = page_aligned_alloc (&ptr, record_size);
50 if (listed_incremental_option)
51 read_directory_file ();
52 }
53
54 /* Sigh about something that differs by writing a MESSAGE to stdlis,
55 given MESSAGE is nonzero. Also set the exit status if not already. */
56 void
57 report_difference (struct tar_stat_info *st __attribute__ ((unused)),
58 const char *fmt, ...)
59 {
60 if (fmt)
61 {
62 va_list ap;
63
64 fprintf (stdlis, "%s: ", quotearg_colon (current_stat_info.file_name));
65 va_start (ap, fmt);
66 vfprintf (stdlis, fmt, ap);
67 va_end (ap);
68 fprintf (stdlis, "\n");
69 }
70
71 if (exit_status == TAREXIT_SUCCESS)
72 exit_status = TAREXIT_DIFFERS;
73 }
74
75 /* Take a buffer returned by read_and_process and do nothing with it. */
76 static int
77 process_noop (size_t size __attribute__ ((unused)),
78 char *data __attribute__ ((unused)))
79 {
80 return 1;
81 }
82
83 static int
84 process_rawdata (size_t bytes, char *buffer)
85 {
86 size_t status = safe_read (diff_handle, diff_buffer, bytes);
87
88 if (status != bytes)
89 {
90 if (status == SAFE_READ_ERROR)
91 {
92 read_error (current_stat_info.file_name);
93 report_difference (&current_stat_info, NULL);
94 }
95 else
96 {
97 report_difference (&current_stat_info,
98 ngettext ("Could only read %lu of %lu byte",
99 "Could only read %lu of %lu bytes",
100 bytes),
101 (unsigned long) status, (unsigned long) bytes);
102 }
103 return 0;
104 }
105
106 if (memcmp (buffer, diff_buffer, bytes))
107 {
108 report_difference (&current_stat_info, _("Contents differ"));
109 return 0;
110 }
111
112 return 1;
113 }
114
115 /* Directory contents, only for GNUTYPE_DUMPDIR. */
116
117 static char *dumpdir_cursor;
118
119 static int
120 process_dumpdir (size_t bytes, char *buffer)
121 {
122 if (memcmp (buffer, dumpdir_cursor, bytes))
123 {
124 report_difference (&current_stat_info, _("Contents differ"));
125 return 0;
126 }
127
128 dumpdir_cursor += bytes;
129 return 1;
130 }
131
132 /* Some other routine wants SIZE bytes in the archive. For each chunk
133 of the archive, call PROCESSOR with the size of the chunk, and the
134 address of the chunk it can work with. The PROCESSOR should return
135 nonzero for success. It it return error once, continue skipping
136 without calling PROCESSOR anymore. */
137 static void
138 read_and_process (off_t size, int (*processor) (size_t, char *))
139 {
140 union block *data_block;
141 size_t data_size;
142
143 if (multi_volume_option)
144 save_sizeleft = size;
145 while (size)
146 {
147 data_block = find_next_block ();
148 if (! data_block)
149 {
150 ERROR ((0, 0, _("Unexpected EOF in archive")));
151 return;
152 }
153
154 data_size = available_space_after (data_block);
155 if (data_size > size)
156 data_size = size;
157 if (!(*processor) (data_size, data_block->buffer))
158 processor = process_noop;
159 set_next_block_after ((union block *)
160 (data_block->buffer + data_size - 1));
161 size -= data_size;
162 if (multi_volume_option)
163 save_sizeleft -= data_size;
164 }
165 }
166
167 /* Call either stat or lstat over STAT_DATA, depending on
168 --dereference (-h), for a file which should exist. Diagnose any
169 problem. Return nonzero for success, zero otherwise. */
170 static int
171 get_stat_data (char const *file_name, struct stat *stat_data)
172 {
173 int status = deref_stat (dereference_option, file_name, stat_data);
174
175 if (status != 0)
176 {
177 if (errno == ENOENT)
178 stat_warn (file_name);
179 else
180 stat_error (file_name);
181 report_difference (&current_stat_info, NULL);
182 return 0;
183 }
184
185 return 1;
186 }
187
188 \f
189 static void
190 diff_dir (void)
191 {
192 struct stat stat_data;
193
194 if (!get_stat_data (current_stat_info.file_name, &stat_data))
195 return;
196
197 if (!S_ISDIR (stat_data.st_mode))
198 report_difference (&current_stat_info, _("File type differs"));
199 else if ((current_stat_info.stat.st_mode & MODE_ALL) !=
200 (stat_data.st_mode & MODE_ALL))
201 report_difference (&current_stat_info, _("Mode differs"));
202 }
203
204 static void
205 diff_file (void)
206 {
207 struct stat stat_data;
208
209 if (!get_stat_data (current_stat_info.file_name, &stat_data))
210 skip_member ();
211 else if (!S_ISREG (stat_data.st_mode))
212 {
213 report_difference (&current_stat_info, _("File type differs"));
214 skip_member ();
215 }
216 else
217 {
218 if ((current_stat_info.stat.st_mode & MODE_ALL) !=
219 (stat_data.st_mode & MODE_ALL))
220 report_difference (&current_stat_info, _("Mode differs"));
221
222 if (!sys_compare_uid (&stat_data, &current_stat_info.stat))
223 report_difference (&current_stat_info, _("Uid differs"));
224 if (!sys_compare_gid (&stat_data, &current_stat_info.stat))
225 report_difference (&current_stat_info, _("Gid differs"));
226
227 if (stat_data.st_mtime != current_stat_info.stat.st_mtime)
228 report_difference (&current_stat_info, _("Mod time differs"));
229 if (current_header->header.typeflag != GNUTYPE_SPARSE &&
230 stat_data.st_size != current_stat_info.stat.st_size)
231 {
232 report_difference (&current_stat_info, _("Size differs"));
233 skip_member ();
234 }
235 else
236 {
237 diff_handle = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
238
239 if (diff_handle < 0)
240 {
241 open_error (current_stat_info.file_name);
242 skip_member ();
243 report_difference (&current_stat_info, NULL);
244 }
245 else
246 {
247 int status;
248
249 if (current_stat_info.is_sparse)
250 sparse_diff_file (diff_handle, &current_stat_info);
251 else
252 {
253 if (multi_volume_option)
254 {
255 assign_string (&save_name,
256 current_stat_info.orig_file_name);
257 save_totsize = current_stat_info.stat.st_size;
258 /* save_sizeleft is set in read_and_process. */
259 }
260
261 read_and_process (current_stat_info.stat.st_size,
262 process_rawdata);
263
264 if (multi_volume_option)
265 assign_string (&save_name, 0);
266 }
267
268 status = close (diff_handle);
269 if (status != 0)
270 close_error (current_stat_info.file_name);
271
272 if (atime_preserve_option)
273 {
274 struct timespec ts[2];
275 ts[0] = get_stat_atime (&stat_data);
276 ts[1] = get_stat_mtime (&stat_data);
277 if (utimens (current_stat_info.file_name, ts) != 0)
278 utime_error (current_stat_info.file_name);
279 }
280 }
281 }
282 }
283 }
284
285 static void
286 diff_link (void)
287 {
288 struct stat file_data;
289 struct stat link_data;
290
291 if (get_stat_data (current_stat_info.file_name, &file_data)
292 && get_stat_data (current_stat_info.link_name, &link_data)
293 && !sys_compare_links (&file_data, &link_data))
294 report_difference (&current_stat_info,
295 _("Not linked to %s"),
296 quote (current_stat_info.link_name));
297 }
298
299 #ifdef HAVE_READLINK
300 static void
301 diff_symlink (void)
302 {
303 size_t len = strlen (current_stat_info.link_name);
304 char *linkbuf = alloca (len + 1);
305
306 int status = readlink (current_stat_info.file_name, linkbuf, len + 1);
307
308 if (status < 0)
309 {
310 if (errno == ENOENT)
311 readlink_warn (current_stat_info.file_name);
312 else
313 readlink_error (current_stat_info.file_name);
314 report_difference (&current_stat_info, NULL);
315 }
316 else if (status != len
317 || strncmp (current_stat_info.link_name, linkbuf, len) != 0)
318 report_difference (&current_stat_info, _("Symlink differs"));
319 }
320 #endif
321
322 static void
323 diff_special (void)
324 {
325 struct stat stat_data;
326
327 /* FIXME: deal with umask. */
328
329 if (!get_stat_data (current_stat_info.file_name, &stat_data))
330 return;
331
332 if (current_header->header.typeflag == CHRTYPE
333 ? !S_ISCHR (stat_data.st_mode)
334 : current_header->header.typeflag == BLKTYPE
335 ? !S_ISBLK (stat_data.st_mode)
336 : /* current_header->header.typeflag == FIFOTYPE */
337 !S_ISFIFO (stat_data.st_mode))
338 {
339 report_difference (&current_stat_info, _("File type differs"));
340 return;
341 }
342
343 if ((current_header->header.typeflag == CHRTYPE
344 || current_header->header.typeflag == BLKTYPE)
345 && current_stat_info.stat.st_rdev != stat_data.st_rdev)
346 {
347 report_difference (&current_stat_info, _("Device number differs"));
348 return;
349 }
350
351 if ((current_stat_info.stat.st_mode & MODE_ALL) !=
352 (stat_data.st_mode & MODE_ALL))
353 report_difference (&current_stat_info, _("Mode differs"));
354 }
355
356 static void
357 diff_dumpdir (void)
358 {
359 char *dumpdir_buffer = get_directory_contents (current_stat_info.file_name,
360 0);
361
362 if (multi_volume_option)
363 {
364 assign_string (&save_name, current_stat_info.orig_file_name);
365 save_totsize = current_stat_info.stat.st_size;
366 /* save_sizeleft is set in read_and_process. */
367 }
368
369 if (dumpdir_buffer)
370 {
371 dumpdir_cursor = dumpdir_buffer;
372 read_and_process (current_stat_info.stat.st_size, process_dumpdir);
373 free (dumpdir_buffer);
374 }
375 else
376 read_and_process (current_stat_info.stat.st_size, process_noop);
377
378 if (multi_volume_option)
379 assign_string (&save_name, 0);
380 }
381
382 static void
383 diff_multivol (void)
384 {
385 struct stat stat_data;
386 int fd, status;
387 off_t offset;
388
389 if (current_stat_info.had_trailing_slash)
390 {
391 diff_dir ();
392 return;
393 }
394
395 if (!get_stat_data (current_stat_info.file_name, &stat_data))
396 return;
397
398 if (!S_ISREG (stat_data.st_mode))
399 {
400 report_difference (&current_stat_info, _("File type differs"));
401 skip_member ();
402 return;
403 }
404
405 offset = OFF_FROM_HEADER (current_header->oldgnu_header.offset);
406 if (stat_data.st_size != current_stat_info.stat.st_size + offset)
407 {
408 report_difference (&current_stat_info, _("Size differs"));
409 skip_member ();
410 return;
411 }
412
413 fd = open (current_stat_info.file_name, O_RDONLY | O_BINARY);
414
415 if (fd < 0)
416 {
417 open_error (current_stat_info.file_name);
418 report_difference (&current_stat_info, NULL);
419 skip_member ();
420 return;
421 }
422
423 if (lseek (fd, offset, SEEK_SET) < 0)
424 {
425 seek_error_details (current_stat_info.file_name, offset);
426 report_difference (&current_stat_info, NULL);
427 return;
428 }
429
430 if (multi_volume_option)
431 {
432 assign_string (&save_name, current_stat_info.orig_file_name);
433 save_totsize = stat_data.st_size;
434 /* save_sizeleft is set in read_and_process. */
435 }
436
437 read_and_process (current_stat_info.stat.st_size, process_rawdata);
438
439 if (multi_volume_option)
440 assign_string (&save_name, 0);
441
442 status = close (fd);
443 if (status != 0)
444 close_error (current_stat_info.file_name);
445 }
446
447 /* Diff a file against the archive. */
448 void
449 diff_archive (void)
450 {
451
452 set_next_block_after (current_header);
453 decode_header (current_header, &current_stat_info, &current_format, 1);
454
455 /* Print the block from current_header and current_stat_info. */
456
457 if (verbose_option)
458 {
459 if (now_verifying)
460 fprintf (stdlis, _("Verify "));
461 print_header (&current_stat_info, -1);
462 }
463
464 switch (current_header->header.typeflag)
465 {
466 default:
467 ERROR ((0, 0, _("%s: Unknown file type `%c', diffed as normal file"),
468 quotearg_colon (current_stat_info.file_name),
469 current_header->header.typeflag));
470 /* Fall through. */
471
472 case AREGTYPE:
473 case REGTYPE:
474 case GNUTYPE_SPARSE:
475 case CONTTYPE:
476
477 /* Appears to be a file. See if it's really a directory. */
478
479 if (current_stat_info.had_trailing_slash)
480 diff_dir ();
481 else
482 diff_file ();
483 break;
484
485 case LNKTYPE:
486 diff_link ();
487 break;
488
489 #ifdef HAVE_READLINK
490 case SYMTYPE:
491 diff_symlink ();
492 break;
493 #endif
494
495 case CHRTYPE:
496 case BLKTYPE:
497 case FIFOTYPE:
498 diff_special ();
499 break;
500
501 case GNUTYPE_DUMPDIR:
502 diff_dumpdir ();
503 /* Fall through. */
504
505 case DIRTYPE:
506 diff_dir ();
507 break;
508
509 case GNUTYPE_VOLHDR:
510 break;
511
512 case GNUTYPE_MULTIVOL:
513 diff_multivol ();
514 }
515 }
516
517 void
518 verify_volume (void)
519 {
520 if (removed_prefixes_p ())
521 {
522 WARN((0, 0,
523 _("Archive contains file names with leading prefixes removed.")));
524 WARN((0, 0,
525 _("Verification may fail to locate original files.")));
526 }
527
528 if (!diff_buffer)
529 diff_init ();
530
531 /* Verifying an archive is meant to check if the physical media got it
532 correctly, so try to defeat clever in-memory buffering pertaining to
533 this particular media. On Linux, for example, the floppy drive would
534 not even be accessed for the whole verification.
535
536 The code was using fsync only when the ioctl is unavailable, but
537 Marty Leisner says that the ioctl does not work when not preceded by
538 fsync. So, until we know better, or maybe to please Marty, let's do it
539 the unbelievable way :-). */
540
541 #if HAVE_FSYNC
542 fsync (archive);
543 #endif
544 #ifdef FDFLUSH
545 ioctl (archive, FDFLUSH);
546 #endif
547
548 #ifdef MTIOCTOP
549 {
550 struct mtop operation;
551 int status;
552
553 operation.mt_op = MTBSF;
554 operation.mt_count = 1;
555 if (status = rmtioctl (archive, MTIOCTOP, (char *) &operation), status < 0)
556 {
557 if (errno != EIO
558 || (status = rmtioctl (archive, MTIOCTOP, (char *) &operation),
559 status < 0))
560 {
561 #endif
562 if (rmtlseek (archive, (off_t) 0, SEEK_SET) != 0)
563 {
564 /* Lseek failed. Try a different method. */
565 seek_warn (archive_name_array[0]);
566 return;
567 }
568 #ifdef MTIOCTOP
569 }
570 }
571 }
572 #endif
573
574 access_mode = ACCESS_READ;
575 now_verifying = 1;
576
577 flush_read ();
578 while (1)
579 {
580 enum read_header status = read_header (false);
581
582 if (status == HEADER_FAILURE)
583 {
584 int counter = 0;
585
586 do
587 {
588 counter++;
589 set_next_block_after (current_header);
590 status = read_header (false);
591 }
592 while (status == HEADER_FAILURE);
593
594 ERROR ((0, 0,
595 ngettext ("VERIFY FAILURE: %d invalid header detected",
596 "VERIFY FAILURE: %d invalid headers detected",
597 counter), counter));
598 }
599 if (status == HEADER_ZERO_BLOCK || status == HEADER_END_OF_FILE)
600 break;
601
602 diff_archive ();
603 tar_stat_destroy (&current_stat_info);
604 xheader_destroy (&extended_header);
605 }
606
607 access_mode = ACCESS_WRITE;
608 now_verifying = 0;
609 }
This page took 0.057592 seconds and 5 git commands to generate.