]> git.ipfire.org Git - thirdparty/libarchive.git/commitdiff
Do not skip past EOF while reading (#2584)
authorTobias Stoeckmann <stoeckmann@users.noreply.github.com>
Tue, 15 Apr 2025 04:02:17 +0000 (06:02 +0200)
committerGitHub <noreply@github.com>
Tue, 15 Apr 2025 04:02:17 +0000 (21:02 -0700)
Make sure to not skip past end of file for better error messages. One
such example is now visible with rar testsuite. You can see the
difference already by an actually not useless use of cat:

```
$ cat .../test_read_format_rar_ppmd_use_after_free.rar | bsdtar -t
bsdtar: Archive entry has empty or unreadable filename ... skipping.
bsdtar: Archive entry has empty or unreadable filename ... skipping.
bsdtar: Truncated input file (needed 119 bytes, only 0 available)
bsdtar: Error exit delayed from previous errors.
```

compared to

```
$ bsdtar -tf .../test_read_format_rar_ppmd_use_after_free.rar
bsdtar: Archive entry has empty or unreadable filename ... skipping.
bsdtar: Archive entry has empty or unreadable filename ... skipping.
bsdtar: Error exit delayed from previous errors.
```

Since the former cannot lseek, the error is a different one
(ARCHIVE_FATAL vs ARCHIVE_EOF). The piped version states explicitly that
truncation occurred, while the latter states EOF because the skip past
the end of file was successful.

Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
libarchive/archive_read_open_fd.c
libarchive/archive_read_open_file.c
libarchive/archive_read_open_filename.c
libarchive/test/test_read_format_rar.c

index 3fd536d57b1f11271aab5c5d2791a0c3cd345950..dc7c9e52c6f63a07841b788cd130d23d39cbd863 100644 (file)
@@ -52,6 +52,7 @@
 struct read_fd_data {
        int      fd;
        size_t   block_size;
+       int64_t  size;
        char     use_lseek;
        void    *buffer;
 };
@@ -95,6 +96,7 @@ archive_read_open_fd(struct archive *a, int fd, size_t block_size)
        if (S_ISREG(st.st_mode)) {
                archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino);
                mine->use_lseek = 1;
+               mine->size = st.st_size;
        }
 #if defined(__CYGWIN__) || defined(_WIN32)
        setmode(mine->fd, O_BINARY);
@@ -151,9 +153,14 @@ file_skip(struct archive *a, void *client_data, int64_t request)
        if (skip == 0)
                return (0);
 
-       if (((old_offset = lseek(mine->fd, 0, SEEK_CUR)) >= 0) &&
-           ((new_offset = lseek(mine->fd, skip, SEEK_CUR)) >= 0))
-               return (new_offset - old_offset);
+       if ((old_offset = lseek(mine->fd, 0, SEEK_CUR)) >= 0) {
+               if (old_offset >= mine->size ||
+                   skip > mine->size - old_offset) {
+                       /* Do not seek past end of file. */
+                       errno = ESPIPE;
+               } else if ((new_offset = lseek(mine->fd, skip, SEEK_CUR)) >= 0)
+                       return (new_offset - old_offset);
+       }
 
        /* If seek failed once, it will probably fail again. */
        mine->use_lseek = 0;
index 2829b9a5c744fbf2f681e03c93585c61d4a861fa..6ed18a0c08eb45b109ad25facebaa8ebfb240e74 100644 (file)
@@ -52,6 +52,7 @@
 struct read_FILE_data {
        FILE    *f;
        size_t   block_size;
+       int64_t  size;
        void    *buffer;
        char     can_skip;
 };
@@ -91,6 +92,7 @@ archive_read_open_FILE(struct archive *a, FILE *f)
                archive_read_extract_set_skip_file(a, st.st_dev, st.st_ino);
                /* Enable the seek optimization only for regular files. */
                mine->can_skip = 1;
+               mine->size = st.st_size;
        }
 
 #if defined(__CYGWIN__) || defined(_WIN32)
@@ -130,6 +132,7 @@ FILE_skip(struct archive *a, void *client_data, int64_t request)
 #else
        long skip = (long)request;
 #endif
+       int64_t old_offset, new_offset;
        int skip_bits = sizeof(skip) * 8 - 1;
 
        (void)a; /* UNUSED */
@@ -153,19 +156,33 @@ FILE_skip(struct archive *a, void *client_data, int64_t request)
 
 #ifdef __ANDROID__
         /* fileno() isn't safe on all platforms ... see above. */
