Skip to content

Fix privileged fd leak in libpqos and symlink race in pqos safe_fopen - #312

Open
camuso wants to merge 3 commits into
intel:masterfrom
camuso:fix-fd-leak-and-symlink-race
Open

camuso wants to merge 3 commits into
intel:masterfrom
camuso:fix-fd-leak-and-symlink-race

Conversation

@camuso

@camuso camuso commented Aug 31, 2026

Copy link
Copy Markdown

Two security issues exist in intel-cmt-cat that allow local privilege
escalation or data corruption when the tools are exposed to non-root
users through sudo or similar delegation:

  1. libpqos opens MSR device files and the API lock file without
    O_CLOEXEC.
    These long-lived descriptors survive execve() and
    can be inherited by child processes that should not have access to
    MSR registers or the API lock.

  2. safe_fopen() in pqos is vulnerable to symlink-following file
    truncation.
    The current implementation calls lstat() to check
    for symlinks, then fopen() to open the file. With mode "w+",
    fopen() follows a pre-existing symlink and truncates the target
    before the post-open lstat/fstat comparison can detect the
    mismatch. No race condition is required — a pre-existing symlink
    is sufficient to truncate an arbitrary file.

The series is structured as follows:

  • Commit 1/2 adds O_CLOEXEC to long-lived descriptor opens in
    lib/machine.c (MSR device files) and lib/lock.c (API lock file)
    as defense-in-depth. These descriptors will be automatically closed
    on any execve() call.

  • Commit 2/2 replaces the lstat()+fopen() pattern in
    pqos/common.c:safe_fopen() with open(O_NOFOLLOW)+fdopen().
    This eliminates the symlink vulnerability entirely — the kernel
    rejects symlinks atomically at open time with ELOOP, so there is
    no window for truncation to occur.

Note: rdtset (removed in v26.06) also leaked inherited MSR file
descriptors to child processes after privilege drop. That issue is
no longer present on master since rdtset has been removed.

Tested on RHEL-10 and RHEL-9 with intel-cmt-cat 26.03 on Intel Xeon
Gold and Dell PowerEdge R660 platforms. The symlink test confirms
that on unpatched code, fopen("w+") truncates the symlink target;
on patched code, O_NOFOLLOW rejects it with ELOOP.

@rkanagar

rkanagar commented Sep 9, 2026

Copy link
Copy Markdown
Contributor

Thanks @camuso . I will merge this before next release.

Copilot AI left a comment

Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

🟡 Changes recommended

Symlinked parent components remain exploitable, file permissions regress, and existing lock tests will fail.

Once you've addressed the issues Copilot identified, you can request another Copilot review.

Pull request overview

Hardens privileged file descriptors and safe_fopen() against inheritance and symlink attacks.

Changes:

  • Adds O_CLOEXEC to MSR and API-lock descriptors.
  • Replaces lstat()/fopen() with open(O_NOFOLLOW)/fdopen().
File summaries
File Description
pqos/common.c Atomically rejects final-component symlinks.
lib/machine.c Prevents MSR descriptor inheritance.
lib/lock.c Prevents API-lock descriptor inheritance.
Review details
  • Files reviewed: 3/3 changed files
  • Comments generated: 4
  • Review effort level: Balanced

💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.

Comment thread pqos/common.c Outdated
else
return NULL;

fd = open(name, flags | O_NOFOLLOW, perms);
Comment thread lib/lock.c
Comment on lines +263 to +264
m_apilock =
open(LOCKFILE, O_RDWR | O_CREAT | O_EXCL | O_CLOEXEC, LOCKFILE_PERMS);
Comment thread pqos/common.c Outdated
return NULL;
else
new_file = 1;
mode_t perms = S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH; /* 0644 */
Comment thread pqos/common.c Outdated
Comment on lines +384 to +388
fd = open(name, flags | O_NOFOLLOW, perms);
if (fd == -1) {
if (errno == ELOOP)
printf("File %s is a symlink\n", name);
return NULL;
Add O_CLOEXEC to long-lived descriptor opens in lib/machine.c
(MSR device files) and lib/lock.c (API lock file) as defense-in-depth
against leaking privileged file descriptors across exec boundaries.

Without O_CLOEXEC, these descriptors survive execve() and can be
inherited by child processes that should not have access to MSR
registers or the API lock.

Signed-off-by: Tony Camuso <tcamuso@redhat.com>
Replace the lstat()+fopen() pattern in safe_fopen() with
open(O_NOFOLLOW)+fdopen().  The original code validates symlinks
only after fopen() has already opened and potentially truncated
the symlink target.  With fopen("w+"), a pre-existing symlink
causes the target to be truncated to zero bytes before the
post-open lstat/fstat comparison can detect the mismatch.

Using open() with O_NOFOLLOW makes the symlink rejection atomic:
the kernel returns ELOOP without opening or modifying anything.

Signed-off-by: Tony Camuso <tcamuso@redhat.com>
Add a new cmocka test suite exercising safe_fopen() from pqos/common.c:

  - NULL name / NULL mode / invalid mode → returns NULL
  - Regular file write+read round-trip
  - Symlink rejection: safe_fopen through a symlink returns NULL
    and the target file is not truncated (O_NOFOLLOW)
  - File permissions: newly created files get 0666 & ~umask,
    matching fopen() behaviour
  - Append mode: existing content is preserved

The test links only common.o (plus a stub for
pqos_get_num_mem_regions) so it is independent of the mock library
and builds cleanly with cmocka 2.x.

Signed-off-by: Tony Camuso <tcamuso@redhat.com>
@camuso

camuso commented Sep 14, 2026

Copy link
Copy Markdown
Author

@rkanagar
Will intel be issuing CVEs for these vulnerabilities, the two originally reported
and the additional one that CoPilot found?
I have devised patches for the additional vulnerability discovered by CoPilot and a fix for the problem introduced by my previous series.

@camuso
camuso force-pushed the fix-fd-leak-and-symlink-race branch from e558710 to c2acdc4 Compare September 14, 2026 13:43
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

3 participants