From: Tobias Stoeckmann Date: Tue, 15 Apr 2025 04:02:17 +0000 (+0200) Subject: Do not skip past EOF while reading (#2584) X-Git-Tag: v3.8.0~40 X-Git-Url: http://git.ipfire.org/gitweb/gitweb.cgi?a=commitdiff_plain;h=dcbf1e0ededa95849f098d154a25876ed5754bcf;p=thirdparty%2Flibarchive.git Do not skip past EOF while reading (#2584) 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 --- diff --git a/libarchive/archive_read_open_fd.c b/libarchive/archive_read_open_fd.c index 3fd536d57..dc7c9e52c 100644 --- a/libarchive/archive_read_open_fd.c +++ b/libarchive/archive_read_open_fd.c @@ -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; diff --git a/libarchive/archive_read_open_file.c b/libarchive/archive_read_open_file.c index 2829b9a5c..6ed18a0c0 100644 --- a/libarchive/archive_read_open_file.c +++ b/libarchive/archive_read_open_file.c @@ -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); } /* diff --git a/libarchive/archive_read_open_filename.c b/libarchive/archive_read_open_filename.c index 3894b15c8..5f5b3f1f7 100644 --- a/libarchive/archive_read_open_filename.c +++ b/libarchive/archive_read_open_filename.c @@ -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; diff --git a/libarchive/test/test_read_format_rar.c b/libarchive/test/test_read_format_rar.c index dce567af4..fce44a9d8 100644 --- a/libarchive/test/test_read_format_rar.c +++ b/libarchive/test/test_read_format_rar.c @@ -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));