libarchive: fix CVE-2025-5918

A vulnerability has been identified in the libarchive library. This flaw can be triggered whe
n file streams are piped into bsdtar, potentially allowing for reading past the end of the fi
le. This out-of-bounds read can lead to unintended consequences, including unpredictable prog
ram behavior, memory corruption, or a denial-of-service condition.

CVE-2025-5918-0001 is the dependent commit and CVE-2025-5918-0002 is the actual CVE fix.

Reference:
https://security-tracker.debian.org/tracker/CVE-2025-5918

Upstream-patches:
89b8c35ff4
dcbf1e0ede

(From OE-Core rev: 369c164a163b2c7f15ee5fc41130be9feaf7245e)

Signed-off-by: Divya Chellam <divya.chellam@windriver.com>
Signed-off-by: Steve Sakoman <steve@sakoman.com>
This commit is contained in:
Divya Chellam
2025-07-02 12:21:34 +05:30
committed by Steve Sakoman
parent 3c2bbf4a1c
commit 37be814fb2
3 changed files with 550 additions and 0 deletions

View File

@@ -0,0 +1,326 @@
From 89b8c35ff4b5addc08a85bf5df02b407f8af1f6c Mon Sep 17 00:00:00 2001
From: Tobias Stoeckmann <stoeckmann@users.noreply.github.com>
Date: Sun, 6 Apr 2025 22:34:37 +0200
Subject: [PATCH] Improve lseek handling (#2564)
The skip functions are limited to 1 GB for cases in which libarchive
runs on a system with an off_t or long with 32 bits. This has negative
impact on 64 bit systems.
Instead, make sure that _all_ subsequent functions truncate properly.
Some of them already did and some had regressions for over 10 years.
Tests pass on Debian 12 i686 configured with --disable-largefile, i.e.
running with an off_t with 32 bits.
Casts added where needed to still pass MSVC builds.
---------
Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
CVE: CVE-2025-5918
Upstream-Status: Backport [https://github.com/libarchive/libarchive/commit/89b8c35ff4b5addc08a85bf5df02b407f8af1f6c]
Signed-off-by: Divya Chellam <divya.chellam@windriver.com>
---
libarchive/archive_read.c | 6 ----
libarchive/archive_read_disk_posix.c | 3 +-
libarchive/archive_read_open_fd.c | 29 +++++++++++++------
libarchive/archive_read_open_file.c | 35 ++++++++++++-----------
libarchive/archive_read_open_filename.c | 37 ++++++++++++++++++-------
libarchive/test/read_open_memory.c | 2 +-
libarchive/test/test_sparse_basic.c | 6 ++--
libarchive/test/test_tar_large.c | 2 +-
8 files changed, 75 insertions(+), 45 deletions(-)
diff --git a/libarchive/archive_read.c b/libarchive/archive_read.c
index 822c534..50db870 100644
--- a/libarchive/archive_read.c
+++ b/libarchive/archive_read.c
@@ -176,15 +176,9 @@ client_skip_proxy(struct archive_read_filter *self, int64_t request)
return 0;
if (self->archive->client.skipper != NULL) {
- /* Seek requests over 1GiB are broken down into
- * multiple seeks. This avoids overflows when the
- * requests get passed through 32-bit arguments. */
- int64_t skip_limit = (int64_t)1 << 30;
int64_t total = 0;
for (;;) {
int64_t get, ask = request;
- if (ask > skip_limit)
- ask = skip_limit;
get = (self->archive->client.skipper)
(&self->archive->archive, self->data, ask);
total += get;
diff --git a/libarchive/archive_read_disk_posix.c b/libarchive/archive_read_disk_posix.c
index 09965eb..4839d62 100644
--- a/libarchive/archive_read_disk_posix.c
+++ b/libarchive/archive_read_disk_posix.c
@@ -778,7 +778,8 @@ _archive_read_data_block(struct archive *_a, const void **buff,
*/
if (t->current_sparse->offset > t->entry_total) {
if (lseek(t->entry_fd,
- (off_t)t->current_sparse->offset, SEEK_SET) < 0) {
+ (off_t)t->current_sparse->offset, SEEK_SET) !=
+ t->current_sparse->offset) {
archive_set_error(&a->archive, errno, "Seek error");
r = ARCHIVE_FATAL;
a->archive.state = ARCHIVE_STATE_FATAL;
diff --git a/libarchive/archive_read_open_fd.c b/libarchive/archive_read_open_fd.c
index debfde2..3fd536d 100644
--- a/libarchive/archive_read_open_fd.c
+++ b/libarchive/archive_read_open_fd.c
@@ -131,7 +131,7 @@ static int64_t
file_skip(struct archive *a, void *client_data, int64_t request)
{
struct read_fd_data *mine = (struct read_fd_data *)client_data;
- int64_t skip = request;
+ off_t skip = (off_t)request;
int64_t old_offset, new_offset;
int skip_bits = sizeof(skip) * 8 - 1; /* off_t is a signed type. */
@@ -140,15 +140,15 @@ file_skip(struct archive *a, void *client_data, int64_t request)
/* Reduce a request that would overflow the 'skip' variable. */
if (sizeof(request) > sizeof(skip)) {
- int64_t max_skip =
+ const int64_t max_skip =
(((int64_t)1 << (skip_bits - 1)) - 1) * 2 + 1;
if (request > max_skip)
- skip = max_skip;
+ skip = (off_t)max_skip;
}
- /* Reduce request to the next smallest multiple of block_size */
- request = (request / mine->block_size) * mine->block_size;
- if (request == 0)
+ /* Reduce 'skip' to the next smallest multiple of block_size */
+ skip = (off_t)(((int64_t)skip / mine->block_size) * mine->block_size);
+ if (skip == 0)
return (0);
if (((old_offset = lseek(mine->fd, 0, SEEK_CUR)) >= 0) &&
@@ -178,11 +178,24 @@ static int64_t
file_seek(struct archive *a, void *client_data, int64_t request, int whence)
{
struct read_fd_data *mine = (struct read_fd_data *)client_data;
+ off_t seek = (off_t)request;
int64_t r;
+ int seek_bits = sizeof(seek) * 8 - 1; /* off_t is a signed type. */
/* We use off_t here because lseek() is declared that way. */
- /* See above for notes about when off_t is less than 64 bits. */
- r = lseek(mine->fd, request, whence);
+
+ /* Reduce a request that would overflow the 'seek' variable. */
+ if (sizeof(request) > sizeof(seek)) {
+ const int64_t max_seek =
+ (((int64_t)1 << (seek_bits - 1)) - 1) * 2 + 1;
+ const int64_t min_seek = ~max_seek;
+ if (request > max_seek)
+ seek = (off_t)max_seek;
+ else if (request < min_seek)
+ seek = (off_t)min_seek;
+ }
+
+ r = lseek(mine->fd, seek, whence);
if (r >= 0)
return r;
diff --git a/libarchive/archive_read_open_file.c b/libarchive/archive_read_open_file.c
index ecd56dc..2829b9a 100644
--- a/libarchive/archive_read_open_file.c
+++ b/libarchive/archive_read_open_file.c
@@ -145,7 +145,7 @@ FILE_skip(struct archive *a, void *client_data, int64_t request)
/* If request is too big for a long or an off_t, reduce it. */
if (sizeof(request) > sizeof(skip)) {
- int64_t max_skip =
+ const int64_t max_skip =
(((int64_t)1 << (skip_bits - 1)) - 1) * 2 + 1;
if (request > max_skip)
skip = max_skip;
@@ -176,39 +176,42 @@ FILE_seek(struct archive *a, void *client_data, int64_t request, int whence)
{
struct read_FILE_data *mine = (struct read_FILE_data *)client_data;
#if HAVE__FSEEKI64
- int64_t skip = request;
+ int64_t seek = request;
#elif HAVE_FSEEKO
- off_t skip = (off_t)request;
+ off_t seek = (off_t)request;
#else
- long skip = (long)request;
+ long seek = (long)request;
#endif
- int skip_bits = sizeof(skip) * 8 - 1;
+ int seek_bits = sizeof(seek) * 8 - 1;
(void)a; /* UNUSED */
- /* If request is too big for a long or an off_t, reduce it. */
- if (sizeof(request) > sizeof(skip)) {
- int64_t max_skip =
- (((int64_t)1 << (skip_bits - 1)) - 1) * 2 + 1;
- if (request > max_skip)
- skip = max_skip;
+ /* Reduce a request that would overflow the 'seek' variable. */
+ if (sizeof(request) > sizeof(seek)) {
+ const int64_t max_seek =
+ (((int64_t)1 << (seek_bits - 1)) - 1) * 2 + 1;
+ const int64_t min_seek = ~max_seek;
+ if (request > max_seek)
+ seek = max_seek;
+ else if (request < min_seek)
+ seek = min_seek;
}
#ifdef __ANDROID__
/* Newer Android versions have fseeko...to meditate. */
- int64_t ret = lseek(fileno(mine->f), skip, whence);
+ int64_t ret = lseek(fileno(mine->f), seek, whence);
if (ret >= 0) {
return ret;
}
#elif HAVE__FSEEKI64
- if (_fseeki64(mine->f, skip, whence) == 0) {
+ if (_fseeki64(mine->f, seek, whence) == 0) {
return _ftelli64(mine->f);
}
#elif HAVE_FSEEKO
- if (fseeko(mine->f, skip, whence) == 0) {
+ if (fseeko(mine->f, seek, whence) == 0) {
return ftello(mine->f);
}
#else
- if (fseek(mine->f, skip, whence) == 0) {
+ if (fseek(mine->f, seek, whence) == 0) {
return ftell(mine->f);
}
#endif
@@ -226,4 +229,4 @@ FILE_close(struct archive *a, void *client_data)
free(mine->buffer);
free(mine);
return (ARCHIVE_OK);
-}
\ No newline at end of file
+}
diff --git a/libarchive/archive_read_open_filename.c b/libarchive/archive_read_open_filename.c
index 05f0ffb..3894b15 100644
--- a/libarchive/archive_read_open_filename.c
+++ b/libarchive/archive_read_open_filename.c
@@ -479,20 +479,24 @@ file_skip_lseek(struct archive *a, void *client_data, int64_t request)
struct read_file_data *mine = (struct read_file_data *)client_data;
#if defined(_WIN32) && !defined(__CYGWIN__)
/* We use _lseeki64() on Windows. */
- int64_t old_offset, new_offset;
+ int64_t old_offset, new_offset, skip = request;
#else
- off_t old_offset, new_offset;
+ off_t old_offset, new_offset, skip = (off_t)request;
#endif
+ int skip_bits = sizeof(skip) * 8 - 1;
/* We use off_t here because lseek() is declared that way. */
- /* TODO: Deal with case where off_t isn't 64 bits.
- * This shouldn't be a problem on Linux or other POSIX
- * systems, since the configuration logic for libarchive
- * tries to obtain a 64-bit off_t.
- */
+ /* Reduce a request that would overflow the 'skip' variable. */
+ if (sizeof(request) > sizeof(skip)) {
+ const int64_t max_skip =
+ (((int64_t)1 << (skip_bits - 1)) - 1) * 2 + 1;
+ if (request > max_skip)
+ skip = max_skip;
+ }
+
if ((old_offset = lseek(mine->fd, 0, SEEK_CUR)) >= 0 &&
- (new_offset = lseek(mine->fd, request, SEEK_CUR)) >= 0)
+ (new_offset = lseek(mine->fd, skip, SEEK_CUR)) >= 0)
return (new_offset - old_offset);
/* If lseek() fails, don't bother trying again. */
@@ -540,11 +544,24 @@ static int64_t
file_seek(struct archive *a, void *client_data, int64_t request, int whence)
{
struct read_file_data *mine = (struct read_file_data *)client_data;
+ off_t seek = (off_t)request;
int64_t r;
+ int seek_bits = sizeof(seek) * 8 - 1;
/* We use off_t here because lseek() is declared that way. */
- /* See above for notes about when off_t is less than 64 bits. */
- r = lseek(mine->fd, request, whence);
+
+ /* Reduce a request that would overflow the 'seek' variable. */
+ if (sizeof(request) > sizeof(seek)) {
+ const int64_t max_seek =
+ (((int64_t)1 << (seek_bits - 1)) - 1) * 2 + 1;
+ const int64_t min_seek = ~max_seek;
+ if (request > max_seek)
+ seek = (off_t)max_seek;
+ else if (request < min_seek)
+ seek = (off_t)min_seek;
+ }
+
+ r = lseek(mine->fd, seek, whence);
if (r >= 0)
return r;
diff --git a/libarchive/test/read_open_memory.c b/libarchive/test/read_open_memory.c
index 6d2468c..9262ab9 100644
--- a/libarchive/test/read_open_memory.c
+++ b/libarchive/test/read_open_memory.c
@@ -167,7 +167,7 @@ memory_read_skip(struct archive *a, void *client_data, int64_t skip)
(void)a; /* UNUSED */
/* We can't skip by more than is available. */
- if ((off_t)skip > (off_t)(mine->end - mine->p))
+ if (skip > mine->end - mine->p)
skip = mine->end - mine->p;
/* Always do small skips by prime amounts. */
if (skip > 71)
diff --git a/libarchive/test/test_sparse_basic.c b/libarchive/test/test_sparse_basic.c
index 23cde56..93710cb 100644
--- a/libarchive/test/test_sparse_basic.c
+++ b/libarchive/test/test_sparse_basic.c
@@ -608,7 +608,8 @@ DEFINE_TEST(test_sparse_basic)
verify_sparse_file(a, "file2", sparse_file2, 20);
/* Encoded non sparse; expect a data block but no sparse entries. */
verify_sparse_file(a, "file3", sparse_file3, 0);
- verify_sparse_file(a, "file4", sparse_file4, 2);
+ if (sizeof(off_t) > 4)
+ verify_sparse_file(a, "file4", sparse_file4, 2);
assertEqualInt(ARCHIVE_OK, archive_read_free(a));
@@ -635,7 +636,8 @@ DEFINE_TEST(test_sparse_basic)
verify_sparse_file(a, "file1", sparse_file1, 0);
verify_sparse_file(a, "file2", sparse_file2, 0);
verify_sparse_file(a, "file3", sparse_file3, 0);
- verify_sparse_file(a, "file4", sparse_file4, 0);
+ if (sizeof(off_t) > 4)
+ verify_sparse_file(a, "file4", sparse_file4, 0);
assertEqualInt(ARCHIVE_OK, archive_read_free(a));
diff --git a/libarchive/test/test_tar_large.c b/libarchive/test/test_tar_large.c
index c1f3791..1cde321 100644
--- a/libarchive/test/test_tar_large.c
+++ b/libarchive/test/test_tar_large.c
@@ -175,7 +175,7 @@ memory_read_skip(struct archive *a, void *_private, int64_t skip)
}
if (private->filebytes > 0) {
if (private->filebytes < skip)
- skip = (off_t)private->filebytes;
+ skip = private->filebytes;
private->filebytes -= skip;
} else {
skip = 0;
--
2.40.0

View File

@@ -0,0 +1,222 @@
From dcbf1e0ededa95849f098d154a25876ed5754bcf Mon Sep 17 00:00:00 2001
From: Tobias Stoeckmann <stoeckmann@users.noreply.github.com>
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 <tobias@stoeckmann.org>
CVE: CVE-2025-5918
Upstream-Status: Backport [https://github.com/libarchive/libarchive/commit/dcbf1e0ededa95849f098d154a25876ed5754bcf]
Signed-off-by: Divya Chellam <divya.chellam@windriver.com>
---
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

View File

@@ -35,6 +35,8 @@ SRC_URI = "http://libarchive.org/downloads/libarchive-${PV}.tar.gz \
file://CVE-2025-5915.patch \
file://CVE-2025-5916.patch \
file://CVE-2025-5917.patch \
file://CVE-2025-5918-0001.patch \
file://CVE-2025-5918-0002.patch \
"
UPSTREAM_CHECK_URI = "http://libarchive.org/"