File CVE-2025-5918.patch of Package libarchive.39705

From 45079e8e60eccbf3002f8b9bc02f19ca22c46682 Mon Sep 17 00:00:00 2001
From: Tobias Stoeckmann <tobias@stoeckmann.org>
Date: Sun, 27 Apr 2025 11:50:20 +0200
Subject: [PATCH] rar: Check packed_size constraints

Make sure that size_t casts do not truncate the value of packed_size on
32 bit systems since it's 64 bit. Extensions to RAR format allow 64 bit
values to be specified in archives.

Also verify that 64 bit signed arithmetics do not overflow.

Signed-off-by: Tobias Stoeckmann <tobias@stoeckmann.org>
---
 Makefile.am                                   |  2 +
 libarchive/archive_read_support_format_rar.c  | 47 ++++++++++++++++--
 libarchive/test/test_read_format_rar.c        | 48 +++++++++++++++++++
 .../test_read_format_rar_newsub_huge.rar.uu   |  5 ++
 .../test_read_format_rar_symlink_huge.rar.uu  |  5 ++
 5 files changed, 104 insertions(+), 3 deletions(-)
 create mode 100644 libarchive/test/test_read_format_rar_newsub_huge.rar.uu
 create mode 100644 libarchive/test/test_read_format_rar_symlink_huge.rar.uu

Index: libarchive-3.5.1/Makefile.am
===================================================================
--- libarchive-3.5.1.orig/Makefile.am
+++ libarchive-3.5.1/Makefile.am
@@ -837,12 +837,14 @@ libarchive_test_EXTRA_DIST=\
 	libarchive/test/test_read_format_rar_multivolume.part0002.rar.uu \
 	libarchive/test/test_read_format_rar_multivolume.part0003.rar.uu \
 	libarchive/test/test_read_format_rar_multivolume.part0004.rar.uu \
+	libarchive/test/test_read_format_rar_newsub_huge.rar.uu \
 	libarchive/test/test_read_format_rar_noeof.rar.uu \
 	libarchive/test/test_read_format_rar_ppmd_lzss_conversion.rar.uu \
 	libarchive/test/test_read_format_rar_ppmd_use_after_free.rar.uu \
 	libarchive/test/test_read_format_rar_ppmd_use_after_free2.rar.uu \
 	libarchive/test/test_read_format_rar_sfx.exe.uu \
 	libarchive/test/test_read_format_rar_subblock.rar.uu \
+	libarchive/test/test_read_format_rar_symlink_huge.rar.uu \
 	libarchive/test/test_read_format_rar_unicode.rar.uu \
 	libarchive/test/test_read_format_rar_windows.rar.uu \
 	libarchive/test/test_read_format_rar5_arm.rar.uu \
Index: libarchive-3.5.1/libarchive/archive_read_support_format_rar.c
===================================================================
--- libarchive-3.5.1.orig/libarchive/archive_read_support_format_rar.c
+++ libarchive-3.5.1/libarchive/archive_read_support_format_rar.c
@@ -859,8 +859,11 @@ archive_read_format_rar_read_header(stru
   {
     unsigned long crc32_val;
 
-    if ((h = __archive_read_ahead(a, 7, NULL)) == NULL)
+    if ((h = __archive_read_ahead(a, 7, NULL)) == NULL) {
+      archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                        "Failed to read next header.");
       return (ARCHIVE_FATAL);
+    }
     p = h;
 
     head_type = p[2];
@@ -1332,7 +1335,11 @@ read_header(struct archive_read *a, stru
   }
 
   if ((h = __archive_read_ahead(a, (size_t)header_size - 7, NULL)) == NULL)
+  {
+    archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                      "Failed to read full header content.");
     return (ARCHIVE_FATAL);
+  }
 
   /* File Header CRC check. */
   crc32_val = crc32(crc32_val, h, (unsigned)(header_size - 7));
