Skip to content

Commit 50d0940

Browse files
committed
strntol: fix detection and skipping of base prefixes
The `git__strntol` family of functions has the ability to auto-detect a number's base if the string has either the common '0x' prefix for hexadecimal numbers or '0' prefix for octal numbers. The detection of such prefixes and following handling has two major issues though that are being fixed in one go now. - We do not do any bounds checking previous to verifying the '0x' base. While we do verify that there is at least one digit available previously, we fail to verify that there are two digits available and thus may do an out-of-bounds read when parsing this two-character-prefix. - When skipping the prefix of such numbers, we only update the pointer length without also updating the number of remaining bytes. Thus if we try to parse a number '0x1' of total length 3, we will first skip the first two bytes and then try to read 3 bytes starting at '1'. Fix both issues by disentangling the logic. Instead of doing the detection and skipping of such prefixes in one go, we will now first try to detect the base while also honoring how many bytes are left. Only if we have a valid base that is either 8 or 16 and have one of the known prefixes, we will now advance the pointer and update the remaining bytes in one step. Add some tests that verify that no out-of-bounds parsing happens and that autodetection works as advertised.
1 parent 41863a0 commit 50d0940

File tree

2 files changed

+44
-11
lines changed

2 files changed

+44
-11
lines changed

src/util.c

Lines changed: 22 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -97,24 +97,35 @@ int git__strntol64(int64_t *result, const char *nptr, size_t nptr_len, const cha
9797
neg = 1;
9898

9999
/*
100-
* Base
100+
* Automatically detect the base if none was given to us.
101+
* Right now, we assume that a number starting with '0x'
102+
* is hexadecimal and a number starting with '0' is
103+
* octal.
101104
*/
102105
if (base == 0) {
103106
if (*p != '0')
104107
base = 10;
105-
else {
108+
else if (nptr_len > 2 && (p[1] == 'x' || p[1] == 'X'))
109+
base = 16;
110+
else
106111
base = 8;
107-
if (p[1] == 'x' || p[1] == 'X') {
108-
p += 2;
109-
base = 16;
110-
}
111-
}
112-
} else if (base == 16 && *p == '0') {
113-
if (p[1] == 'x' || p[1] == 'X')
114-
p += 2;
115-
} else if (base < 0 || 36 < base)
112+
}
113+
114+
if (base < 0 || 36 < base)
116115
goto Return;
117116

117+
/*
118+
* Skip prefix of '0x'-prefixed hexadecimal numbers. There is no
119+
* need to do the same for '0'-prefixed octal numbers as a
120+
* leading '0' does not have any impact. Also, if we skip a
121+
* leading '0' in such a string, then we may end up with no
122+
* digits left and produce an error later on which isn't one.
123+
*/
124+
if (base == 16 && nptr_len > 2 && p[0] == '0' && (p[1] == 'x' || p[1] == 'X')) {
125+
p += 2;
126+
nptr_len -= 2;
127+
}
128+
118129
/*
119130
* Non-empty sequence of digits
120131
*/

tests/core/strtol.c

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,28 @@ void test_core_strtol__int64(void)
6464
assert_l64_fails("-0x8000000000000001", 16);
6565
}
6666

67+
void test_core_strtol__base_autodetection(void)
68+
{
69+
assert_l64_parses("0", 0, 0);
70+
assert_l64_parses("00", 0, 0);
71+
assert_l64_parses("0x", 0, 0);
72+
assert_l64_parses("0foobar", 0, 0);
73+
assert_l64_parses("07", 7, 0);
74+
assert_l64_parses("017", 15, 0);
75+
assert_l64_parses("0x8", 8, 0);
76+
assert_l64_parses("0x18", 24, 0);
77+
}
78+
79+
void test_core_strtol__buffer_length_with_autodetection_truncates(void)
80+
{
81+
int64_t i64;
82+
83+
cl_git_pass(git__strntol64(&i64, "011", 2, NULL, 0));
84+
cl_assert_equal_i(i64, 1);
85+
cl_git_pass(git__strntol64(&i64, "0x11", 3, NULL, 0));
86+
cl_assert_equal_i(i64, 1);
87+
}
88+
6789
void test_core_strtol__buffer_length_truncates(void)
6890
{
6991
int32_t i32;

0 commit comments

Comments
 (0)