From 888910dadeae13f290bf97606d0f388f9f10903d Mon Sep 17 00:00:00 2001
From: Julian Andres Klode <jak@debian.org>
Date: Mon, 7 Dec 2020 12:31:04 +0100
Subject: [PATCH] Import Debian version 1.8.2.2

apt (1.8.2.2) buster-security; urgency=high

  * SECURITY UPDATE: Integer overflow in parsing (LP: #1899193)
    - apt-pkg/contrib/arfile.cc: add extra checks.
    - apt-pkg/contrib/tarfile.cc: limit tar item sizes to 128 GiB
    - apt-pkg/deb/debfile.cc: limit control file sizes to 64 MiB
    - test/*: add tests.
    - CVE-2020-27350
  * Additional hardening:
    - apt-pkg/contrib/tarfile.cc: Limit size of long names and links to 1 MiB
  * Fix autopkgtest regression in 1.8.2.1 security update
---
 CMakeLists.txt                                |   2 +-
 apt-inst/contrib/arfile.cc                    |  14 +-
 apt-inst/contrib/extracttar.cc                |  21 +-
 apt-inst/deb/debfile.cc                       |  15 +
 debian/changelog                              |  14 +
 doc/apt-verbatim.ent                          |   2 +-
 doc/po/apt-doc.pot                            |   4 +-
 po/apt-all.pot                                |   4 +-
 test/integration/test-cve-2020-27350          |  25 ++
 .../test-github-111-invalid-armember          |   8 +-
 test/interactive-helper/CMakeLists.txt        |   2 +
 .../createdeb-cve-2020-27350.cc               | 325 ++++++++++++++++++
 12 files changed, 424 insertions(+), 12 deletions(-)
 create mode 100755 test/integration/test-cve-2020-27350
 create mode 100644 test/interactive-helper/createdeb-cve-2020-27350.cc

diff --git a/CMakeLists.txt b/CMakeLists.txt
index 83334ba..4117aeb 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -193,7 +193,7 @@ check_cxx_target(HAVE_FMV_SSE42_AND_CRC32DI "sse4.2" "__builtin_ia32_crc32di(0,
 # Configure some variables like package, version and architecture.
 set(PACKAGE ${PROJECT_NAME})
 set(PACKAGE_MAIL "APT Development Team <deity@lists.debian.org>")
-set(PACKAGE_VERSION "1.8.2.1")
+set(PACKAGE_VERSION "1.8.2.2")
 
 if (NOT DEFINED DPKG_DATADIR)
   execute_process(COMMAND ${PERL_EXECUTABLE} -MDpkg -e "print $Dpkg::DATADIR;"
diff --git a/apt-inst/contrib/arfile.cc b/apt-inst/contrib/arfile.cc
index 5cb43c6..6d4a1f1 100644
--- a/apt-inst/contrib/arfile.cc
+++ b/apt-inst/contrib/arfile.cc
@@ -94,7 +94,12 @@ bool ARArchive::LoadHeaders()
 	 delete Memb;
 	 return _error->Error(_("Invalid archive member header"));
       }
-	 
+
+      if (Left < 0 || Memb->Size > static_cast<unsigned long long>(Left))
+      {
+	 delete Memb;
+	 return _error->Error(_("Invalid archive member header"));
+      }
       // Check for an extra long name string
       if (memcmp(Head.Name,"#1/",3) == 0)
       {
@@ -106,6 +111,13 @@ bool ARArchive::LoadHeaders()
 	    delete Memb;
 	    return _error->Error(_("Invalid archive member header"));
 	 }
+
+	 if (Len > Memb->Size)
+	 {
+	    delete Memb;
+	    return _error->Error(_("Invalid archive member header"));
+	 }
+
 	 if (File.Read(S,Len) == false)
 	 {
 	    delete Memb;
diff --git a/apt-inst/contrib/extracttar.cc b/apt-inst/contrib/extracttar.cc
index 19ac1c8..cbee4d1 100644
--- a/apt-inst/contrib/extracttar.cc
+++ b/apt-inst/contrib/extracttar.cc
@@ -55,7 +55,17 @@ struct ExtractTar::TarHeader
    char Major[8];
    char Minor[8];      
 };
-   
+
+// We need to read long names (names and link targets) into memory, so let's
+// have a limit (shamelessly stolen from libarchive) to avoid people OOMing
+// us with large streams.
+static const unsigned long long APT_LONGNAME_LIMIT = 1048576llu;
+
+// A file size limit that we allow extracting. Currently, that's 128 GB.
+// We also should leave some wiggle room for code adding files to it, and
+// possibly conversion for signed, so this should not be larger than like 2**62.
+static const unsigned long long APT_FILESIZE_LIMIT = 1llu << 37;
+
 // ExtractTar::ExtractTar - Constructor					/*{{{*/
 // ---------------------------------------------------------------------
 /* */
@@ -170,6 +180,11 @@ bool ExtractTar::Go(pkgDirStream &Stream)
 	  StrToNum(Tar->Minor,Itm.Minor,sizeof(Tar->Minor),8) == false)
 	 return _error->Error(_("Corrupted archive"));
 
