diff mbox series

[v2,2/4] media: venus: hfi_parser: avoid OOB access beyond payload word count

Message ID 20241128-venus_oob_2-v2-2-483ae0a464b8@quicinc.com
State New
Headers show
Series Venus driver fixes to avoid possible OOB accesses | expand

Commit Message

Vikash Garodia Nov. 28, 2024, 5:05 a.m. UTC
words_count denotes the number of words in total payload, while data
points to payload of various property within it. When words_count
reaches last word, data can access memory beyond the total payload. This
can lead to OOB access. Refactor the parsing logic such that the
remaining payload is checked before parsing it.

Cc: stable@vger.kernel.org
Fixes: 1a73374a04e5 ("media: venus: hfi_parser: add common capability parser")
Signed-off-by: Vikash Garodia <quic_vgarodia@quicinc.com>
---
 drivers/media/platform/qcom/venus/hfi_parser.c | 57 +++++++++++++++++++++-----
 1 file changed, 46 insertions(+), 11 deletions(-)

Comments

Bryan O'Donoghue Dec. 2, 2024, 12:08 p.m. UTC | #1
On 28/11/2024 05:05, Vikash Garodia wrote:
> words_count denotes the number of words in total payload, while data
> points to payload of various property within it. When words_count
> reaches last word, data can access memory beyond the total payload. This
> can lead to OOB access. Refactor the parsing logic such that the
> remaining payload is checked before parsing it.
> 
> Cc: stable@vger.kernel.org
> Fixes: 1a73374a04e5 ("media: venus: hfi_parser: add common capability parser")
> Signed-off-by: Vikash Garodia <quic_vgarodia@quicinc.com>
> ---
>   drivers/media/platform/qcom/venus/hfi_parser.c | 57 +++++++++++++++++++++-----
>   1 file changed, 46 insertions(+), 11 deletions(-)
> 
> diff --git a/drivers/media/platform/qcom/venus/hfi_parser.c b/drivers/media/platform/qcom/venus/hfi_parser.c
> index 1cc17f3dc8948160ea6c3015d2c03e475b8aa29e..14349c2f84b205a8b79dee3acff1408bb63ac54a 100644
> --- a/drivers/media/platform/qcom/venus/hfi_parser.c
> +++ b/drivers/media/platform/qcom/venus/hfi_parser.c
> @@ -282,8 +282,8 @@ static int hfi_platform_parser(struct venus_core *core, struct venus_inst *inst)
>   u32 hfi_parser(struct venus_core *core, struct venus_inst *inst, void *buf,
>   	       u32 size)
>   {
> +	u32 *words = buf, *payload, codecs = 0, domain = 0;
>   	unsigned int words_count = size >> 2;
> -	u32 *word = buf, *data, codecs = 0, domain = 0;
>   	int ret;
>   
>   	ret = hfi_platform_parser(core, inst);
> @@ -301,36 +301,71 @@ u32 hfi_parser(struct venus_core *core, struct venus_inst *inst, void *buf,
>   	}
>   
>   	while (words_count) {
> -		data = word + 1;
> +		payload = words + 1;
>   
> -		switch (*word) {
> +		switch (*words) {
>   		case HFI_PROPERTY_PARAM_CODEC_SUPPORTED:
> -			parse_codecs(core, data);
> +			if (words_count < sizeof(struct hfi_codec_supported))
> +				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
> +
> +			parse_codecs(core, payload);
>   			init_codecs(core);
> +			words_count -= sizeof(struct hfi_codec_supported);
> +			words += sizeof(struct hfi_codec_supported);
>   			break;
>   		case HFI_PROPERTY_PARAM_MAX_SESSIONS_SUPPORTED:
> -			parse_max_sessions(core, data);
> +			if (words_count < sizeof(struct hfi_max_sessions_supported))
> +				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
> +
> +			parse_max_sessions(core, payload);
> +			words_count -= sizeof(struct hfi_max_sessions_supported);
> +			words += sizeof(struct hfi_max_sessions_supported);
>   			break;
>   		case HFI_PROPERTY_PARAM_CODEC_MASK_SUPPORTED:
> -			parse_codecs_mask(&codecs, &domain, data);
> +			if (words_count < sizeof(struct hfi_codec_mask_supported))
> +				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
> +
> +			parse_codecs_mask(&codecs, &domain, payload);
> +			words_count -= sizeof(struct hfi_codec_mask_supported);
> +			words += sizeof(struct hfi_codec_mask_supported);
>   			break;
>   		case HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED:
> -			parse_raw_formats(core, codecs, domain, data);
> +			if (words_count < sizeof(struct hfi_uncompressed_format_supported))
> +				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
> +
> +			parse_raw_formats(core, codecs, domain, payload);
> +			words_count -= sizeof(struct hfi_uncompressed_format_supported);
> +			words += sizeof(struct hfi_uncompressed_format_supported);
>   			break;
>   		case HFI_PROPERTY_PARAM_CAPABILITY_SUPPORTED:
> -			parse_caps(core, codecs, domain, data);
> +			if (words_count < sizeof(struct hfi_capabilities))
> +				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
> +
> +			parse_caps(core, codecs, domain, payload);
> +			words_count -= sizeof(struct hfi_capabilities);
> +			words += sizeof(struct hfi_capabilities);
>   			break;
>   		case HFI_PROPERTY_PARAM_PROFILE_LEVEL_SUPPORTED:
> -			parse_profile_level(core, codecs, domain, data);
> +			if (words_count < sizeof(struct hfi_profile_level_supported))
> +				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
> +
> +			parse_profile_level(core, codecs, domain, payload);
> +			words_count -= sizeof(struct hfi_profile_level_supported);
> +			words += sizeof(struct hfi_profile_level_supported);
>   			break;
>   		case HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE_SUPPORTED:
> -			parse_alloc_mode(core, codecs, domain, data);
> +			if (words_count < sizeof(struct hfi_buffer_alloc_mode_supported))
> +				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
> +
> +			parse_alloc_mode(core, codecs, domain, payload);
> +			words_count -= sizeof(struct hfi_buffer_alloc_mode_supported);
> +			words += sizeof(struct hfi_buffer_alloc_mode_supported);
>   			break;
>   		default:
>   			break;
>   		}
>   
> -		word++;
> +		words++;
>   		words_count--;
>   	}
>   
> 

I like the changes made here.

Let me suggest you have the parse_something() return the size of the 
buffer consumed or an error code.

If you calculate the maximum pointer instead of the words_count

frame_size = payload + max;

/* Your while can look like this */

while (words < frame_size)
switch(*words){
case HFI_PROPERTY_X:
     /* if the function returns the bytes consumed */
     ret = parse_x();
     break;
case HFI_PROPERTY_X:
     ret = parse_x();
     break;
}

if (ret < 0)
     return -ret;

/* you can increment the pointer once at the bottom of the loop */
words += ret;
}


That way you can

1. Get rid of words_count and not have to decrement it
2. Have one variable words which is checked against the maximum
    size while(words < frame_size)
3. Have the function that consumes the data return
    how much buffer it has consumed, instead of inlining in the
    switch
4. Increment at the bottom of the switch once instead
    of several times in the switch

IMO it would be clearer/neater that way. Please consider.

---
bod
Vikash Garodia Dec. 2, 2024, 1:24 p.m. UTC | #2
On 12/2/2024 5:38 PM, Bryan O'Donoghue wrote:
> On 28/11/2024 05:05, Vikash Garodia wrote:
>> words_count denotes the number of words in total payload, while data
>> points to payload of various property within it. When words_count
>> reaches last word, data can access memory beyond the total payload. This
>> can lead to OOB access. Refactor the parsing logic such that the
>> remaining payload is checked before parsing it.
>>
>> Cc: stable@vger.kernel.org
>> Fixes: 1a73374a04e5 ("media: venus: hfi_parser: add common capability parser")
>> Signed-off-by: Vikash Garodia <quic_vgarodia@quicinc.com>
>> ---
>>   drivers/media/platform/qcom/venus/hfi_parser.c | 57 +++++++++++++++++++++-----
>>   1 file changed, 46 insertions(+), 11 deletions(-)
>>
>> diff --git a/drivers/media/platform/qcom/venus/hfi_parser.c
>> b/drivers/media/platform/qcom/venus/hfi_parser.c
>> index
>> 1cc17f3dc8948160ea6c3015d2c03e475b8aa29e..14349c2f84b205a8b79dee3acff1408bb63ac54a 100644
>> --- a/drivers/media/platform/qcom/venus/hfi_parser.c
>> +++ b/drivers/media/platform/qcom/venus/hfi_parser.c
>> @@ -282,8 +282,8 @@ static int hfi_platform_parser(struct venus_core *core,
>> struct venus_inst *inst)
>>   u32 hfi_parser(struct venus_core *core, struct venus_inst *inst, void *buf,
>>              u32 size)
>>   {
>> +    u32 *words = buf, *payload, codecs = 0, domain = 0;
>>       unsigned int words_count = size >> 2;
>> -    u32 *word = buf, *data, codecs = 0, domain = 0;
>>       int ret;
>>         ret = hfi_platform_parser(core, inst);
>> @@ -301,36 +301,71 @@ u32 hfi_parser(struct venus_core *core, struct
>> venus_inst *inst, void *buf,
>>       }
>>         while (words_count) {
>> -        data = word + 1;
>> +        payload = words + 1;
>>   -        switch (*word) {
>> +        switch (*words) {
>>           case HFI_PROPERTY_PARAM_CODEC_SUPPORTED:
>> -            parse_codecs(core, data);
>> +            if (words_count < sizeof(struct hfi_codec_supported))
>> +                return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
>> +
>> +            parse_codecs(core, payload);
>>               init_codecs(core);
>> +            words_count -= sizeof(struct hfi_codec_supported);
>> +            words += sizeof(struct hfi_codec_supported);
>>               break;
>>           case HFI_PROPERTY_PARAM_MAX_SESSIONS_SUPPORTED:
>> -            parse_max_sessions(core, data);
>> +            if (words_count < sizeof(struct hfi_max_sessions_supported))
>> +                return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
>> +
>> +            parse_max_sessions(core, payload);
>> +            words_count -= sizeof(struct hfi_max_sessions_supported);
>> +            words += sizeof(struct hfi_max_sessions_supported);
>>               break;
>>           case HFI_PROPERTY_PARAM_CODEC_MASK_SUPPORTED:
>> -            parse_codecs_mask(&codecs, &domain, data);
>> +            if (words_count < sizeof(struct hfi_codec_mask_supported))
>> +                return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
>> +
>> +            parse_codecs_mask(&codecs, &domain, payload);
>> +            words_count -= sizeof(struct hfi_codec_mask_supported);
>> +            words += sizeof(struct hfi_codec_mask_supported);
>>               break;
>>           case HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED:
>> -            parse_raw_formats(core, codecs, domain, data);
>> +            if (words_count < sizeof(struct hfi_uncompressed_format_supported))
>> +                return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
>> +
>> +            parse_raw_formats(core, codecs, domain, payload);
>> +            words_count -= sizeof(struct hfi_uncompressed_format_supported);
>> +            words += sizeof(struct hfi_uncompressed_format_supported);
>>               break;
>>           case HFI_PROPERTY_PARAM_CAPABILITY_SUPPORTED:
>> -            parse_caps(core, codecs, domain, data);
>> +            if (words_count < sizeof(struct hfi_capabilities))
>> +                return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
>> +
>> +            parse_caps(core, codecs, domain, payload);
>> +            words_count -= sizeof(struct hfi_capabilities);
>> +            words += sizeof(struct hfi_capabilities);
>>               break;
>>           case HFI_PROPERTY_PARAM_PROFILE_LEVEL_SUPPORTED:
>> -            parse_profile_level(core, codecs, domain, data);
>> +            if (words_count < sizeof(struct hfi_profile_level_supported))
>> +                return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
>> +
>> +            parse_profile_level(core, codecs, domain, payload);
>> +            words_count -= sizeof(struct hfi_profile_level_supported);
>> +            words += sizeof(struct hfi_profile_level_supported);
>>               break;
>>           case HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE_SUPPORTED:
>> -            parse_alloc_mode(core, codecs, domain, data);
>> +            if (words_count < sizeof(struct hfi_buffer_alloc_mode_supported))
>> +                return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
>> +
>> +            parse_alloc_mode(core, codecs, domain, payload);
>> +            words_count -= sizeof(struct hfi_buffer_alloc_mode_supported);
>> +            words += sizeof(struct hfi_buffer_alloc_mode_supported);
>>               break;
>>           default:
>>               break;
>>           }
>>   -        word++;
>> +        words++;
>>           words_count--;
>>       }
>>  
> 
> I like the changes made here.
> 
> Let me suggest you have the parse_something() return the size of the buffer
> consumed or an error code.
> 
> If you calculate the maximum pointer instead of the words_count
> 
> frame_size = payload + max;
> 
> /* Your while can look like this */
> 
> while (words < frame_size)
> switch(*words){
> case HFI_PROPERTY_X:
>     /* if the function returns the bytes consumed */
>     ret = parse_x();
>     break;
> case HFI_PROPERTY_X:
>     ret = parse_x();
>     break;
> }
> 
> if (ret < 0)
>     return -ret;
> 
> /* you can increment the pointer once at the bottom of the loop */
> words += ret;
> }
> 
> 
> That way you can
> 
> 1. Get rid of words_count and not have to decrement it
> 2. Have one variable words which is checked against the maximum
>    size while(words < frame_size)
> 3. Have the function that consumes the data return
>    how much buffer it has consumed, instead of inlining in the
>    switch
> 4. Increment at the bottom of the switch once instead
>    of several times in the switch
> 
> IMO it would be clearer/neater that way. Please consider.
Appreciate your time to dig deeper into it. Expanding your suggestion, filling
in the details

frame_size = words + size;

/* Your while can look like this */

while (words < frame_size)
remaining_size = framesize - words;
switch(*words){
 case HFI_PROPERTY_X:
     if (remaining_size < sizeof(payload_X)
        return insuff_res;
     /* if the function returns the bytes consumed */
     ret = parse_x(core, words + 1);
     break;
 case HFI_PROPERTY_Y:
     if (remaining_size < sizeof(payload_X)
        return insuff_res;
     ret = parse_y(core, words + 1);
     break;
 default:
     ret = 1;
 }

 if (ret < 0)
     return -ret;

 /* you can increment the pointer once at the bottom of the loop */
 words += ret;
 }

If you see, words_count is doing the role of remaining_size. In existing
implementation as well, we can move those increments per case to once per loop,
just that to avoid incrementing for default case.

Regards,
Vikash
Bryan O'Donoghue Dec. 2, 2024, 2:46 p.m. UTC | #3
On 02/12/2024 13:24, Vikash Garodia wrote:
> If you see, words_count is doing the role of remaining_size. In existing
> implementation as well, we can move those increments per case to once per loop,
> just that to avoid incrementing for default case.

Yes.

To me it seems

- A redundant step to have words_count
- That the functions themselves should return the amount of bytes
   words += should increment by
- That you could do that words += outside of the switch instead of
   at each case:

but to be clear the logic of incrementing the words looks right to me 
now, I'm suggesting additional stylistic change.

---
bod
diff mbox series

Patch

diff --git a/drivers/media/platform/qcom/venus/hfi_parser.c b/drivers/media/platform/qcom/venus/hfi_parser.c
index 1cc17f3dc8948160ea6c3015d2c03e475b8aa29e..14349c2f84b205a8b79dee3acff1408bb63ac54a 100644
--- a/drivers/media/platform/qcom/venus/hfi_parser.c
+++ b/drivers/media/platform/qcom/venus/hfi_parser.c
@@ -282,8 +282,8 @@  static int hfi_platform_parser(struct venus_core *core, struct venus_inst *inst)
 u32 hfi_parser(struct venus_core *core, struct venus_inst *inst, void *buf,
 	       u32 size)
 {
+	u32 *words = buf, *payload, codecs = 0, domain = 0;
 	unsigned int words_count = size >> 2;
-	u32 *word = buf, *data, codecs = 0, domain = 0;
 	int ret;
 
 	ret = hfi_platform_parser(core, inst);
@@ -301,36 +301,71 @@  u32 hfi_parser(struct venus_core *core, struct venus_inst *inst, void *buf,
 	}
 
 	while (words_count) {
-		data = word + 1;
+		payload = words + 1;
 
-		switch (*word) {
+		switch (*words) {
 		case HFI_PROPERTY_PARAM_CODEC_SUPPORTED:
-			parse_codecs(core, data);
+			if (words_count < sizeof(struct hfi_codec_supported))
+				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
+
+			parse_codecs(core, payload);
 			init_codecs(core);
+			words_count -= sizeof(struct hfi_codec_supported);
+			words += sizeof(struct hfi_codec_supported);
 			break;
 		case HFI_PROPERTY_PARAM_MAX_SESSIONS_SUPPORTED:
-			parse_max_sessions(core, data);
+			if (words_count < sizeof(struct hfi_max_sessions_supported))
+				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
+
+			parse_max_sessions(core, payload);
+			words_count -= sizeof(struct hfi_max_sessions_supported);
+			words += sizeof(struct hfi_max_sessions_supported);
 			break;
 		case HFI_PROPERTY_PARAM_CODEC_MASK_SUPPORTED:
-			parse_codecs_mask(&codecs, &domain, data);
+			if (words_count < sizeof(struct hfi_codec_mask_supported))
+				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
+
+			parse_codecs_mask(&codecs, &domain, payload);
+			words_count -= sizeof(struct hfi_codec_mask_supported);
+			words += sizeof(struct hfi_codec_mask_supported);
 			break;
 		case HFI_PROPERTY_PARAM_UNCOMPRESSED_FORMAT_SUPPORTED:
-			parse_raw_formats(core, codecs, domain, data);
+			if (words_count < sizeof(struct hfi_uncompressed_format_supported))
+				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
+
+			parse_raw_formats(core, codecs, domain, payload);
+			words_count -= sizeof(struct hfi_uncompressed_format_supported);
+			words += sizeof(struct hfi_uncompressed_format_supported);
 			break;
 		case HFI_PROPERTY_PARAM_CAPABILITY_SUPPORTED:
-			parse_caps(core, codecs, domain, data);
+			if (words_count < sizeof(struct hfi_capabilities))
+				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
+
+			parse_caps(core, codecs, domain, payload);
+			words_count -= sizeof(struct hfi_capabilities);
+			words += sizeof(struct hfi_capabilities);
 			break;
 		case HFI_PROPERTY_PARAM_PROFILE_LEVEL_SUPPORTED:
-			parse_profile_level(core, codecs, domain, data);
+			if (words_count < sizeof(struct hfi_profile_level_supported))
+				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
+
+			parse_profile_level(core, codecs, domain, payload);
+			words_count -= sizeof(struct hfi_profile_level_supported);
+			words += sizeof(struct hfi_profile_level_supported);
 			break;
 		case HFI_PROPERTY_PARAM_BUFFER_ALLOC_MODE_SUPPORTED:
-			parse_alloc_mode(core, codecs, domain, data);
+			if (words_count < sizeof(struct hfi_buffer_alloc_mode_supported))
+				return HFI_ERR_SYS_INSUFFICIENT_RESOURCES;
+
+			parse_alloc_mode(core, codecs, domain, payload);
+			words_count -= sizeof(struct hfi_buffer_alloc_mode_supported);
+			words += sizeof(struct hfi_buffer_alloc_mode_supported);
 			break;
 		default:
 			break;
 		}
 
-		word++;
+		words++;
 		words_count--;
 	}