From dcbf1e0ededa95849f098d154a25876ed5754bcf Mon Sep 17 00:00:00 2001 From: Tobias Stoeckmann Date: Tue, 15 Apr 2025 06:02:17 +0200 Subject: [PATCH] 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 CVE: CVE-2025-5918 Upstream-Status: Backport [https://github.com/libarchive/libarchive/commit/dcbf1e0ededa95849f098d154a25876ed5754bcf] Signed-off-by: Divya Chellam --- libarchive/archive_read_open_fd.c | 13 +++++++--- libarchive/archive_read_open_file.c | 33 +++++++++++++++++++------ libarchive/archive_read_open_filename.c | 16 +++++++++--- libarchive/test/test_read_format_rar.c | 6 ++--- 4 files changed, 50 insertions(+), 18 deletions(-) diff --git a/libarchive/archive_read_open_fd.c b/libarchive/archive_read_open_fd.c index 3fd536d..dc7c9e5 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 2829b9a..6ed18a0 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 3894b15..5f5b3f1 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 dce567a..fce44a9 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)); -- 2.40.0