+      // Security check. Prevents overflows below the code when rounding up in skip/copy code,
+      // and provides modest protection against decompression bombs.
+      if (Itm.Size > APT_FILESIZE_LIMIT)
+	 return _error->Error("Tar member too large: %llu > %llu bytes", Itm.Size, APT_FILESIZE_LIMIT);
+
       // Grab the filename and link target: use last long name if one was
       // set, otherwise use the header value as-is, but remember that it may
       // fill the entire 100-byte block and needs to be zero-terminated.
@@ -222,6 +237,8 @@ bool ExtractTar::Go(pkgDirStream &Stream)
 	 {
 	    unsigned long long Length = Itm.Size;
 	    unsigned char Block[512];
+	    if (Length > APT_LONGNAME_LIMIT)
+	       return _error->Error("Long name to large: %llu bytes > %llu bytes", Length, APT_LONGNAME_LIMIT);
 	    while (Length > 0)
 	    {
 	       if (InFd.Read(Block,sizeof(Block),true) == false)
@@ -241,6 +258,8 @@ bool ExtractTar::Go(pkgDirStream &Stream)
 	 {
 	    unsigned long long Length = Itm.Size;
 	    unsigned char Block[512];
+	    if (Length > APT_LONGNAME_LIMIT)
+	       return _error->Error("Long name to large: %llu bytes > %llu bytes", Length, APT_LONGNAME_LIMIT);
 	    while (Length > 0)
 	    {
 	       if (InFd.Read(Block,sizeof(Block),true) == false)
diff --git a/apt-inst/deb/debfile.cc b/apt-inst/deb/debfile.cc
index f8d752e..bef0cd0 100644
--- a/apt-inst/deb/debfile.cc
+++ b/apt-inst/deb/debfile.cc
@@ -184,11 +184,23 @@ bool debDebFile::ControlExtract::DoItem(Item &Itm,int &Fd)
 // ---------------------------------------------------------------------
 /* This sets up to extract the control block member file into a memory 
    block of just the right size. All other files go into the bit bucket. */
+
+// Upper size limit for control files. Two reasons for having a limit here:
+//
+// 1. We read those files into memory and want to avoid being killed by OOM
+//
+// 2. We allocate (Itm.Size+2)-large arrays, so this can overflow if Itm.Size
+// becomes 2**64-2 or larger. This is obviously
+//
+// 64 MiB seems like a terribly large size that everyone should be happy with.
+static const unsigned long long DEB_CONTROL_SIZE_LIMIT = 64 * 1024 * 1024;
 bool debDebFile::MemControlExtract::DoItem(Item &Itm,int &Fd)
 {
    // At the control file, allocate buffer memory.
    if (Member == Itm.Name)
    {
+      if (Itm.Size > DEB_CONTROL_SIZE_LIMIT)
+	 return _error->Error("Control file too large: %llu > %llu bytes", Itm.Size, DEB_CONTROL_SIZE_LIMIT);
       delete [] Control;
       Control = new char[Itm.Size+2];
       IsControl = true;
@@ -237,6 +249,9 @@ bool debDebFile::MemControlExtract::Read(debDebFile &Deb)
    record. */
 bool debDebFile::MemControlExtract::TakeControl(const void *Data,unsigned long long Size)
 {
+   if (Size > DEB_CONTROL_SIZE_LIMIT)
+      return _error->Error("Control file too large: %llu > %llu bytes", Size, DEB_CONTROL_SIZE_LIMIT);
+
    delete [] Control;
    Control = new char[Size+2];
    Length = Size;
diff --git a/debian/changelog b/debian/changelog
index ec4769b..44f80d1 100644
--- a/debian/changelog
+++ b/debian/changelog
@@ -1,3 +1,17 @@
+apt (1.8.2.2) buster-security; urgency=high
+
+  * SECURITY UPDATE: Integer overflow in parsing (LP: #1899193)
+    - apt-pkg/contrib/arfile.cc: add extra checks.
+    - apt-pkg/contrib/tarfile.cc: limit tar item sizes to 128 GiB
+    - apt-pkg/deb/debfile.cc: limit control file sizes to 64 MiB
+    - test/*: add tests.
+    - CVE-2020-27350
+  * Additional hardening:
+    - apt-pkg/contrib/tarfile.cc: Limit size of long names and links to 1 MiB
+  * Fix autopkgtest regression in 1.8.2.1 security update
+
+ -- Julian Andres Klode <jak@debian.org>  Mon, 07 Dec 2020 12:31:04 +0100
+
 apt (1.8.2.1) buster-security; urgency=high
 
   * SECURITY UPDATE: Out of bounds read in ar, tar implementations (LP: #1878177)
diff --git a/doc/apt-verbatim.ent b/doc/apt-verbatim.ent
index 54c81b8..b8c33d0 100644
--- a/doc/apt-verbatim.ent
+++ b/doc/apt-verbatim.ent
@@ -268,7 +268,7 @@
 ">
 
 <!-- this will be updated by 'prepare-release' -->
-<!ENTITY apt-product-version "1.8.2.1">
+<!ENTITY apt-product-version "1.8.2.2">
 
 <!-- (Code)names for various things used all over the place -->
 <!ENTITY debian-oldstable-codename "stretch">
diff --git a/doc/po/apt-doc.pot b/doc/po/apt-doc.pot
index c0ec085..d143d0a 100644
--- a/doc/po/apt-doc.pot
+++ b/doc/po/apt-doc.pot
@@ -5,9 +5,9 @@
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: apt-doc 1.8.2.1\n"
+"Project-Id-Version: apt-doc 1.8.2.2\n"
 "Report-Msgid-Bugs-To: APT Development Team <deity@lists.debian.org>\n"
-"POT-Creation-Date: 2020-05-12 18:00+0000\n"
+"POT-Creation-Date: 2020-12-02 17:16+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
diff --git a/po/apt-all.pot b/po/apt-all.pot
index 8d005b8..373c405 100644
--- a/po/apt-all.pot
+++ b/po/apt-all.pot
@@ -5,9 +5,9 @@
 #, fuzzy
 msgid ""
 msgstr ""
-"Project-Id-Version: apt 1.8.2.1\n"
+"Project-Id-Version: apt 1.8.2.2\n"
 "Report-Msgid-Bugs-To: APT Development Team <deity@lists.debian.org>\n"
-"POT-Creation-Date: 2020-05-12 18:00+0000\n"
+"POT-Creation-Date: 2020-12-02 17:16+0000\n"
 "PO-Revision-Date: YEAR-MO-DA HO:MI+ZONE\n"
 "Last-Translator: FULL NAME <EMAIL@ADDRESS>\n"
 "Language-Team: LANGUAGE <LL@li.org>\n"
diff --git a/test/integration/test-cve-2020-27350 b/test/integration/test-cve-2020-27350
new file mode 100755
index 0000000..a32bf95
--- /dev/null
+++ b/test/integration/test-cve-2020-27350
@@ -0,0 +1,25 @@
+#!/bin/sh
+set -e
+
+TESTDIR="$(readlink -f "$(dirname "$0")")"
+. "$TESTDIR/framework"
+setupenvironment
+configarchitecture "amd64"
+
+${APTTESTHELPERSBINDIR}/createdeb-cve-2020-27350 crash crash.deb
+testequal "E: Invalid archive member header" runapt ${APTTESTHELPERSBINDIR}/testdeb ./crash.deb
+
+${APTTESTHELPERSBINDIR}/createdeb-cve-2020-27350 loop loop.deb
+testequal "E: Invalid archive member header" runapt ${APTTESTHELPERSBINDIR}/testdeb ./loop.deb
+
+${APTTESTHELPERSBINDIR}/createdeb-cve-2020-27350 long-name long-name.deb
+testequal "E: Long name to large: 67108865 bytes > 1048576 bytes" runapt ${APTTESTHELPERSBINDIR}/extract-control long-name.deb control
+
+${APTTESTHELPERSBINDIR}/createdeb-cve-2020-27350 long-link long-link.deb
+testequal "E: Long name to large: 67108865 bytes > 1048576 bytes" runapt ${APTTESTHELPERSBINDIR}/extract-control long-link.deb control
+
+${APTTESTHELPERSBINDIR}/createdeb-cve-2020-27350 long-control long-control.deb
+testequal "E: Control file too large: 67108865 > 67108864 bytes" runapt ${APTTESTHELPERSBINDIR}/extract-control long-control.deb control
+
+${APTTESTHELPERSBINDIR}/createdeb-cve-2020-27350 too-long-control too-long-control.deb
+testequal "E: Tar member too large: $((128 * 1024 * 1024 * 1024 + 1)) > $((128 * 1024 * 1024 * 1024)) bytes" runapt ${APTTESTHELPERSBINDIR}/extract-control too-long-control.deb control
diff --git a/test/integration/test-github-111-invalid-armember b/test/integration/test-github-111-invalid-armember
index ec2163b..1e095ee 100755
--- a/test/integration/test-github-111-invalid-armember
+++ b/test/integration/test-github-111-invalid-armember
@@ -10,19 +10,19 @@ setupaptarchive
 # this used to crash, but it should treat it as an invalid member header
 touch ' '
 ar -q test.deb ' '
-testsuccessequal "E: Invalid archive member header" ${BUILDDIRECTORY}/../test/interactive-helper/testdeb test.deb
+testsuccessequal "E: Invalid archive member header" ${APTTESTHELPERSBINDIR}/testdeb test.deb
 
 
 rm test.deb
 touch 'x'
 ar -q test.deb 'x'
-testsuccessequal "E: This is not a valid DEB archive, missing 'debian-binary' member" ${BUILDDIRECTORY}/../test/interactive-helper/testdeb test.deb
+testsuccessequal "E: This is not a valid DEB archive, missing 'debian-binary' member" ${APTTESTHELPERSBINDIR}/testdeb test.deb
 
 
 # <name><size> [ other fields] - name is not nul terminated here, it ends in .
 msgmsg "Unterminated ar member name"
 printf '!<arch>\0120123456789ABCDE.A123456789A.01234.01234.0123456.012345678.0.' > test.deb
-testsuccessequal "E: Invalid archive member header" ${BUILDDIRECTORY}/../test/interactive-helper/testdeb test.deb
+testsuccessequal "E: Invalid archive member header" ${APTTESTHELPERSBINDIR}/testdeb test.deb
 
 
 # unused source code for generating $tar below
@@ -85,4 +85,4 @@ cp control.tar.gz data.tar.gz
 touch debian-binary
 rm test.deb
 ar -q test.deb debian-binary control.tar.gz data.tar.gz
-testsuccessequal "W: Unknown TAR header type 88" ${BUILDDIRECTORY}/../test/interactive-helper/testdeb test.deb
+testsuccessequal "W: Unknown TAR header type 88" ${APTTESTHELPERSBINDIR}/testdeb test.deb
diff --git a/test/interactive-helper/CMakeLists.txt b/test/interactive-helper/CMakeLists.txt
index 5a32ca1..0de7d9c 100644
--- a/test/interactive-helper/CMakeLists.txt
+++ b/test/interactive-helper/CMakeLists.txt
@@ -10,6 +10,8 @@ add_executable(aptdropprivs aptdropprivs.cc)
 target_link_libraries(aptdropprivs apt-pkg)
 add_executable(test_fileutl test_fileutl.cc)
 target_link_libraries(test_fileutl apt-pkg)
+add_executable(createdeb-cve-2020-27350 createdeb-cve-2020-27350.cc)
+
 
 add_library(noprofile SHARED libnoprofile.c)
 target_link_libraries(noprofile ${CMAKE_DL_LIBS})
diff --git a/test/interactive-helper/createdeb-cve-2020-27350.cc b/test/interactive-helper/createdeb-cve-2020-27350.cc
new file mode 100644
index 0000000..8b96194
--- /dev/null
+++ b/test/interactive-helper/createdeb-cve-2020-27350.cc
@@ -0,0 +1,325 @@
+#include <errno.h>
+#include <fcntl.h>
+#include <stdint.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <sys/stat.h>
+#include <sys/types.h>
+#include <unistd.h>
+
+struct Header
+{
+   char Name[16];
+   char MTime[12];
+   char UID[6];
+   char GID[6];
+   char Mode[8];
+   char Size[10];
+   char Magic[2];
+};
+
+struct TarHeader
+{
+   char Name[100];
+   char Mode[8];
+   char UserID[8];
+   char GroupID[8];
+   char Size[12];
+   char MTime[12];
+   char Checksum[8];
+   char LinkFlag;
+   char LinkName[100];
+   char MagicNumber[8];
+   char UserName[32];
+   char GroupName[32];
+   char Major[8];
+   char Minor[8];
+};
+
+// Call `write` and check the result.
+static void write_chk(int fd, const void *buf, size_t count)
+{
+   const ssize_t wr = write(fd, buf, count);
+   if (wr < 0)
+   {
+      const int err = errno;
+      fprintf(stderr, "Write failed: %s\n", strerror(err));
+      exit(EXIT_FAILURE);
+   }
+   if ((size_t)wr != count)
+   {
+      fprintf(stderr, "Incomplete write.\n");
+      exit(EXIT_FAILURE);
+   }
+}
+
+// Triggers a negative integer overflow at https://git.launchpad.net/ubuntu/+source/apt/tree/apt-pkg/contrib/arfile.cc?h=applied/ubuntu/focal-updates&id=4c264e60b524855b211751e1632ba48526f6b44d#n116:
+//
+//    Memb->Size -= Len;
+//
+// Due to the integer overflow, the value of Memb->Size is 0xFFFFFFFFFFFFFFFF.
+// This leads to an out-of-memory error at https://git.launchpad.net/ubuntu/+source/python-apt/tree/python/arfile.cc?h=applied/ubuntu/focal-updates&id=0f7cc93acdb51d943114f1cd79002288c4ca4d24#n602:
+//
+//    char* value = new char[member->Size];
+//
+// The out-of-memory error causes aptd to crash.
+static void createdeb_crash(const int fd)
+{
+   // Magic number
+   static const char *magic = "!<arch>\n";
+   write_chk(fd, magic, strlen(magic));
+
+   struct Header h;
+   memset(&h, 0, sizeof(h));
+
+   memcpy(h.Name, "control.tar     ", sizeof(h.Name));
+   write_chk(fd, &h, sizeof(h));
+
+   memset(&h, 0, sizeof(h));
+   memcpy(h.Name, "data.tar        ", sizeof(h.Name));
+   write_chk(fd, &h, sizeof(h));
+
+   memset(&h, 0, sizeof(h));
+   memcpy(h.Name, "#1/13           ", sizeof(h.Name));
+   strcpy(h.Size, "12");
+   write_chk(fd, &h, sizeof(h));
+   write_chk(fd, "debian-binary", 13);
+}
+
+// Triggers an infinite loop in `ARArchive::LoadHeaders()`.
+// The bug is due to the use of `strtoul` at https://git.launchpad.net/ubuntu/+source/apt/tree/apt-pkg/contrib/strutl.cc?h=applied/ubuntu/focal-updates&id=4c264e60b524855b211751e1632ba48526f6b44d#n1169:
+//
+//    Res = strtoul(S,&End,Base);
+//
+// The problem is that `strtoul` accepts negative numbers. We exploit that here by setting the size string to "-60".
+static void createdeb_loop(const int fd)
+{
+   // Magic number
+   static const char *magic = "!<arch>\n";
+   write_chk(fd, magic, strlen(magic));
+
+   struct Header h;
+   memset(&h, 0, sizeof(h));
+
+   memcpy(h.Name, "#1/20           ", sizeof(h.Name));
+   strcpy(h.Size, "-60");
+   write_chk(fd, &h, sizeof(h));
+
+   char buf[20];
+   memset(buf, 0, sizeof(buf));
+   write_chk(fd, buf, sizeof(buf));
+}
+
+// Leaks a file descriptor in `debfile_new()`:
+//
+// https://git.launchpad.net/python-apt/tree/python/arfile.cc?h=2.0.0#n588
+//
+// If the .deb file is invalid then the function returns without deleting
+// `self`, which means that the file descriptor isn't closed.
+static void createdeb_leakfd(const int fd)
+{
+   // Magic number
+   static const char *magic = "!<arch>\n";
+   write_chk(fd, magic, strlen(magic));
+
+   struct Header h;
+   memset(&h, 0, sizeof(h));
+
+   memset(&h, 0, sizeof(h));
+   memcpy(h.Name, "data.tar        ", sizeof(h.Name));
+   write_chk(fd, &h, sizeof(h));
+}
+
+static void set_checksum(unsigned char block[512])
+{
+   struct TarHeader *tar = (struct TarHeader *)&block[0];
+   memset(tar->Checksum, ' ', sizeof(tar->Checksum));
+   uint32_t sum = 0;
+   for (int i = 0; i < 512; i++)
+   {
+      sum += block[i];
+   }
+   snprintf(tar->Checksum, sizeof(tar->Checksum), "%o", sum);
+}
+static void base256_encode(char *Str, unsigned long long Num, unsigned int Len)
+{
+   Str += Len;
+   while (Len-- > 0) {
+      *--Str = static_cast<char>(Num & 0xff);
+      Num >>= 8;
+   }
+
+  *Str |= 0x80; // mark as base256
+}
+
+// Create a deb with a control.tar that contains a too large file or link name (GNU extension)
+static void createdeb_bigtarfilelength(const int fd, int flag, unsigned long long size = 64llu * 1024 * 1024 + 1)
+{
+   // Magic number
+   static const char *magic = "!<arch>\n";
+   write_chk(fd, magic, strlen(magic));
+
+   struct Header h;
+   memset(&h, ' ', sizeof(h));
+   memcpy(h.Name, "debian-binary/     ", sizeof(h.Name));
+   h.MTime[0] = '0';
+   h.UID[0] = '0';
+   h.GID[0] = '0';
+   memcpy(h.Mode, "644", 3);
+   h.Size[0] = '0';
+   memcpy(h.Magic, "`\n", 2);
+
+   write_chk(fd, &h, sizeof(h));
+
+   memset(&h, ' ', sizeof(h));
+   memcpy(h.Name, "data.tar/       ", sizeof(h.Name));
+   h.MTime[0] = '0';
+   h.UID[0] = '0';
+   h.GID[0] = '0';
+   memcpy(h.Mode, "644", 3);
+   h.Size[0] = '0';
+   memcpy(h.Magic, "`\n", 2);
+
+   write_chk(fd, &h, sizeof(h));
+
+   memset(&h, ' ', sizeof(h));
+   memcpy(h.Name, "control.tar/       ", sizeof(h.Name));
+   h.MTime[0] = '0';
+   h.UID[0] = '0';
+   h.GID[0] = '0';
+   memcpy(h.Mode, "644", 3);
+   memcpy(h.Size, "512", 3);
+   memcpy(h.Magic, "`\n", 2);
+
+   write_chk(fd, &h, sizeof(h));
+   union
+   {
+      struct TarHeader t;
+      unsigned char buf[512];
+   } t;
+   for (unsigned int i = 0; i < sizeof(t.buf); i++)
+      t.buf[i] = '7';
+   memcpy(t.t.Name, "control\0        ", 16);
+   memcpy(t.t.UserName, "userName", 8);
+   memcpy(t.t.GroupName, "thisIsAGroupNamethisIsAGroupName", 32);
+   t.t.LinkFlag = flag;
+   base256_encode(t.t.Size, size, sizeof(t.t.Size));
+   memset(t.t.Checksum, ' ', sizeof(t.t.Checksum));
+
+   unsigned long sum = 0;
+   for (unsigned int i = 0; i < sizeof(t.buf); i++)
+      sum += t.buf[i];
+
+   int written = sprintf(t.t.Checksum, "%lo", sum);
+   for (unsigned int i = written; i < sizeof(t.t.Checksum); i++)
+      t.t.Checksum[i] = ' ';
+
+   write_chk(fd, t.buf, sizeof(t.buf));
+}
+
+static void createdeb_test(const int fd)
+{
+   // Magic number
+   static const char *magic = "!<arch>\n";
+   write_chk(fd, magic, strlen(magic));
+
+   struct Header h;
+
+   memset(&h, 0, sizeof(h));
+   memcpy(h.Name, "data.tar        ", sizeof(h.Name));
+   write_chk(fd, &h, sizeof(h));
+
+   memset(&h, 0, sizeof(h));
+   memcpy(h.Name, "debian-binary   ", sizeof(h.Name));
+   strcpy(h.Size, "4");
+   write_chk(fd, &h, sizeof(h));
+   static const char *debian_binary = "2.0\n";
+   write_chk(fd, debian_binary, strlen(debian_binary));
+
+   static const char *control =
+      "Architecture: all\n"
+      "Package: kevsh\n\n";
+   memset(&h, 0, sizeof(h));
+   memcpy(h.Name, "control.tar     ", sizeof(h.Name));
+   snprintf(h.Size, sizeof(h.Size), "%ld", (size_t)512 + 512);
+   write_chk(fd, &h, sizeof(h));
+
+   unsigned char block[512];
+   memset(block, 0, sizeof(block));
+   struct TarHeader *tar = (struct TarHeader *)&block[0];
+   strcpy(tar->Name, "control");
+   strcpy(tar->Mode, "644");
+   snprintf(tar->Size, sizeof(tar->Size), "%lo", strlen(control));
+   set_checksum(block);
+   write_chk(fd, block, sizeof(block));
+
+   memset(block, 0, sizeof(block));
+   strcpy((char *)block, control);
+   write_chk(fd, block, sizeof(block));
+}
+
+int main(int argc, char *argv[])
+{
+   if (argc != 3)
+   {
+      const char *progname = argc > 0 ? argv[0] : "a.out";
+      fprintf(
+	 stderr,
+	 "usage: %s <mode> <filename>\n"
+	 "modes: loop, segv, leakfd\n",
+	 progname);
+      return EXIT_FAILURE;
+   }
+
+   const char *mode = argv[1];
+   const char *filename = argv[2];
+
+   const int fd = open(filename, O_CREAT | O_WRONLY | O_TRUNC, 00644);
+   if (fd < 0)
+   {
+      const int err = errno;
+      fprintf(stderr, "Could not open %s: %s\n", filename, strerror(err));
+      return EXIT_FAILURE;
+   }
+
+   if (strcmp(mode, "crash") == 0)
+   {
+      createdeb_crash(fd);
+   }
+   else if (strcmp(mode, "loop") == 0)
+   {
+      createdeb_loop(fd);
+   }
+   else if (strcmp(mode, "leakfd") == 0)
+   {
+      createdeb_leakfd(fd);
+   }
+   else if (strcmp(mode, "long-name") == 0)
+   {
+      createdeb_bigtarfilelength(fd, 'L');
+   }
+   else if (strcmp(mode, "long-link") == 0)
+   {
+      createdeb_bigtarfilelength(fd, 'K');
+   }
+   else if (strcmp(mode, "long-control") == 0)
+   {
+      createdeb_bigtarfilelength(fd, '0');
+   }
+   else if (strcmp(mode, "too-long-control") == 0)
+   {
+      createdeb_bigtarfilelength(fd, '0', 128llu * 1024 * 1024 * 1024 + 1);
+   }
+   else if (strcmp(mode, "test") == 0)
+   {
+      createdeb_test(fd);
+   }
+   else
+   {
+      fprintf(stderr, "Mode not recognized: %s\n", mode);
+   }
+
+   close(fd);
+   return EXIT_SUCCESS;
+}
-- 
GitLab