Subject: Re: [PATCH v2 4/9] cache-tree: avoid strtol() on non-string buffer

From: Phillip Wood <[email-masked]>
Date: Sun, 23 Nov 2025 15:51:57 +0000

On 23/11/2025 06:19, Junio C Hamano wrote:
> Phillip Wood <[email-masked]> writes:
> 
>>> +	while (len && *s == '-') {
>>> +		sign *= -1;
>>> +		s++;
>>> +		len--;
>>> +	}
>>
>> This accepts any number of '-' signs but I believe strtol() only accepts
>> a single sign (the standard says "optionally preceded by a plus or minus
>> sign") so this is a change in behavior from the existing code. I'm not
>> sure we really need to be that accommodating here.
> 
> That is true, but at the same time I do not think we really need to
> make it more strict with extra code.

All we need to do to accept a single minus sign is s/while/if/

>>> +	while (len) {
>>> +		if (!isdigit(*s))
>>> +			break;
>>> +		ret *= 10;
>>> +		ret += *s - '0';
>>> +		s++;
>>> +		len--;
>>> +	}
>>> +
>>> +	if (s == *ptr)
>>> +		return -1;
>>
>> This accepts "-" as a valid input, as we're tightening up our parsing it
>> would be nice to require a digit after any '-' sign.
> 
> Ditto.

If we limit ourselves to accepting a single minus sign then this can become
	if (s == *ptr + (sign == -1))

so we need very little in the way of extra code.

> We could try to be more careful, but it quickly became messy when I
> tried.  Here is an unfinished attempt of mine.

A generic helper to replace strtol() that takes a length rather than 
assuming the input is NUL terminated could be useful elsewhere but I'm 
not sure we need something that complicated here. I do like the fact 
that overflow does not cause undefined behavior though. Changing ret for 
"int" to "unsigned" in peff's patch should fix that.

Thanks

Phillip

> 
> static int parse_int(const char **ptr, unsigned long *len_p, int *out)
> {
> 	const char *s = *ptr;
> 	unsigned long len = *len_p;
> 	unsigned val = 0;
> 	bool negate = false;
> 	int saw_digits = 0;
> 
> 	while (len && isspace(*s)) {
> 		len--;
> 		s++;
> 	}
> 	if (!len)
> 		return -1;
> 	switch (*s) {
> 	case '-':
> 		negate = true;
> 		/* fallthru */
> 	case '+':
> 		s++;
> 		len--;
> 		break;
> 	default:
> 		break;
> 	}
> 
> 	while (len) {
> 		unsigned next;
> 		if (!isdigit(*s))
> 			break;
> 		next = val * 10 + *s - '0';
> 		if (next < val)
> 			return -1;
> 		val = next;
> 		s++;
> 		len--;
> 		saw_digits = 1;
> 	}
> 	if (!saw_digits ||
> 	    (!negate && INT_MAX <= val) ||
> 	    (negate && INT_MAX < val))
> 		return -1;
> 
> 	*ptr = s;
> 	*len_p = len;
> 	*out = negate ? (0 - val) : val;
> 	return 0;
> }