Re: [PATCH GSoC v4 2/9] fetch-object-info: detect malformed server responses

Junio C Hamano <[email protected]> Tue, 04 Aug 2026 13:40:11 -0700
Newsgroups org.kernel.vger.git
Message-ID <[email protected]>
Pablo Sabater <[email protected]> writes:

> The loop reading the object-info response stops as soon as the reader
> returns something other than PACKET_READ_NORMAL, or once it has read as
> many lines as we requested. Neither end is checked.
>
> A server that answers with fewer objects leaves the end of the result
> arrays empty, and the caller trusts that every requested object was
> filled in. A server that answers with more leaves the extra packets
> unread. On stateless transports check_stateless_delimiter() notices, but
> on the others it passes unnoticed.
>
> Check both limits by extracting the packet_reader_read() from the loop
> condition, so the loop no longer consumes the last packet (flush). If
> while looping the read is different from a PACKET_READ_NORMAL, die()
> meaning there are fewer objects than expected. After iterating, we only
> expect a flush, so if the last packet is not a flush, die().

OK.

After looking at "if (i != oids->nr)" now gone from the end of the
function, but before looking at how the loop terminates its
iterations, I wondered how the "there are too few" detection went.
It now not just stops iterating when seeing a status other than
PACKET_READ_NORMAL, but it actively barfs by dying, to detect a
short read.  So the only thing we need to check after the loop is if
we are truly at the end of the "list of oids->nr things".

Makes sense.

> Helped-by: Junio C Hamano <[email protected]>
> Mentored-by: Karthik Nayak <[email protected]>
> Mentored-by: Chandra Pratap <[email protected]>
> Signed-off-by: Pablo Sabater <[email protected]>
> ---
>  fetch-object-info.c | 14 ++++++++++----
>  1 file changed, 10 insertions(+), 4 deletions(-)
>
> diff --git a/fetch-object-info.c b/fetch-object-info.c
> index ba7e179c44..c2d4bf9403 100644
> --- a/fetch-object-info.c
> +++ b/fetch-object-info.c
> @@ -106,12 +106,13 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar
>  		}
>  	}
>  
> -	for (size_t i = 0;
> -	     packet_reader_read(reader) == PACKET_READ_NORMAL &&
> -	     i < args->oids->nr;
> -	     i++) {
> +	for (size_t i = 0; i < args->oids->nr; i++) {
>  		struct string_list object_info_values = STRING_LIST_INIT_DUP;
>  
> +		if (packet_reader_read(reader) != PACKET_READ_NORMAL)
> +			die(_("object-info: expected %" PRIuMAX " objects, got %" PRIuMAX),
> +			    (uintmax_t)args->oids->nr, (uintmax_t)i);
> +
>  		string_list_split(&object_info_values, reader->line, " ", -1);
>  
>  		if (strcmp(object_info_values.items[0].string,
> @@ -150,6 +151,11 @@ int fetch_object_info(const enum protocol_version version, struct object_info_ar
>  
>  		string_list_clear(&object_info_values, 0);
>  	}
> +
> +	if (packet_reader_read(reader) != PACKET_READ_FLUSH)
> +		die(_("object-info: expected flush after %"PRIuMAX" objects"),
> +		    (uintmax_t)args->oids->nr);
> +
>  	check_stateless_delimiter(stateless_rpc, reader, "stateless delimiter expected");
>  
>  	return 0;