diff mbox series

[v2] arm/ioreq: guard interaction data on read/write operations

Message ID 20231005092141.2540016-1-andrii_chepurnyi@epam.com (mailing list archive)
State Superseded
Headers show
Series [v2] arm/ioreq: guard interaction data on read/write operations | expand

Commit Message

Andrii Chepurnyi Oct. 5, 2023, 9:21 a.m. UTC
For read operations, there's a potential issue when the data field
of the ioreq struct is partially updated in the response. To address
this, zero data field during read operations. This modification
serves as a safeguard against implementations that may inadvertently
partially update the data field in response to read requests.
For instance, consider an 8-bit read operation. In such cases, QEMU,
returns the same content of the dat field with only 8 bits of
updated data. This behavior could potentially result in the
propagation of incorrect or unintended data to ioreq clients.
There is also a good point to guard interaction data with actual size
of the interaction.

Signed-off-by: Andrii Chepurnyi <andrii_chepurnyi@epam.com>
---
 xen/arch/arm/ioreq.c | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

Comments

Julien Grall Oct. 5, 2023, 12:30 p.m. UTC | #1
(+ Henry)

Hi Andrii,

@Henry, this patch is a candidate for Xen 4.18. The fix is 
self-contained in the IOREQ code which is in tech preview. So I think 
the risk is limited.

On 05/10/2023 10:21, Andrii Chepurnyi wrote:
> For read operations, there's a potential issue when the data field
> of the ioreq struct is partially updated in the response. To address
> this, zero data field during read operations. This modification
> serves as a safeguard against implementations that may inadvertently
> partially update the data field in response to read requests.
> For instance, consider an 8-bit read operation. In such cases, QEMU,
> returns the same content of the dat field with only 8 bits of
> updated data. This behavior could potentially result in the
> propagation of incorrect or unintended data to ioreq clients.
> There is also a good point to guard interaction data with actual size
> of the interaction.

I don't quite understand the last sentence. Is it meant to justify why 
the two other changes? I.e.:
   * Masking the value for a write
   * Masking the value returned by the Device-Model

