Skip to content
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

#6981: fs_permissions find permission also sym links #7022

Merged
merged 4 commits into from
Feb 9, 2024

Conversation

grandizzy
Copy link
Collaborator

Motivation

see #6981

Solution

  • resolve configured fs_permissions symlinks paths when finding permissions

Tests

  • could write a test for unix family that creates sym link in temp dir and check permissions, didn't find this approach in project, pls advise

Copy link
Member

@onbjerg onbjerg left a comment

Choose a reason for hiding this comment

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

I'd like to see some x-platform tests on this because Path::canonicalize also transforms paths into the abysmal Windows extended path format which has historically messed up a lot of path-related things in Foundry. For example, the directory separator becomes a backslash instead of a forward slash, which might mean this actually breaks on Windows.

You can see some examples of integration tests that set up foundry.toml and projects in https://github.com/foundry-rs/foundry/tree/master/crates/forge/tests/cli

@onbjerg onbjerg added the T-bug Type: bug label Feb 6, 2024
Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

I'd like to see an additional non-windows test for this

crates/config/src/fs_permissions.rs Outdated Show resolved Hide resolved
- for default fs permissions config
- for parsing custom fs permissions
- to resolve symlink permissions
@grandizzy
Copy link
Collaborator Author

I'd like to see an additional non-windows test for this

added here 250bbc5#diff-289d6a2d36ab0e59edc4f4baeeaa5026ecae1002e13a5fc80e033e46d612bc7bR676

@grandizzy
Copy link
Collaborator Author

I'd like to see some x-platform tests on this because Path::canonicalize also transforms paths into the abysmal Windows extended path format which has historically messed up a lot of path-related things in Foundry. For example, the directory separator becomes a backslash instead of a forward slash, which might mean this actually breaks on Windows.

You can see some examples of integration tests that set up foundry.toml and projects in https://github.com/foundry-rs/foundry/tree/master/crates/forge/tests/cli

thanks for hint, I added couple of them (some mimics the unit tests) with 250bbc5, using dunce::canonicalize as suggested should address further concerns

Copy link
Member

@onbjerg onbjerg left a comment

Choose a reason for hiding this comment

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

lgtm, pending @mattsse

Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

can you elaborate on the fix, unclear why we need to do this

we also want an actual solidity test example, see repro in the issue

Comment on lines +57 to +58
let permission_path = dunce::canonicalize(&perm.path).unwrap_or(perm.path.clone());
if path.starts_with(permission_path) {
Copy link
Member

Choose a reason for hiding this comment

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

I'm actually not sure why this is the right fix, because the path should already be canonicalized here

/// Returns true if the given path is allowed, if any path `allowed_paths` is an ancestor of the
/// path
///
/// We only allow paths that are inside allowed paths. To prevent path traversal
/// ("../../etc/passwd") we canonicalize/normalize the path first. We always join with the
/// configured root directory.
pub fn is_path_allowed(&self, path: impl AsRef<Path>, kind: FsAccessKind) -> bool {
self.is_normalized_path_allowed(&self.normalized_path(path), kind)
}

Copy link
Collaborator Author

@grandizzy grandizzy Feb 8, 2024

Choose a reason for hiding this comment

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

I'm actually not sure why this is the right fix, because the path should already be canonicalized here

/// Returns true if the given path is allowed, if any path `allowed_paths` is an ancestor of the
/// path
///
/// We only allow paths that are inside allowed paths. To prevent path traversal
/// ("../../etc/passwd") we canonicalize/normalize the path first. We always join with the
/// configured root directory.
pub fn is_path_allowed(&self, path: impl AsRef<Path>, kind: FsAccessKind) -> bool {
self.is_normalized_path_allowed(&self.normalized_path(path), kind)
}

that's the normalized path to the file desired to be loaded in test (by using vm.readFile), mind that is passed to is_normalized_path_allowed function which checks it against configured fs_permissions path (that are not normalized)

pub fn is_path_allowed(&self, path: impl AsRef<Path>, kind: FsAccessKind) -> bool {
self.is_normalized_path_allowed(&self.normalized_path(path), kind)
}
fn is_normalized_path_allowed(&self, path: &Path, kind: FsAccessKind) -> bool {
self.fs_permissions.is_path_allowed(path, kind)
}

Copy link
Member

Choose a reason for hiding this comment

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

ah now I see, it, we're canonicalizing the permission path, got it ty!

@grandizzy
Copy link
Collaborator Author

grandizzy commented Feb 8, 2024

can you elaborate on the fix, unclear why we need to do this

sure, so using repo in #6981 with following structure (mind that the config.json file to be read in foundry test is outside foundry repo)

foundry-symlink-issue-example/
├── package.json
├── packages
│   ├── contracts
│   │   ├── foundry.toml
│   │   ├── node_modules
│   │   │   └── @repo
│   │   │       └── imported -> ../../../imported
│   │   ├── package.json
│   │   ├── remappings.txt
│   │   ├── test
│   │   │   └── ImportTest.t.sol
│   │   └── yarn.lock
│   └── imported
│       ├── files
│       │   └── config.json
│       └── package.json
├── README.md
└── yarn.lock

with configured permission

fs_permissions = [
  { access = "read", path = "./node_modules/@repo/imported/files/config.json" },
]

when trying to read the file as

        string memory jsonString = vm.readFile(
            "node_modules/@repo/imported/files/config.json"
        );

the "node_modules/@repo/imported/files/config.json" path is joined with root dir and normalized in is_path_allowed hence transformed to "~/foundry-symlink-issue-example/packages/imported/files/config.json". Then this path is checked for permissions (by checking if it starts with any configured permission paths, but this one not canonicalized)

/// Returns the permission for the matching path.
///
/// This finds the longest matching path, e.g. if we have the following permissions:
///
/// `./out` = `read`
/// `./out/contracts` = `read-write`
///
/// And we check for `./out/contracts/MyContract.sol` we will get `read-write` as permission.
pub fn find_permission(&self, path: &Path) -> Option<FsAccessPermission> {
let mut permission: Option<&PathPermission> = None;
for perm in &self.permissions {
if path.starts_with(&perm.path) {

so comparing if ~/foundry-symlink-issue-example/packages/imported/files/config.json starts with
~/foundry-symlink-issue-example/packages/contracts/./node_modules/@repo/imported/files/config.json which is false

Proposed fix is to normalize the configured path = "./node_modules/@repo/imported/files/config.json" to "~/foundry-symlink-issue-example/packages/imported/files/config.json"

we also want an actual solidity test example, see repro in the issue

I made the tests against repo provided in #6981 (which I mimicked in can_resolve_symlink_fs_permissions test) , can add solidity in tests too

Copy link
Member

@mattsse mattsse left a comment

Choose a reason for hiding this comment

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

thanks for explaining, now I got it

lgtm

Comment on lines +57 to +58
let permission_path = dunce::canonicalize(&perm.path).unwrap_or(perm.path.clone());
if path.starts_with(permission_path) {
Copy link
Member

Choose a reason for hiding this comment

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

ah now I see, it, we're canonicalizing the permission path, got it ty!

@mattsse mattsse merged commit a1fc146 into foundry-rs:master Feb 9, 2024
20 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
T-bug Type: bug
Projects
None yet
Development

Successfully merging this pull request may close these issues.

3 participants