@@ -1397,10 +1404,23 @@ read_header(struct archive_read *a, stru
    */
   if (head_type == NEWSUB_HEAD) {
     size_t distance = p - (const char *)h;
+    if (rar->packed_size > INT64_MAX - header_size) {
+      archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                        "Extended header size too large.");
+      return (ARCHIVE_FATAL);
+    }
     header_size += rar->packed_size;
+    if ((uintmax_t)header_size > SIZE_MAX) {
+      archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                        "Unable to read extended header data.");
+      return (ARCHIVE_FATAL);
+    }
     /* Make sure we have the extended data. */
-    if ((h = __archive_read_ahead(a, (size_t)header_size - 7, NULL)) == NULL)
-        return (ARCHIVE_FATAL);
+    if ((h = __archive_read_ahead(a, (size_t)header_size - 7, NULL)) == NULL) {
+      archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                        "Failed to read extended header data.");
+      return (ARCHIVE_FATAL);
+    }
     p = h;
     endp = p + header_size - 7;
     p += distance;
@@ -1564,6 +1584,12 @@ read_header(struct archive_read *a, stru
     }
     if (rar->dbo[rar->cursor].start_offset < 0)
     {
+      if (rar->packed_size > INT64_MAX - a->filter->position)
+      {
+        archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                          "Unable to store offsets.");
+        return (ARCHIVE_FATAL);
+      }
       rar->dbo[rar->cursor].start_offset = a->filter->position;
       rar->dbo[rar->cursor].end_offset = rar->dbo[rar->cursor].start_offset +
         rar->packed_size;
@@ -1615,6 +1641,11 @@ read_header(struct archive_read *a, stru
   }
 
   __archive_read_consume(a, header_size - 7);
+  if (rar->packed_size > INT64_MAX - a->filter->position) {
+    archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                      "Unable to store offsets.");
+    return (ARCHIVE_FATAL);
+  }
   rar->dbo[0].start_offset = a->filter->position;
   rar->dbo[0].end_offset = rar->dbo[0].start_offset + rar->packed_size;
 
@@ -1819,8 +1850,18 @@ read_symlink_stored(struct archive_read
   int ret = (ARCHIVE_OK);
 
   rar = (struct rar *)(a->format->data);
+  if ((uintmax_t)rar->packed_size > SIZE_MAX)
+  {
+    archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                      "Unable to read link.");
+    return (ARCHIVE_FATAL);
+  }
   if ((h = rar_read_ahead(a, (size_t)rar->packed_size, NULL)) == NULL)
+  {
+    archive_set_error(&a->archive, ARCHIVE_ERRNO_FILE_FORMAT,
+                      "Failed to read link.");
     return (ARCHIVE_FATAL);
+  }
   p = h;
 
   if (archive_entry_copy_symlink_l(entry,
Index: libarchive-3.5.1/libarchive/test/test_read_format_rar.c
===================================================================
--- libarchive-3.5.1.orig/libarchive/test/test_read_format_rar.c
+++ libarchive-3.5.1/libarchive/test/test_read_format_rar.c
@@ -3776,8 +3776,8 @@ DEFINE_TEST(test_read_format_rar_ppmd_us
   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));
@@ -3803,8 +3803,56 @@ DEFINE_TEST(test_read_format_rar_ppmd_us
   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));
+}
+
+DEFINE_TEST(test_read_format_rar_newsub_huge)
+{
+#if SIZE_MAX == UINT64_MAX
+  skipping("not relevant on 64 bit platforms");
+#else
+  const char* reffile = "test_read_format_rar_newsub_huge.rar";
+
+  struct archive_entry *ae;
+  struct archive *a;
+
+  extract_reference_file(reffile);
+  assert((a = archive_read_new()) != NULL);
+  assertA(0 == archive_read_support_filter_all(a));
+  assertA(0 == archive_read_support_format_all(a));
+  assertA(0 == archive_read_open_filename(a, reffile, 10240));
+
+  /* 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));
+#endif
+}
+
+DEFINE_TEST(test_read_format_rar_symlink_huge)
+{
+#if SIZE_MAX == UINT64_MAX
+  skipping("not relevant on 64 bit platforms");
+#else
+  const char* reffile = "test_read_format_rar_symlink_huge.rar";
+
+  struct archive_entry *ae;
+  struct archive *a;
+
+  extract_reference_file(reffile);
+  assert((a = archive_read_new()) != NULL);
+  assertA(0 == archive_read_support_filter_all(a));
+  assertA(0 == archive_read_support_format_all(a));
+  assertA(0 == archive_read_open_filename(a, reffile, 10240));
+
+  /* Test for invalid entry */
+  assertA(ARCHIVE_FATAL == archive_read_next_header(a, &ae));
 
   assertEqualIntA(a, ARCHIVE_OK, archive_read_close(a));
   assertEqualInt(ARCHIVE_OK, archive_read_free(a));
+#endif
 }