-       if (lseek(fileno(mine->f), skip, SEEK_CUR) < 0)
+       old_offset = lseek(fileno(mine->f), 0, SEEK_CUR);
 #elif HAVE__FSEEKI64
-       if (_fseeki64(mine->f, skip, SEEK_CUR) != 0)
+       old_offset = _ftelli64(mine->f);
 #elif HAVE_FSEEKO
-       if (fseeko(mine->f, skip, SEEK_CUR) != 0)
+       old_offset = ftello(mine->f);
 #else
-       if (fseek(mine->f, skip, SEEK_CUR) != 0)
+       old_offset = ftell(mine->f);
 #endif
-       {
-               mine->can_skip = 0;
-               return (0);
+       if (old_offset >= 0) {
+               if (old_offset < mine->size &&
+                   skip <= mine->size - old_offset) {
+#ifdef __ANDROID__
+                       new_offset = lseek(fileno(mine->f), skip, SEEK_CUR);
+#elif HAVE__FSEEKI64
+                       new_offset = _fseeki64(mine->f, skip, SEEK_CUR);
+#elif HAVE_FSEEKO
+                       new_offset = fseeko(mine->f, skip, SEEK_CUR);
+#else
+                       new_offset = fseek(mine->f, skip, SEEK_CUR);
+#endif
+                       if (new_offset >= 0)
+                               return (new_offset - old_offset);
+               }
        }
-       return (request);
+
+       mine->can_skip = 0;
+       return (0);
 }
 
 /*
index 3894b15c86df2a887befdbb5c5547e8e7d9a410b..5f5b3f1f7259f940ff2eee66e1a949c450fa7448 100644 (file)
@@ -74,6 +74,7 @@ struct read_file_data {
        size_t   block_size;
        void    *buffer;
        mode_t   st_mode;  /* Mode bits for opened file. */
+       int64_t  size;
        char     use_lseek;
        enum fnt_e { FNT_STDIN, FNT_MBS, FNT_WCS } filename_type;
        union {
@@ -400,8 +401,10 @@ file_open(struct archive *a, void *client_data)
        mine->st_mode = st.st_mode;
 
        /* Disk-like inputs can use lseek(). */
-       if (is_disk_like)
+       if (is_disk_like) {
                mine->use_lseek = 1;
+               mine->size = st.st_size;
+       }
 
        return (ARCHIVE_OK);
 fail:
@@ -495,9 +498,14 @@ file_skip_lseek(struct archive *a, void *client_data, int64_t request)
                        skip = max_skip;
        }
 
-       if ((old_offset = lseek(mine->fd, 0, SEEK_CUR)) >= 0 &&
-           (new_offset = lseek(mine->fd, skip, SEEK_CUR)) >= 0)
-               return (new_offset - old_offset);
+       if ((old_offset = lseek(mine->fd, 0, SEEK_CUR)) >= 0) {
+               if (old_offset >= mine->size ||
+                   skip > mine->size - old_offset) {
+                       /* Do not seek past end of file. */
+                       errno = ESPIPE;
+               } else if ((new_offset = lseek(mine->fd, skip, SEEK_CUR)) >= 0)
+                       return (new_offset - old_offset);
+       }
 
        /* If lseek() fails, don't bother trying again. */
        mine->use_lseek = 0;
index dce567af48a907401a4cd6eed4261c9ef1bcb04a..fce44a9d8e04b245c2090d823bf062763bc256a6 100644 (file)
@@ -3829,8 +3829,8 @@ DEFINE_TEST(test_read_format_rar_ppmd_use_after_free)
   assertA(ARCHIVE_OK == archive_read_next_header(a, &ae));
   assertA(archive_read_data(a, buf, sizeof(buf)) <= 0);
 
-  /* Test EOF */
-  assertA(1 == archive_read_next_header(a, &ae));
+  /* Test for truncation */
+  assertA(ARCHIVE_FATAL == archive_read_next_header(a, &ae));
 
   assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
   assertEqualInt(ARCHIVE_OK, archive_read_free(a));
@@ -3856,7 +3856,7 @@ DEFINE_TEST(test_read_format_rar_ppmd_use_after_free2)
   assertA(archive_read_data(a, buf, sizeof(buf)) <= 0);
 
   /* Test EOF */
-  assertA(1 == archive_read_next_header(a, &ae));
+  assertA(ARCHIVE_FATAL == archive_read_next_header(a, &ae));
 
   assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
   assertEqualInt(ARCHIVE_OK, archive_read_free(a));