Exploit-DB #40916
Description: APT - Repository Signing Bypass via Memory Allocation Failure
Type: remote
Platform: linux
Date: 2016-12-14
Author: Google Security Research

Exploit:
Source: https://bugs.chromium.org/p/project-zero/issues/detail?id=1020

== 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