diff --git a/commit/cherry_pick_commit.rs b/commit/cherry_pick_commit.rs index 1dfb873..a383f5a 100644 --- a/commit/cherry_pick_commit.rs +++ b/commit/cherry_pick_commit.rs @@ -3,6 +3,8 @@ use crate::commit::create_commit::command_ok; use crate::error::{GitError, GitResult}; use crate::pb::{CherryPickCommitRequest, CreateCommitResponse, GetCommitRequest}; +const MAX_CHERRY_PICK_PATCH_BYTES: usize = 100 * 1024 * 1024; + impl GitBare { pub fn cherry_pick_commit( &self, @@ -77,6 +79,12 @@ impl GitBare { .unchecked() .run()?; let patch_data = command_ok(diff)?; + if patch_data.len() > MAX_CHERRY_PICK_PATCH_BYTES { + return Err(GitError::InvalidArgument(format!( + "cherry-pick patch too large ({} bytes, max {MAX_CHERRY_PICK_PATCH_BYTES})", + patch_data.len() + ))); + } let apply = duct::cmd( "git", diff --git a/commit/create_commit.rs b/commit/create_commit.rs index 7bd0922..0dbcf97 100644 --- a/commit/create_commit.rs +++ b/commit/create_commit.rs @@ -23,6 +23,14 @@ impl GitBare { } } + const MAX_ACTIONS_PER_COMMIT: usize = 10_000; + if request.actions.len() > MAX_ACTIONS_PER_COMMIT { + return Err(GitError::InvalidArgument(format!( + "too many commit actions ({} > max {MAX_ACTIONS_PER_COMMIT})", + request.actions.len() + ))); + } + let repo = self.gix_repo()?; let branch = request.branch.clone(); tracing::debug!( @@ -160,6 +168,14 @@ impl GitBare { index_path: &str, action: &crate::pb::CreateCommitAction, ) -> GitResult<()> { + const MAX_ACTION_CONTENT_BYTES: usize = 100 * 1024 * 1024; + if action.content.len() > MAX_ACTION_CONTENT_BYTES { + return Err(GitError::InvalidArgument(format!( + "action content too large ({} bytes, max {MAX_ACTION_CONTENT_BYTES})", + action.content.len() + ))); + } + // Validate file paths to prevent command injection / traversal if !action.file_path.is_empty() { crate::sanitize::validate_file_path(&action.file_path)?; @@ -325,6 +341,14 @@ impl GitBare { author: Option<&crate::pb::Signature>, committer: Option<&crate::pb::Signature>, ) -> GitResult { + const MAX_COMMIT_MESSAGE_BYTES: usize = 10 * 1024 * 1024; + if message.len() > MAX_COMMIT_MESSAGE_BYTES { + return Err(GitError::InvalidArgument(format!( + "commit message too large ({} bytes, max {MAX_COMMIT_MESSAGE_BYTES})", + message.len() + ))); + } + let mut args = vec![ "--git-dir".to_string(), self.bare_dir.to_string_lossy().into_owned(), diff --git a/commit/query.rs b/commit/query.rs index a0c7f6b..d08eb73 100644 --- a/commit/query.rs +++ b/commit/query.rs @@ -83,6 +83,13 @@ impl GitBare { &self, request: CheckObjectsExistRequest, ) -> GitResult { + const MAX_CHECK_REVISIONS: usize = 10_000; + if request.revisions.len() > MAX_CHECK_REVISIONS { + return Err(crate::error::GitError::InvalidArgument(format!( + "too many revisions (max {MAX_CHECK_REVISIONS})" + ))); + } + let repo = self.gix_repo()?; let mut revisions = Vec::new(); diff --git a/commit/revert_commit.rs b/commit/revert_commit.rs index 9c71800..672fed5 100644 --- a/commit/revert_commit.rs +++ b/commit/revert_commit.rs @@ -3,6 +3,8 @@ use crate::commit::create_commit::command_ok; use crate::error::{GitError, GitResult}; use crate::pb::{CreateCommitResponse, GetCommitRequest, RevertCommitRequest}; +const MAX_REVERT_PATCH_BYTES: usize = 100 * 1024 * 1024; + impl GitBare { pub fn revert_commit(&self, request: RevertCommitRequest) -> GitResult { let target_branch = request.branch.clone(); @@ -79,6 +81,12 @@ impl GitBare { .unchecked() .run()?; let patch_data = command_ok(diff)?; + if patch_data.len() > MAX_REVERT_PATCH_BYTES { + return Err(GitError::InvalidArgument(format!( + "revert patch too large ({} bytes, max {MAX_REVERT_PATCH_BYTES})", + patch_data.len() + ))); + } let apply = duct::cmd( "git", diff --git a/merge/rebase.rs b/merge/rebase.rs index 37a2635..4bd519f 100644 --- a/merge/rebase.rs +++ b/merge/rebase.rs @@ -3,6 +3,9 @@ use crate::commit::create_commit::command_ok; use crate::error::{GitError, GitResult}; use crate::pb::{RebaseRequest, RebaseResult, rebase_result}; +const MAX_REBASE_COMMITS: usize = 10_000; +const MAX_REBASE_PATCH_BYTES: usize = 100 * 1024 * 1024; + impl GitBare { pub fn rebase(&self, request: RebaseRequest) -> GitResult { let branch = request.branch.clone(); @@ -68,6 +71,13 @@ impl GitBare { .map(String::from) .collect(); + if commits.len() > MAX_REBASE_COMMITS { + return Err(GitError::InvalidArgument(format!( + "too many commits to rebase ({} > max {MAX_REBASE_COMMITS})", + commits.len() + ))); + } + if commits.is_empty() { return Ok(RebaseResult { status: rebase_result::Status::RebaseResultStatusAlreadyUpToDate as i32, @@ -150,6 +160,12 @@ impl GitBare { .unchecked() .run()?; let patch_data = command_ok(diff)?; + if patch_data.len() > MAX_REBASE_PATCH_BYTES { + return Err(GitError::InvalidArgument(format!( + "rebase patch too large for {commit_hex} ({} bytes, max {MAX_REBASE_PATCH_BYTES})", + patch_data.len() + ))); + } let apply = duct::cmd( "git", diff --git a/merge/resolve_merge_conflicts.rs b/merge/resolve_merge_conflicts.rs index fc52eb2..42e3607 100644 --- a/merge/resolve_merge_conflicts.rs +++ b/merge/resolve_merge_conflicts.rs @@ -3,6 +3,8 @@ use crate::commit::create_commit::command_ok; use crate::error::{GitError, GitResult}; use crate::pb::{MergeResult, ResolveMergeConflictsRequest, merge_result}; +const MAX_RESOLUTION_CONTENT_BYTES: usize = 100 * 1024 * 1024; + impl GitBare { pub fn resolve_merge_conflicts( &self, @@ -50,8 +52,21 @@ impl GitBare { .run()?; command_ok(read_tree)?; + const MAX_RESOLUTIONS_COUNT: usize = 10_000; + if request.resolutions.len() > MAX_RESOLUTIONS_COUNT { + return Err(GitError::InvalidArgument(format!( + "too many resolutions (max {MAX_RESOLUTIONS_COUNT})" + ))); + } + for resolution in &request.resolutions { crate::sanitize::validate_file_path(&resolution.path)?; + if resolution.content.len() > MAX_RESOLUTION_CONTENT_BYTES { + return Err(GitError::InvalidArgument(format!( + "resolution content too large ({} bytes, max {MAX_RESOLUTION_CONTENT_BYTES})", + resolution.content.len() + ))); + } let hash = duct::cmd( "git", ["--git-dir", bare.as_str(), "hash-object", "-w", "--stdin"],