Skip to content

Commit 2f18b7d

Browse files
authored
Merge pull request #463 from rustcoreutils/copilot/fix-from-str-radix-usage
Fix from_str_radix accepting leading signs in non-decimal radixes
2 parents 41d0114 + 0766f69 commit 2f18b7d

File tree

15 files changed

+111
-12
lines changed

15 files changed

+111
-12
lines changed

display/printf.rs

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -840,6 +840,10 @@ fn parse_integer_arg(arg: &[u8]) -> Result<(i64, bool), String> {
840840
.or_else(|| num_str.strip_prefix("0X"))
841841
{
842842
// Hexadecimal
843+
// Reject if hex part starts with a sign (after we already handled the sign above)
844+
if hex.starts_with('+') || hex.starts_with('-') {
845+
return Err(format!("invalid number: {arg_str}"));
846+
}
843847
match i64::from_str_radix(hex, 16) {
844848
Ok(n) => (n, true),
845849
Err(_) => {
@@ -863,6 +867,10 @@ fn parse_integer_arg(arg: &[u8]) -> Result<(i64, bool), String> {
863867
.unwrap_or(false)
864868
{
865869
// Octal
870+
// Reject if octal part starts with a sign (after we already handled the sign above)
871+
if num_str.starts_with('+') || num_str.starts_with('-') {
872+
return Err(format!("invalid number: {arg_str}"));
873+
}
866874
let valid_len = num_str
867875
.chars()
868876
.take_while(|c| ('0'..='7').contains(c))

display/tests/printf/mod.rs

Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -532,3 +532,26 @@ fn test_float_missing_arg() {
532532
fn test_float_char_constant() {
533533
printf_test(&["%f", "'A"], "65.000000");
534534
}
535+
536+
// Regression tests for from_str_radix security issue
537+
// These should fail because double signs are invalid
538+
#[test]
539+
#[should_panic]
540+
fn test_reject_double_plus_hex() {
541+
// This should fail: "+0x+55" should be rejected, not parsed as 0x55
542+
printf_test(&["%d", "+0x+55"], "85");
543+
}
544+
545+
#[test]
546+
#[should_panic]
547+
fn test_reject_double_minus_hex() {
548+
// This should fail: "-0x-55" should be rejected
549+
printf_test(&["%d", "-0x-55"], "-85");
550+
}
551+
552+
#[test]
553+
#[should_panic]
554+
fn test_reject_double_plus_octal() {
555+
// This should fail: "+0+55" should be rejected
556+
printf_test(&["%d", "+0+55"], "45");
557+
}

file/find.rs

Lines changed: 7 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -476,12 +476,14 @@ fn parse_perm_mode(mode_str: &str) -> Result<PermMode, String> {
476476

477477
/// Parse a mode value (octal or symbolic)
478478
fn parse_mode_value(mode_str: &str) -> Result<u32, String> {
479-
// Try octal first
480-
if let Ok(m) = u32::from_str_radix(mode_str, 8) {
481-
if m > 0o7777 {
482-
return Err(format!("invalid mode: {}", mode_str));
479+
// Try octal first, but reject if it starts with a sign
480+
if !mode_str.starts_with('+') && !mode_str.starts_with('-') {
481+
if let Ok(m) = u32::from_str_radix(mode_str, 8) {
482+
if m > 0o7777 {
483+
return Err(format!("invalid mode: {}", mode_str));
484+
}
485+
return Ok(m);
483486
}
484-
return Ok(m);
485487
}
486488

487489
// Parse as symbolic mode starting from 0

file/od.rs

Lines changed: 17 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -219,9 +219,19 @@ fn parse_count<T: FromStr<Err = ParseIntError> + FromStrRadix>(
219219
count: &str,
220220
) -> Result<T, Box<dyn std::error::Error>> {
221221
if count.starts_with("0x") || count.starts_with("0X") {
222-
T::from_str_radix(&count[2..], 16).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
222+
let hex_part = &count[2..];
223+
// Reject if hex part contains a sign
224+
if hex_part.starts_with('+') || hex_part.starts_with('-') {
225+
return Err("invalid hexadecimal number".into());
226+
}
227+
T::from_str_radix(hex_part, 16).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
223228
} else if count.starts_with('0') && count.len() > 1 {
224-
T::from_str_radix(&count[1..], 8).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
229+
let oct_part = &count[1..];
230+
// Reject if octal part contains a sign
231+
if oct_part.starts_with('+') || oct_part.starts_with('-') {
232+
return Err("invalid octal number".into());
233+
}
234+
T::from_str_radix(oct_part, 8).map_err(|e| Box::new(e) as Box<dyn std::error::Error>)
225235
} else {
226236
count
227237
.parse::<T>()
@@ -279,6 +289,11 @@ fn parse_offset(offset: &str) -> Result<u64, ParseIntError> {
279289
offset
280290
};
281291

292+
// Reject if offset contains a sign (offsets should be unsigned)
293+
if offset.starts_with('+') || offset.starts_with('-') {
294+
return Err("invalid offset".parse::<u64>().unwrap_err());
295+
}
296+
282297
let parsed_offset = u64::from_str_radix(offset, base)?;
283298

284299
Ok(parsed_offset * multiplier)

pax/formats/cpio.rs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -272,8 +272,12 @@ fn parse_header<R: Read>(header: &[u8; HEADER_SIZE], reader: &mut R) -> PaxResul
272272
fn parse_octal_field(bytes: &[u8]) -> PaxResult<u64> {
273273
let s = std::str::from_utf8(bytes)
274274
.map_err(|_| PaxError::InvalidHeader("invalid octal field".to_string()))?;
275-
u64::from_str_radix(s.trim(), 8)
276-
.map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s)))
275+
let s = s.trim();
276+
// Reject if the octal string contains a sign
277+
if s.starts_with('+') || s.starts_with('-') {
278+
return Err(PaxError::InvalidHeader(format!("invalid octal: {}", s)));
279+
}
280+
u64::from_str_radix(s, 8).map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s)))
277281
}
278282

279283
/// Parse filename, removing NUL terminator

pax/formats/pax.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -837,6 +837,10 @@ fn parse_octal(bytes: &[u8]) -> PaxResult<u64> {
837837
if s.is_empty() {
838838
return Ok(0);
839839
}
840+
// Reject if the octal string contains a sign
841+
if s.starts_with('+') || s.starts_with('-') {
842+
return Err(PaxError::InvalidHeader(format!("invalid octal: {}", s)));
843+
}
840844
u64::from_str_radix(&s, 8).map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s)))
841845
}
842846

pax/formats/ustar.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -273,6 +273,10 @@ fn parse_octal(bytes: &[u8]) -> PaxResult<u64> {
273273
if s.is_empty() {
274274
return Ok(0);
275275
}
276+
// Reject if the octal string contains a sign
277+
if s.starts_with('+') || s.starts_with('-') {
278+
return Err(PaxError::InvalidHeader(format!("invalid octal: {}", s)));
279+
}
276280
u64::from_str_radix(&s, 8).map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s)))
277281
}
278282

pax/main.rs

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -557,6 +557,11 @@ fn is_valid_tar_checksum(buf: &[u8]) -> bool {
557557
return false;
558558
}
559559

560+
// Reject if checksum contains a sign
561+
if chksum_str.starts_with('+') || chksum_str.starts_with('-') {
562+
return false;
563+
}
564+
560565
let stored = match u32::from_str_radix(chksum_str, 8) {
561566
Ok(v) => v,
562567
Err(_) => return false,

pax/modes/append.rs

Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -124,6 +124,11 @@ fn is_valid_tar_checksum(header: &[u8]) -> bool {
124124
return false;
125125
}
126126

127+
// Reject if checksum contains a sign
128+
if chksum_str.starts_with('+') || chksum_str.starts_with('-') {
129+
return false;
130+
}
131+
127132
let stored = match u32::from_str_radix(chksum_str, 8) {
128133
Ok(v) => v,
129134
Err(_) => return false,
@@ -214,6 +219,10 @@ fn parse_octal(bytes: &[u8]) -> PaxResult<u64> {
214219
if s.is_empty() {
215220
return Ok(0);
216221
}
222+
// Reject if the octal string contains a sign
223+
if s.starts_with('+') || s.starts_with('-') {
224+
return Err(PaxError::InvalidHeader(format!("invalid octal: {}", s)));
225+
}
217226
u64::from_str_radix(s, 8).map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s)))
218227
}
219228

pax/multivolume.rs

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -745,6 +745,10 @@ fn parse_octal(bytes: &[u8]) -> PaxResult<u64> {
745745
if s.is_empty() {
746746
return Ok(0);
747747
}
748+
// Reject if the octal string contains a sign
749+
if s.starts_with('+') || s.starts_with('-') {
750+
return Err(PaxError::InvalidHeader(format!("invalid octal: {}", s)));
751+
}
748752
u64::from_str_radix(s, 8).map_err(|_| PaxError::InvalidHeader(format!("invalid octal: {}", s)))
749753
}
750754

0 commit comments

Comments
 (0)