Skip to content

Conversation

@letFunny
Copy link
Collaborator

  • Have you signed the CLA?

This commits adds several security controls and tests so that files
cannot be extracted outside of the target directory, and hardlinks
cannot point to files outside of the target directory.

This commits adds several security controls and tests so that files
cannot be extracted outside of the target directory, and hardlinks
cannot point to files outside of the target directory.
@github-actions
Copy link

github-actions bot commented Feb 14, 2025

Command Mean [s] Min [s] Max [s] Relative
BASE 8.235 ± 0.042 8.189 8.333 1.00
HEAD 8.551 ± 0.027 8.516 8.598 1.04 ± 0.01

}},
},
},
error: `cannot extract from package "test-package": cannot create path /[a-z0-9\-\/]*/file outside of root /[a-z0-9\-\/]*`,
Copy link
Contributor

Choose a reason for hiding this comment

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

/[a-z0-9-/]*/file

What is this path? Is it to hide the root?

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Yes indeed

Copy link
Contributor

Choose a reason for hiding this comment

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

To be clear, this is not hiding the root. This is a regular expression that is used for matching. The actual message will hold the real paths.

Comment on lines 16 to 17
// Root defaults to "/" if empty.
Root string
Copy link
Contributor

Choose a reason for hiding this comment

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

Won't that be a bit risky to have / as the default? What if Chisel overwrites the files in the system?

How about /var/lib/chisel or $PWD? The snap may not be able to write to the former.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

This is the root of the filesystem, I am not sure we can provide a good default as users may choose whatever folder they wish. But, more importantly, I put the root as / because this is a general API and, in my opinion, the default shouldn't correspond to one use-case.

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree having the default in an arbitrary place doesn't seem natural, but it does seem safer to force it to be defined. This way we'll avoid errors created by missing that separation of concerns, which actually could result in security bugs. All it takes is a Root: "/" to get through it when that's what's desired.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Done in da20063, now Root is required.

Copy link
Contributor

@niemeyer niemeyer left a comment

Choose a reason for hiding this comment

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

Very nicely done, thanks. Only minor tuning suggested:

Comment on lines 16 to 17
// Root defaults to "/" if empty.
Root string
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree having the default in an arbitrary place doesn't seem natural, but it does seem safer to force it to be defined. This way we'll avoid errors created by missing that separation of concerns, which actually could result in security bugs. All it takes is a Root: "/" to get through it when that's what's desired.

if o.Root != "/" {
o.Root = filepath.Clean(o.Root) + "/"
}
o.Path = filepath.Clean(filepath.Join(o.Root, o.Path))
Copy link
Contributor

Choose a reason for hiding this comment

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

This is unexpected as it means we're mutating data to be able to use the feature at all. Worse: if we happen to "clean" this same options object twice we'll get a corrupt value. I suggest having a method like abspath() (string, error) which returns the absolute path after doing the required verifications.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Agree that a clean function that avoids mutation and can be called twice is preferred. If I understood your suggestion correctly I have done it as part of da20063.

// No mode parameter for now as slices are supposed to list files
// explicitly instead.
entry, err := fsutil.Create(&fsutil.CreateOptions{
Root: "/",
Copy link
Collaborator Author

Choose a reason for hiding this comment

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

[Comment for reviewer]: There is some complexity in how we handle paths above for Starlark. The function gets the fpath and follows symlinks among other things, I am hesitant to change it without a more comprehensive refactor which I don't think should happen in this PR.

if err != nil {
return nil, err
}
o.Path = path
Copy link
Contributor

Choose a reason for hiding this comment

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

This is still doing the same thing that was raised in the prior review: mutating the options in place, and changing the meaning of Root and Path. If we change what Path points to so that it includes Root, and we pass this same Options to this same function again, we'll then have the Root joined twice on the same path.

The suggestion was to use an actual abspath() method that would do that, and return the final path after error checking. If that's bad or inconvenient for the actual use cases, let's please talk about it.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Using the method instead of changing the options means we have to push abspath and the validation to each leaf function individually (create file, create symlink, create directory, etc.) I will do the changes and see if there is redundancy. The biggest sticking point for me is about moving the logic in the other comment, i.e. sanitize the root and add the trailing /, to each function but I understand that is not what we want. We just want to push the path function and we can leave the validation outside.

Copy link
Collaborator Author

Choose a reason for hiding this comment

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

Discussed offline, this is about changing the meaning of Root and Path. I have incorporated changes in 8046cf5 so that Root and Path are respected and the helper is used absPath, this also makes the transformations idempotent.

return nil, nil, fmt.Errorf("root cannot be empty")
}
if o.Root != "/" {
o.Root = filepath.Clean(o.Root) + "/"
Copy link
Contributor

Choose a reason for hiding this comment

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

And this is repeating the same logic.

Copy link
Contributor

@niemeyer niemeyer left a comment

Choose a reason for hiding this comment

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

Thanks, looking good now!

@niemeyer niemeyer merged commit 9ac6134 into canonical:main Mar 31, 2025
18 checks passed
@letFunny letFunny deleted the security-check-root-creating-files branch April 30, 2025 08:07
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