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

fix: protect read file from path traversal #4943

Open
wants to merge 6 commits into
base: master
Choose a base branch
from

Conversation

ndom91
Copy link
Contributor

@ndom91 ndom91 commented Sep 18, 2024

☕️ Reasoning

  • Ensure that the new get_pr_template_contents fn can't read anything outside of the project's root directory

🧢 Changes

Copy link

vercel bot commented Sep 18, 2024

The latest updates on your projects. Learn more about Vercel for Git ↗︎

Name Status Preview Comments Updated (UTC)
gitbutler-web ✅ Ready (Inspect) Visit Preview 💬 Add feedback Sep 18, 2024 2:33pm

@ndom91
Copy link
Contributor Author

ndom91 commented Sep 18, 2024

@mtsgrd some small rust question:

  • There's probably no need for .context() on each method that potentially fails, right?
  • Is it okay to add the entire git2 crate to gitbutler-fs's Cargo.toml? Can I add something like feature = { Repository } to only add what I need here?

@Byron
Copy link
Collaborator

Byron commented Sep 18, 2024

  • Is it okay to add the entire git2 crate to gitbutler-fs's Cargo.toml? Can I add something like feature = { Repository } to only add what I need here?

Features are predefined in the crate and they usually only provide features if it grates off other dependencies, not just code. When I see the example above, it reminds me of typescript imports :).

Comment on lines +53 to +73
fn read_file_from_workspace(&self, relative_path: &Path) -> Result<String> {
let ctx = CommandContext::open(self)?;
let base_path = ctx
.repository()
.path()
.parent()
.ok_or(anyhow::anyhow!("Could not find repository base path"))?;

let canonicalized_file_path = base_path.join(relative_path).canonicalize()?;

if canonicalized_file_path.as_path().starts_with(base_path) {
let tree = ctx.repository().head()?.peel_to_tree()?;
let entry = tree.get_path(relative_path)?;
let blob = ctx.repository().find_blob(entry.id())?;
let content = std::str::from_utf8(blob.content())?;

Ok(content.to_string())
} else {
anyhow::bail!("Invalid workspace file");
}
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Small nit, the path() function may return the repository path rather than the .git folder if the repository is bare. If we had a repository that was bare, that would mean that it could access files outside the repository.

Suggested change
fn read_file_from_workspace(&self, relative_path: &Path) -> Result<String> {
let ctx = CommandContext::open(self)?;
let base_path = ctx
.repository()
.path()
.parent()
.ok_or(anyhow::anyhow!("Could not find repository base path"))?;
let canonicalized_file_path = base_path.join(relative_path).canonicalize()?;
if canonicalized_file_path.as_path().starts_with(base_path) {
let tree = ctx.repository().head()?.peel_to_tree()?;
let entry = tree.get_path(relative_path)?;
let blob = ctx.repository().find_blob(entry.id())?;
let content = std::str::from_utf8(blob.content())?;
Ok(content.to_string())
} else {
anyhow::bail!("Invalid workspace file");
}
}
fn read_file_from_workspace(&self, relative_path: &Path) -> Result<String> {
let ctx = CommandContext::open(self)?;
let repository = ctx.repository();
// If a repostiory is bare, repository.path will return the repository
// directory rather than the `.git` repository.
// Ref: https://docs.rs/git2/latest/git2/struct.Repository.html#method.path
let base_path = if repository.is_bare() {
repository.path()
} else {
repository
.path()
.parent()
.ok_or(anyhow::anyhow!("Could not find repository base path"))?
};
let canonicalized_file_path = base_path.join(relative_path).canonicalize()?;
if canonicalized_file_path.as_path().starts_with(base_path) {
let tree = repository.head()?.peel_to_tree()?;
let entry = tree.get_path(relative_path)?;
let blob = repository.find_blob(entry.id())?;
let content = std::str::from_utf8(blob.content())?;
Ok(content.to_string())
} else {
anyhow::bail!("Invalid workspace file");
}
}

I wonder if we should name the function something like read_commited_file or something like that, to make it clear that its not going to look at uncommitted changes.

Copy link
Contributor

Choose a reason for hiding this comment

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

Copy link
Contributor

Choose a reason for hiding this comment

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

Actually, I've overcomplicating it, we have the project, so we should be able to just use self.path with no faffing about 😆

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
rust Pull requests that update Rust code
Projects
None yet
Development

Successfully merging this pull request may close these issues.

5 participants