Index: libarchive-3.5.1/libarchive/test/test_read_format_rar_newsub_huge.rar.uu
===================================================================
--- /dev/null
+++ libarchive-3.5.1/libarchive/test/test_read_format_rar_newsub_huge.rar.uu
@@ -0,0 +1,5 @@
+begin 644 test_read_format_rar_newsub_huge.rar.uu
+M4F%R(1H'`",E>@`!*```````````````````````````````````____?P``
+"````
+`
+end
Index: libarchive-3.5.1/libarchive/test/test_read_format_rar_symlink_huge.rar.uu
===================================================================
--- /dev/null
+++ libarchive-3.5.1/libarchive/test/test_read_format_rar_symlink_huge.rar.uu
@@ -0,0 +1,5 @@
+begin 644 test_read_format_rar_symlink_huge.rar
+M4F%R(1H'`'6E=``!*0````````````,``````````````0``H```____?P``
+#``!X
+`
+end
Index: libarchive-3.5.1/libarchive/archive_read_open_filename.c
===================================================================
--- libarchive-3.5.1.orig/libarchive/archive_read_open_filename.c
+++ libarchive-3.5.1/libarchive/archive_read_open_filename.c
@@ -75,6 +75,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 {
@@ -370,8 +371,10 @@ file_open(struct archive *a, void *clien
 	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:
@@ -449,21 +452,30 @@ file_skip_lseek(struct archive *a, void
 	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.
-	 */
-	if ((old_offset = lseek(mine->fd, 0, SEEK_CUR)) >= 0 &&
-	    (new_offset = lseek(mine->fd, request, SEEK_CUR)) >= 0)
-		return (new_offset - old_offset);
+	/* 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) {
+		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;
@@ -510,11 +522,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;
 
Index: libarchive-3.5.1/libarchive/archive_read_open_fd.c
===================================================================
--- libarchive-3.5.1.orig/libarchive/archive_read_open_fd.c
+++ libarchive-3.5.1/libarchive/archive_read_open_fd.c
@@ -53,6 +53,7 @@ __FBSDID("$FreeBSD: head/lib/libarchive/
 struct read_fd_data {
 	int	 fd;
 	size_t	 block_size;
+	int64_t	 size;
 	char	 use_lseek;
 	void	*buffer;
 };
@@ -96,6 +97,7 @@ archive_read_open_fd(struct archive *a,
 	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);
@@ -152,9 +154,14 @@ file_skip(struct archive *a, void *clien
 	if (request == 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: libarchive-3.5.1/libarchive/archive_read_open_file.c
===================================================================
--- libarchive-3.5.1.orig/libarchive/archive_read_open_file.c
+++ libarchive-3.5.1/libarchive/archive_read_open_file.c
@@ -53,6 +53,7 @@ __FBSDID("$FreeBSD: head/lib/libarchive/
 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
 		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;
 	} else
 		mine->can_skip = 0;
 
@@ -130,6 +132,7 @@ file_skip(struct archive *a, void *clien
 #else
 	long skip = (long)request;
 #endif
+	int64_t old_offset, new_offset = -1;
 	int skip_bits = sizeof(skip) * 8 - 1;
 
 	(void)a; /* UNUSED */
openSUSE Build Service is sponsored by