Skip to content

Conversation

sean-breen
Copy link
Contributor

@sean-breen sean-breen commented Sep 16, 2025

Proposed changes

Stops isAllowedDir function from matching by prefix. Originally this function would allow any path which was prefixed by an allowed directory, i.e

  • if /var/log/nginx was present in allowed dirs, then the path /var/log/nginx-test would also be allowed. Not good!

This PR stops this match from taking place, but still matches if the provided path is a subdirectory of an already allowed directory, i.e

  • if /var/log/nginx is in allowed dirs, /var/log/nginx/mysubdir will be allowed but /var/log/nginx-test will be blocked.

Checklist

Before creating a PR, run through this checklist and mark each as complete.

  • I have read the CONTRIBUTING document
  • I have run make install-tools and have attached any dependency changes to this pull request
  • If applicable, I have added tests that prove my fix is effective or that my feature works
  • If applicable, I have checked that any relevant tests pass after adding my changes
  • If applicable, I have updated any relevant documentation (README.md)
  • If applicable, I have tested my cross-platform changes on Ubuntu 22, Redhat 8, SUSE 15 and FreeBSD 13

@sean-breen sean-breen requested a review from a team as a code owner September 16, 2025 11:05
@github-actions github-actions bot added bug Something isn't working chore Pull requests for routine tasks labels Sep 16, 2025
Comment on lines +506 to +507
if err != nil {
if !os.IsNotExist(err) {
Copy link
Member

Choose a reason for hiding this comment

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

As said above, I think this whole Stat and err handling isn't useful, but if I'm missing the reason then can we please handle the errors as one block rather than as three separate ifs, one of which is needlessly nested?

switch {
  case os.IsNotExists(err):  // nothing to do so why did we do this?
  case err != nil:
     return false, fmt.Errorf("PathError %s %w", path, err) // disk i/o error surfaced in a weird location
  case !info.IsDir():
     // we don't trust our code to handle files that _do_ exist, so get the parent directory
     path = filepath.Dir(path)
}

if strings.HasPrefix(directoryPath, allowedDirectory) {
for _, dir := range allowedDirs {
// Check if the path is a direct match, or is a subdirectory of the allowed directory
if slices.Contains(allowedDirs, path) || strings.HasPrefix(path, dir+"/") {
Copy link
Member

Choose a reason for hiding this comment

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

I don't think this is a good check

  • for dir in alloweddirs, do a slices.Contains on the alloweddirs list; this is needlessly O(n^2).
  • if the AllowedDirs list is ["/"] this is going to check if the path has the prefix //

Look at this implementation

We walk the given path up the tree ensuring that it is an exact match against the allowed prefix. we use strings.HasPrefix as a shortcircuit out -- e.g. /var/www2/foo/bar/bam/jack/ checked against /var/www exits out once it is comparing /var/www2 against /var/www. Thinking about it right now, that hasprefix shortcircuit might take more computation then just continuing to walk up the directory tree-- would need a benchmark to really evaluate.

See https://go.dev/play/p/tYMSZ6FbL31 as an example of walking up the tree

Comment on lines +499 to +505
// Does the path exist?
info, err := os.Stat(path)
if err == nil && !info.IsDir() {
// Path exists and is not a directory
// It's a file so get the parent directory
path = filepath.Dir(path)
}
Copy link
Member

Choose a reason for hiding this comment

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

Why do we think this stat and parent is useful?

If this function is called with isAllowedDir("/etc/nginx/non-existant-file.conf", ["/etc/nginx"]) then it will skip this branch and needs to work.

If this function is called with isAllowedDir("/etc/nginx/exists.conf", ["/etc/nginx"]) then it will be converted to "/etc/nginx" and it needs to work.

What are we gaining on this Stat and IsDir check? The only case I can think of is to try and catch a case where the file does exist and this process doesn't have read permission to it. But that's not the purpose of this function, and surfacing the error here feels really odd...

"/etc/nginx",
},
filePath: "/etc/nginx-test/nginx.conf",
},
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
},
},
{
name: "Test 8: allow root should allow all",
allowed: true,
allowedDirs: []string{"/"},
filePath: "/etc/nginx-test/nginx.conf",
},

AllowedDirectories: []string{
"/etc/nginx/", "/etc/nginx-agent/", "/usr/local/etc/nginx/", "/var/run/nginx/", "/var/log/nginx/",
"/usr/share/nginx/modules/", "/etc/app_protect/",
"/etc/nginx/", "/etc/nginx-agent/", "/usr/local/etc/nginx", "/var/run/nginx", "/var/log/nginx",
Copy link
Member

Choose a reason for hiding this comment

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

Why are we removing the trailing slash on some of the paths in this test?

Copy link
Member

Choose a reason for hiding this comment

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

When I played with this briefly it looked like these paths were normlaized and had the trailing slash stripped as config was being parsed and processed.

This change is confusing though, why remove it from some and not all?

@sean-breen sean-breen marked this pull request as draft September 18, 2025 08:44
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
bug Something isn't working chore Pull requests for routine tasks
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants