-
Notifications
You must be signed in to change notification settings - Fork 53
feat: cannot extract files outside the target directory #205
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
feat: cannot extract files outside the target directory #205
Conversation
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.
|
| }}, | ||
| }, | ||
| }, | ||
| error: `cannot extract from package "test-package": cannot create path /[a-z0-9\-\/]*/file outside of root /[a-z0-9\-\/]*`, |
There was a problem hiding this comment.
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?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes indeed
There was a problem hiding this comment.
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.
internal/fsutil/create.go
Outdated
| // Root defaults to "/" if empty. | ||
| Root string |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this 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:
internal/fsutil/create.go
Outdated
| // Root defaults to "/" if empty. | ||
| Root string |
There was a problem hiding this comment.
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.
internal/fsutil/create.go
Outdated
| if o.Root != "/" { | ||
| o.Root = filepath.Clean(o.Root) + "/" | ||
| } | ||
| o.Path = filepath.Clean(filepath.Join(o.Root, o.Path)) |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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: "/", |
There was a problem hiding this comment.
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.
internal/fsutil/create.go
Outdated
| if err != nil { | ||
| return nil, err | ||
| } | ||
| o.Path = path |
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
There was a problem hiding this comment.
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.
internal/fsutil/create.go
Outdated
| return nil, nil, fmt.Errorf("root cannot be empty") | ||
| } | ||
| if o.Root != "/" { | ||
| o.Root = filepath.Clean(o.Root) + "/" |
There was a problem hiding this comment.
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.
There was a problem hiding this 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!
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.