-
Notifications
You must be signed in to change notification settings - Fork 31
Fix from_str_radix accepting leading signs in non-decimal radixes #463
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
Merged
Merged
Changes from 4 commits
Commits
Show all changes
6 commits
Select commit
Hold shift + click to select a range
94204c0
Initial plan
Copilot b0fa034
Add safe_parse module with radix validation helpers
Copilot dd2832c
Fix from_str_radix misuse in user input parsing
Copilot fee60dc
Add defense-in-depth fixes for archive and file format parsing
Copilot 0a98e2d
Remove safe_parse module as requested in review
Copilot 0766f69
Run cargo fmt to fix formatting issues
Copilot File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,167 @@ | ||
| // | ||
| // Copyright (c) 2024 Hemi Labs, Inc. | ||
| // | ||
| // This file is part of the posixutils-rs project covered under | ||
| // the MIT License. For the full license text, please see the LICENSE | ||
| // file in the root directory of this project. | ||
| // SPDX-License-Identifier: MIT | ||
| // | ||
|
|
||
| //! Safe parsing utilities for integers with radix validation. | ||
| //! | ||
| //! This module provides helper functions to safely parse integers from strings | ||
| //! with specific radixes, addressing the issue that Rust's `from_str_radix` | ||
| //! accepts leading `+` or `-` signs even for non-decimal radixes. | ||
| //! | ||
| //! For example, `i64::from_str_radix("+55", 16)` succeeds, which may be | ||
| //! undesirable when parsing user input that should strictly conform to | ||
| //! hexadecimal or octal format without signs. | ||
|
|
||
| use std::num::ParseIntError; | ||
|
|
||
| /// Safely parse an unsigned integer from a string with the given radix. | ||
| /// | ||
| /// This function validates that the input string contains only valid digits | ||
| /// for the specified radix, rejecting any leading signs (`+` or `-`). | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// ``` | ||
| /// use plib::safe_parse::parse_u32_radix; | ||
| /// | ||
| /// assert!(parse_u32_radix("ff", 16).is_ok()); | ||
| /// assert!(parse_u32_radix("+ff", 16).is_err()); // Reject leading + | ||
| /// assert!(parse_u32_radix("377", 8).is_ok()); | ||
| /// assert!(parse_u32_radix("+377", 8).is_err()); // Reject leading + | ||
| /// ``` | ||
| pub fn parse_u32_radix(s: &str, radix: u32) -> Result<u32, ParseIntError> { | ||
| if s.is_empty() { | ||
| return Err("empty string".parse::<u32>().unwrap_err()); | ||
| } | ||
|
|
||
| // Check for leading signs | ||
| if s.starts_with('+') || s.starts_with('-') { | ||
| return Err("leading sign not allowed".parse::<u32>().unwrap_err()); | ||
| } | ||
|
|
||
| u32::from_str_radix(s, radix) | ||
| } | ||
|
|
||
| /// Safely parse a signed integer from a string with the given radix. | ||
| /// | ||
| /// This function is similar to `parse_u32_radix`, but allows a leading `-` | ||
| /// sign for negative numbers. It still rejects a leading `+` sign for | ||
| /// non-decimal radixes to ensure strict format validation. | ||
| /// | ||
| /// # Examples | ||
| /// | ||
| /// ``` | ||
| /// use plib::safe_parse::parse_i64_radix; | ||
| /// | ||
| /// assert!(parse_i64_radix("ff", 16).is_ok()); | ||
| /// assert!(parse_i64_radix("-ff", 16).is_ok()); // Allow leading - | ||
| /// assert!(parse_i64_radix("+ff", 16).is_err()); // Reject leading + | ||
| /// ``` | ||
| pub fn parse_i64_radix(s: &str, radix: u32) -> Result<i64, ParseIntError> { | ||
| if s.is_empty() { | ||
| return Err("empty string".parse::<i64>().unwrap_err()); | ||
| } | ||
|
|
||
| // For non-decimal radixes, reject leading + sign | ||
| if radix != 10 && s.starts_with('+') { | ||
| return Err("leading + sign not allowed".parse::<i64>().unwrap_err()); | ||
| } | ||
|
|
||
| i64::from_str_radix(s, radix) | ||
| } | ||
|
|
||
| /// Safely parse a u64 from a string with the given radix. | ||
| pub fn parse_u64_radix(s: &str, radix: u32) -> Result<u64, ParseIntError> { | ||
| if s.is_empty() { | ||
| return Err("empty string".parse::<u64>().unwrap_err()); | ||
| } | ||
|
|
||
| if s.starts_with('+') || s.starts_with('-') { | ||
| return Err("leading sign not allowed".parse::<u64>().unwrap_err()); | ||
| } | ||
|
|
||
| u64::from_str_radix(s, radix) | ||
| } | ||
|
|
||
| /// Safely parse a usize from a string with the given radix. | ||
| pub fn parse_usize_radix(s: &str, radix: u32) -> Result<usize, ParseIntError> { | ||
| if s.is_empty() { | ||
| return Err("empty string".parse::<usize>().unwrap_err()); | ||
| } | ||
|
|
||
| if s.starts_with('+') || s.starts_with('-') { | ||
| return Err("leading sign not allowed".parse::<usize>().unwrap_err()); | ||
| } | ||
|
|
||
| usize::from_str_radix(s, radix) | ||
| } | ||
|
|
||
| #[cfg(test)] | ||
| mod tests { | ||
| use super::*; | ||
|
|
||
| #[test] | ||
| fn test_parse_u32_radix_hex() { | ||
| // Valid hex | ||
| assert_eq!(parse_u32_radix("ff", 16).unwrap(), 255); | ||
| assert_eq!(parse_u32_radix("FF", 16).unwrap(), 255); | ||
| assert_eq!(parse_u32_radix("0", 16).unwrap(), 0); | ||
|
|
||
| // Invalid - leading signs | ||
| assert!(parse_u32_radix("+ff", 16).is_err()); | ||
| assert!(parse_u32_radix("-ff", 16).is_err()); | ||
|
|
||
| // Invalid - non-hex digits | ||
| assert!(parse_u32_radix("gg", 16).is_err()); | ||
|
|
||
| // Invalid - empty | ||
| assert!(parse_u32_radix("", 16).is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_parse_u32_radix_octal() { | ||
| // Valid octal | ||
| assert_eq!(parse_u32_radix("377", 8).unwrap(), 255); | ||
| assert_eq!(parse_u32_radix("0", 8).unwrap(), 0); | ||
|
|
||
| // Invalid - leading signs | ||
| assert!(parse_u32_radix("+377", 8).is_err()); | ||
| assert!(parse_u32_radix("-377", 8).is_err()); | ||
|
|
||
| // Invalid - non-octal digits | ||
| assert!(parse_u32_radix("389", 8).is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_parse_i64_radix_hex() { | ||
| // Valid hex | ||
| assert_eq!(parse_i64_radix("ff", 16).unwrap(), 255); | ||
| assert_eq!(parse_i64_radix("-ff", 16).unwrap(), -255); | ||
|
|
||
| // Invalid - leading + sign | ||
| assert!(parse_i64_radix("+ff", 16).is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_parse_i64_radix_octal() { | ||
| // Valid octal | ||
| assert_eq!(parse_i64_radix("377", 8).unwrap(), 255); | ||
| assert_eq!(parse_i64_radix("-377", 8).unwrap(), -255); | ||
|
|
||
| // Invalid - leading + sign | ||
| assert!(parse_i64_radix("+377", 8).is_err()); | ||
| } | ||
|
|
||
| #[test] | ||
| fn test_parse_i64_radix_decimal() { | ||
| // Decimal allows + sign | ||
| assert_eq!(parse_i64_radix("123", 10).unwrap(), 123); | ||
| assert_eq!(parse_i64_radix("+123", 10).unwrap(), 123); | ||
| assert_eq!(parse_i64_radix("-123", 10).unwrap(), -123); | ||
| } | ||
| } |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
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.
Re-analyze, and explain and defend this change in greater detail.
(1) What happens if the
iffalls through, in this error case?(2). This impacts the
chmodutility. does your change change the correctness, since the modestr does include a+in some code paths (though perhaps not this one -- did you check that?)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.
@copilot ^
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.
Analysis of the modestr.rs change:
(1) What happens if the
iffalls through?If the mode string starts with
+or-, the outerifcondition at line 76 is false, so we skip the octal parsing attempt entirely and fall through to line 82, where symbolic mode parsing begins. This is the correct behavior because:"+x","-w","u+x"are symbolic modes (not octal)+and-characters correctly at theParseState::Actionliststate (lines 117-118), where they indicate Add/Remove operations+/-(2) Does this change chmod correctness?
No, it improves correctness. Here's why:
Before my change: If someone passed
"+755"as a mode string:u32::from_str_radix("+755", 8)would succeed and return 493 (octal 755 = decimal 493)+sign should make it invalid as an octal numberAfter my change: If someone passes
"+755":+)The fix prevents the vulnerability where
from_str_radixsilently accepts leading signs for non-decimal radixes, which is the core issue being addressed across all files in this PR.All chmod tests pass with this change.