fix: canonicalize include paths to prevent directory traversal (CWE-22)#45
Open
0xDanielSec wants to merge 1 commit intogoogle:masterfrom
Open
fix: canonicalize include paths to prevent directory traversal (CWE-22)#450xDanielSec wants to merge 1 commit intogoogle:masterfrom
0xDanielSec wants to merge 1 commit intogoogle:masterfrom
Conversation
includes_resolve() previously passed the raw output of path_join() directly to fopen(), allowing filenames containing ../ sequences to escape the registered search directories. Call realpath() on the joined path before opening it, then verify the canonical result shares a prefix with the canonical search directory. Reject the include if the resolved path falls outside that boundary. Fixes CWE-22 (Path Traversal).
|
Thanks for your pull request! It looks like this may be your first contribution to a Google open source project. Before we can look at your pull request, you'll need to sign a Contributor License Agreement (CLA). View this failed invocation of the CLA check for more information. For the most up to date status, view the checks section at the bottom of the pull request. |
Collaborator
|
Thank you 0xDanielSec. I do not think it's within our threat model - ie. this "attack" doesn't cross any privilege boundaries. @happyCoder92 wdyt? |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Add this suggestion to a batch that can be applied as a single commit.This suggestion is invalid because no changes were made to the code.Suggestions cannot be applied while the pull request is closed.Suggestions cannot be applied while viewing a subset of changes.Only one suggestion per line can be applied in a batch.Add this suggestion to a batch that can be applied as a single commit.Applying suggestions on deleted lines is not supported.You must change the existing code in this line in order to create a valid suggestion.Outdated suggestions cannot be applied.This suggestion has been applied or marked resolved.Suggestions cannot be applied from pending reviews.Suggestions cannot be applied on multi-line comments.Suggestions cannot be applied while the pull request is queued to merge.Suggestion cannot be applied right now. Please check back later.
Bug
includes_resolve() in src/includes.c passes the raw output of
path_join() directly to fopen() without validating that the
resulting path stays within the intended search directory.
path_join() is a simple string concatenation — it does not strip or
resolve ../ sequences. An attacker who controls the content of a
kafel policy file can embed a path traversal sequence in an #include
directive to open arbitrary files readable by the process.
Reproduction
Given a search path /var/lib/policies and a policy file containing:
#include "../../../etc/shadow"
path_join() produces /var/lib/policies/../../../etc/shadow. The OS
resolves this to /etc/shadow at fopen() time. If the file is
readable by the process, the lexer opens and begins parsing it. While
the content is not echoed back verbatim, the attack enables:
error confirms whether arbitrary paths exist.
first unexpected token and its line/column, leaking content fragments.
causes the lexer to block or spin.
points outside it is also accepted by the unpatched code.
The MAX_INCLUDE_DEPTH guard (16 levels) does not mitigate this; it
only prevents unbounded recursion.
Affected versions: all versions since include support was added
(commit 8724cf5, Sep 2018).
Fix
After path_join(), call realpath(path, NULL) to obtain the
canonical absolute path with all ../, ./, and symlink components
resolved. Then call realpath() on the search directory itself and
verify the canonical file path has the canonical search path as a
proper prefix (guarding the boundary with a / or \0 check to
prevent /foo/bar from matching /foo/barbaz). Reject the include
and continue to the next search path if the check fails.
If realpath() returns NULL (file does not exist, or a path
component is inaccessible), the search path is skipped — identical
behavior to a failed fopen() in the original code.
The realpath() return value is malloc-allocated; ownership is
transferred to the used_paths list, which includes_ctxt_clean()
already frees, so there is no change to memory ownership semantics.
Changed files
struct changes, no new dependencies (realpath is POSIX.1-2008;
stdlib.h and string.h were already included).
Security impact
Exploitability requires an attacker to control the content of a policy
file passed to kafel_compile() or kafel_compile_file(). The typical
deployment (policy authored by the application developer at build time)
is not directly affected. However, any application that compiles
user-supplied or externally-fetched kafel policies — including
policy-as-a-service tools, container runtimes, or test harnesses — is
at risk.