>  > Signed-off-by: Andrii Chepurnyi <andrii_chepurnyi@epam.com>
> ---
>   xen/arch/arm/ioreq.c | 8 ++++++--
>   1 file changed, 6 insertions(+), 2 deletions(-)
> 
> diff --git a/xen/arch/arm/ioreq.c b/xen/arch/arm/ioreq.c
> index 3bed0a14c0..26dae8ca28 100644
> --- a/xen/arch/arm/ioreq.c
> +++ b/xen/arch/arm/ioreq.c
> @@ -17,6 +17,8 @@ enum io_state handle_ioserv(struct cpu_user_regs *regs, struct vcpu *v)
>   {
>       const union hsr hsr = { .bits = regs->hsr };
>       const struct hsr_dabt dabt = hsr.dabt;
> +    const uint8_t access_size = (1 << dabt.size) * 8;

Please use 1U.

> +    const uint64_t access_mask = GENMASK_ULL(access_size - 1, 0);
>       /* Code is similar to handle_read */
>       register_t r = v->io.req.data;
>   
> @@ -26,6 +28,7 @@ enum io_state handle_ioserv(struct cpu_user_regs *regs, struct vcpu *v)
>       if ( dabt.write )
>           return IO_HANDLED;
>   
> +    r &= access_mask;

I would add a comment on top with:

"The Arm Arm requires the value to be zero-extended to the size of the 
register. The Device Model is not meant to touch the bits outside of the 
access size, but let's not trust that."

>       r = sign_extend(dabt, r);
>   
>       set_user_reg(regs, dabt.reg, r);
> @@ -39,6 +42,8 @@ enum io_state try_fwd_ioserv(struct cpu_user_regs *regs,
>       struct vcpu_io *vio = &v->io;
>       const struct instr_details instr = info->dabt_instr;
>       struct hsr_dabt dabt = info->dabt;
> +    const uint8_t access_size = (1 << dabt.size) * 8;

Please use 1U.

> +    const uint64_t access_mask = GENMASK_ULL(access_size - 1, 0);
>       ioreq_t p = {
>           .type = IOREQ_TYPE_COPY,
>           .addr = info->gpa,
> @@ -79,8 +84,7 @@ enum io_state try_fwd_ioserv(struct cpu_user_regs *regs,
>           return IO_HANDLED;
>   
>       ASSERT(dabt.valid);
> -

This change seems to be spurious?

> -    p.data = get_user_reg(regs, info->dabt.reg);
> +    p.data = p.dir ? 0 : get_user_reg(regs, info->dabt.reg) & access_mask;

For this case, I would add:

"During a write access, the Device Model only need to know the content 
of the bits associated with the access size (e.g. for 8-bit, the lower 
8-bits). During a read access, the Device Model don't need to know any 
value. So restrict the value it can access."

Cheers,
Henry Wang Oct. 5, 2023, 12:34 p.m. UTC | #2
Hi Julien,

> On Oct 5, 2023, at 20:30, Julien Grall <julien@xen.org> wrote:
> 
> (+ Henry)

Thanks.

> 
> Hi Andrii,
> 
> @Henry, this patch is a candidate for Xen 4.18. The fix is self-contained in the IOREQ code which is in tech preview. So I think the risk is limited.

Sure, with your comments below properly addressed, I think it is fine
to include this patch in 4.18, so:

Release-acked-by: Henry Wang <Henry.Wang@arm.com>

Kind regards,
Henry

> 
> On 05/10/2023 10:21, Andrii Chepurnyi wrote:
>> For read operations, there's a potential issue when the data field
>> of the ioreq struct is partially updated in the response. To address
>> this, zero data field during read operations. This modification
>> serves as a safeguard against implementations that may inadvertently
>> partially update the data field in response to read requests.
>> For instance, consider an 8-bit read operation. In such cases, QEMU,
>> returns the same content of the dat field with only 8 bits of
>> updated data. This behavior could potentially result in the
>> propagation of incorrect or unintended data to ioreq clients.
>> There is also a good point to guard interaction data with actual size
>> of the interaction.
> 
> I don't quite understand the last sentence. Is it meant to justify why the two other changes? I.e.:
>  * Masking the value for a write
>  * Masking the value returned by the Device-Model
> 
>> > Signed-off-by: Andrii Chepurnyi <andrii_chepurnyi@epam.com>
>> ---
>>  xen/arch/arm/ioreq.c | 8 ++++++--
>>  1 file changed, 6 insertions(+), 2 deletions(-)
>> diff --git a/xen/arch/arm/ioreq.c b/xen/arch/arm/ioreq.c
>> index 3bed0a14c0..26dae8ca28 100644
>> --- a/xen/arch/arm/ioreq.c
>> +++ b/xen/arch/arm/ioreq.c
>> @@ -17,6 +17,8 @@ enum io_state handle_ioserv(struct cpu_user_regs *regs, struct vcpu *v)
>>  {
>>      const union hsr hsr = { .bits = regs->hsr };
>>      const struct hsr_dabt dabt = hsr.dabt;
>> +    const uint8_t access_size = (1 << dabt.size) * 8;
> 
> Please use 1U.
> 
>> +    const uint64_t access_mask = GENMASK_ULL(access_size - 1, 0);
>>      /* Code is similar to handle_read */
>>      register_t r = v->io.req.data;
>>  @@ -26,6 +28,7 @@ enum io_state handle_ioserv(struct cpu_user_regs *regs, struct vcpu *v)
>>      if ( dabt.write )
>>          return IO_HANDLED;
>>  +    r &= access_mask;
> 
> I would add a comment on top with:
> 
> "The Arm Arm requires the value to be zero-extended to the size of the register. The Device Model is not meant to touch the bits outside of the access size, but let's not trust that."
> 
>>      r = sign_extend(dabt, r);
>>        set_user_reg(regs, dabt.reg, r);
>> @@ -39,6 +42,8 @@ enum io_state try_fwd_ioserv(struct cpu_user_regs *regs,
>>      struct vcpu_io *vio = &v->io;
>>      const struct instr_details instr = info->dabt_instr;
>>      struct hsr_dabt dabt = info->dabt;
>> +    const uint8_t access_size = (1 << dabt.size) * 8;
> 
> Please use 1U.
> 
>> +    const uint64_t access_mask = GENMASK_ULL(access_size - 1, 0);
>>      ioreq_t p = {
>>          .type = IOREQ_TYPE_COPY,
>>          .addr = info->gpa,
>> @@ -79,8 +84,7 @@ enum io_state try_fwd_ioserv(struct cpu_user_regs *regs,
>>          return IO_HANDLED;
>>        ASSERT(dabt.valid);
>> -
> 
> This change seems to be spurious?
> 
>> -    p.data = get_user_reg(regs, info->dabt.reg);
>> +    p.data = p.dir ? 0 : get_user_reg(regs, info->dabt.reg) & access_mask;
> 
> For this case, I would add:
> 
> "During a write access, the Device Model only need to know the content of the bits associated with the access size (e.g. for 8-bit, the lower 8-bits). During a read access, the Device Model don't need to know any value. So restrict the value it can access."
> 
> Cheers,
> 
> -- 
> Julien Grall
diff mbox series

Patch

diff --git a/xen/arch/arm/ioreq.c b/xen/arch/arm/ioreq.c
index 3bed0a14c0..26dae8ca28 100644
--- a/xen/arch/arm/ioreq.c
+++ b/xen/arch/arm/ioreq.c
@@ -17,6 +17,8 @@  enum io_state handle_ioserv(struct cpu_user_regs *regs, struct vcpu *v)
 {
     const union hsr hsr = { .bits = regs->hsr };
     const struct hsr_dabt dabt = hsr.dabt;
+    const uint8_t access_size = (1 << dabt.size) * 8;
+    const uint64_t access_mask = GENMASK_ULL(access_size - 1, 0);
     /* Code is similar to handle_read */
     register_t r = v->io.req.data;
 
@@ -26,6 +28,7 @@  enum io_state handle_ioserv(struct cpu_user_regs *regs, struct vcpu *v)
     if ( dabt.write )
         return IO_HANDLED;
 
+    r &= access_mask;
     r = sign_extend(dabt, r);
 
     set_user_reg(regs, dabt.reg, r);
@@ -39,6 +42,8 @@  enum io_state try_fwd_ioserv(struct cpu_user_regs *regs,
     struct vcpu_io *vio = &v->io;
     const struct instr_details instr = info->dabt_instr;
     struct hsr_dabt dabt = info->dabt;
+    const uint8_t access_size = (1 << dabt.size) * 8;
+    const uint64_t access_mask = GENMASK_ULL(access_size - 1, 0);
     ioreq_t p = {
         .type = IOREQ_TYPE_COPY,
         .addr = info->gpa,
@@ -79,8 +84,7 @@  enum io_state try_fwd_ioserv(struct cpu_user_regs *regs,
         return IO_HANDLED;
 
     ASSERT(dabt.valid);
-
-    p.data = get_user_reg(regs, info->dabt.reg);
+    p.data = p.dir ? 0 : get_user_reg(regs, info->dabt.reg) & access_mask;
     vio->req = p;
     vio->suspended = false;
     vio->info.dabt_instr = instr;