Skip to content

refactor: extract confined_join#7712

Open
erickguan wants to merge 3 commits into
mainfrom
fix/confine-compfs-monoiofs-root
Open

refactor: extract confined_join#7712
erickguan wants to merge 3 commits into
mainfrom
fix/confine-compfs-monoiofs-root

Conversation

@erickguan

Copy link
Copy Markdown
Member

Which issue does this PR close?

Complement to #7702. Merge after #7702

Rationale for this change

Better code cohesion and documentation.

What changes are included in this PR?

  • extraction of confined_join

Are there any user-facing changes?

None

AI Usage Statement

No

@erickguan erickguan requested a review from Xuanwo as a code owner June 9, 2026 02:57
@dosubot dosubot Bot added size:L This PR changes 100-499 lines, ignoring generated files. releases-note/refactor The PR does a refactor on code or has a title that begins with "refactor" labels Jun 9, 2026

@tonghuaroot tonghuaroot left a comment

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

confined_join rejects .. traversal but does not guard against absolute paths. On Unix, PathBuf::join with an absolute argument replaces the base entirely:

let base = Path::new("/data/root");
assert_eq!(base.join("/etc/passwd"), Path::new("/etc/passwd")); // escapes root

I know normalize_path strips the leading / before keys reach the backend, but since confined_join is now a public API in core, it would be safer to also reject Component::RootDir (and Component::Prefix on Windows) so the function is self-contained and doesn't rely on callers to sanitize input.

@erickguan erickguan force-pushed the fix/confine-compfs-monoiofs-root branch from 3b7a7f4 to 5230cd2 Compare June 10, 2026 14:39
@erickguan

Copy link
Copy Markdown
Member Author

@tonghuaroot Good point! Anything in core/src/raw are private APIs subject to our changes. We apply normalize_path in Operator before a service sees the path. But I can see confined_join rather confusing for service maintainers.

From OpenDAL users perspective, trying something with "/etc/passwd" should never work when root is /root:

let op = Operator::new("fs", "/root")?.finish(); # pesudo code
op.read("/etc/passwd").await?; # We don't have access to this file.

normalize_path strips the leading / before keys reach the backend

normalize_path has a corner case which will return "/" when path is empty. This will also break the confined_join as you pointed out PathBuf::join will replace path with an absolute path. So a safer choice would be to reject absolute path except "/".

    let trimmed = path.trim_end_matches('/');
    if trimmed == "" {
        return Ok(base)
    }
    if Path::new(trimmed)
        .components()
        .any(|c| matches!(c, Component::ParentDir))
    {
        return Err(Error::new(
            ErrorKind::NotFound,
            "path escapes the configured root via `..`",
        )
        .with_context("path", path));
    }
    Ok(base.join(normalized))
}

Another problem is that we are losing information from this API. e.g. when we have a path dir/, our function strips the information. I believe some services rely on the trailing / so this is still not of satisfactory.

@erickguan erickguan marked this pull request as draft June 12, 2026 17:58
@erickguan

Copy link
Copy Markdown
Member Author

Let's wait #7641 to see what do we want to do.
E.g., could return a path; could simply validate.

erickguan added a commit that referenced this pull request Jun 18, 2026
Restructure the RFC to:
- broaden motivation beyond trim() to alignment with fs/object store
  conventions
- combine confined_join extraction into the proposal
- replace code-level reference explanation with design rationale
- describe confined_join purpose and extraction independently of #7712
- build the problem statement in own words rather than quoting other docs
@erickguan erickguan force-pushed the fix/confine-compfs-monoiofs-root branch 3 times, most recently from 642021a to 729daa1 Compare June 20, 2026 04:16
@erickguan erickguan force-pushed the fix/confine-compfs-monoiofs-root branch from 729daa1 to 109ecd4 Compare June 20, 2026 04:17
@erickguan erickguan requested a review from tonghuaroot June 20, 2026 04:17
@erickguan erickguan marked this pull request as ready for review June 20, 2026 04:17
Comment thread core/services/compfs/src/core.rs Outdated
core.root_join("./a/b").unwrap(),
PathBuf::from("/data/root/a/b")
);
// A key containing `..` only as a substring of a name is not a traversal.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

These tests (test_root_join_rejects_parent_dir / test_root_join_allows_normal_keys) are near-identical to the test_confined_join_* tests already added in path.rs. Since root_join is a one-line wrapper over confined_join, this duplicates coverage. Consider keeping only the path.rs tests to avoid maintaining two copies.

Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Yes! Merged.

@tonghuaroot

Copy link
Copy Markdown
Contributor

#7641 is merged now. The empty-path "/" case you mentioned is another good catch. Happy to help if you'd like me to send a follow-up PR for the absolute path guard.

@erickguan erickguan requested a review from tonghuaroot June 20, 2026 06:18
@erickguan

Copy link
Copy Markdown
Member Author

@tonghuaroot Sounds good! Can you open a new PR after this one?

@tonghuaroot

Copy link
Copy Markdown
Contributor

LGTM on the refactoring. Will open a follow-up PR for the absolute path guard after this lands.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

releases-note/refactor The PR does a refactor on code or has a title that begins with "refactor" size:L This PR changes 100-499 lines, ignoring generated files.

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants