[PATCH] hw/i2c: pmbus: NUL-terminate strings received via block write

imaginos posted 1 patch 1 month, 1 week ago
Patches applied successfully (tree, apply log)
git fetch https://github.com/patchew-project/qemu tags/patchew/20260614222131.5795-1-imaginos32@gmail.com
Maintainers: Titus Rwantare <titusr@google.com>, Tyrone Ting <kfting@nuvoton.com>, Hao Wu <wuhaotsh@google.com>, "Philippe Mathieu-Daudé" <philmd@mailo.com>
hw/i2c/pmbus_device.c         | 13 +++++++++++++
hw/sensor/adm1266.c           |  7 +++----
include/hw/i2c/pmbus_device.h | 10 ++++++++++
3 files changed, 26 insertions(+), 4 deletions(-)
[PATCH] hw/i2c: pmbus: NUL-terminate strings received via block write
Posted by imaginos 1 month, 1 week ago
The PMBus string registers (e.g. MFR_MODEL on the ADM1266) are writable
by the guest. pmbus_receive_block() can fill the destination field
completely, leaving no NUL terminator. When the value is later read back,
pmbus_send_string() calls strlen() on it, which reads past the end of the
array and returns a length that trips

    g_assert(len + pmdev->out_buf_len < SMBUS_DATA_MAX_LEN);

aborting QEMU. This is guest-triggerable.

Add pmbus_receive_string(), the write-side mirror of pmbus_send_string(),
which reserves the last byte of the destination so the stored value is
always NUL-terminated, and use it for the ADM1266 MFR string registers.

Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3388
Signed-off-by: imaginos <imaginos32@gmail.com>
---
 hw/i2c/pmbus_device.c         | 13 +++++++++++++
 hw/sensor/adm1266.c           |  7 +++----
 include/hw/i2c/pmbus_device.h | 10 ++++++++++
 3 files changed, 26 insertions(+), 4 deletions(-)

diff --git a/hw/i2c/pmbus_device.c b/hw/i2c/pmbus_device.c
index b1f9843f52..7c65d8c8b5 100644
--- a/hw/i2c/pmbus_device.c
+++ b/hw/i2c/pmbus_device.c
@@ -142,6 +142,19 @@ uint8_t pmbus_receive_block(PMBusDevice *pmdev, uint8_t *dest, size_t len)
     return len;
 }
 
+uint8_t pmbus_receive_string(PMBusDevice *pmdev, char *dest, size_t len)
+{
+    uint8_t rv;
+
+    if (len == 0) {
+        return 0;
+    }
+
+    rv = pmbus_receive_block(pmdev, (uint8_t *)dest, len - 1);
+    dest[len - 1] = '\0';
+    return rv;
+}
+
 
 static uint64_t pmbus_receive_uint(PMBusDevice *pmdev)
 {
diff --git a/hw/sensor/adm1266.c b/hw/sensor/adm1266.c
index 37d1cffd57..81545385a5 100644
--- a/hw/sensor/adm1266.c
+++ b/hw/sensor/adm1266.c
@@ -142,16 +142,15 @@ static int adm1266_write_data(PMBusDevice *pmdev, const uint8_t *buf,
 
     switch (pmdev->code) {
     case PMBUS_MFR_ID:                    /* R/W block */
-        pmbus_receive_block(pmdev, (uint8_t *)s->mfr_id, sizeof(s->mfr_id));
+        pmbus_receive_string(pmdev, s->mfr_id, sizeof(s->mfr_id));
         break;
 
     case PMBUS_MFR_MODEL:                 /* R/W block */
-        pmbus_receive_block(pmdev, (uint8_t *)s->mfr_model,
-                            sizeof(s->mfr_model));
+        pmbus_receive_string(pmdev, s->mfr_model, sizeof(s->mfr_model));
         break;
 
     case PMBUS_MFR_REVISION:               /* R/W block*/
-        pmbus_receive_block(pmdev, (uint8_t *)s->mfr_rev, sizeof(s->mfr_rev));
+        pmbus_receive_string(pmdev, s->mfr_rev, sizeof(s->mfr_rev));
         break;
 
     case ADM1266_SET_RTC:   /* do nothing */
diff --git a/include/hw/i2c/pmbus_device.h b/include/hw/i2c/pmbus_device.h
index f195c11384..4c4d3e5ae2 100644
--- a/include/hw/i2c/pmbus_device.h
+++ b/include/hw/i2c/pmbus_device.h
@@ -518,6 +518,16 @@ void pmbus_send_string(PMBusDevice *state, const char *data);
  */
 uint8_t pmbus_receive_block(PMBusDevice *pmdev, uint8_t *dest, size_t len);
 
+/**
+ * @brief Receive a Block Write and store it as a NUL-terminated string.
+ * Write-side mirror of pmbus_send_string(): guarantees dest is always a valid
+ * C string so later reads cannot run past the field. Use for string registers
+ * (e.g. MFR_ID, MFR_MODEL); use pmbus_receive_block() for binary blocks.
+ * @param dest - string buffer with enough capacity to receive the write
+ * @param len - the capacity of dest (the last byte is reserved for the NUL)
+ */
+uint8_t pmbus_receive_string(PMBusDevice *pmdev, char *dest, size_t len);
+
 /**
  * @brief Receive data over PMBus
  * These methods help track how much data is being received over PMBus
-- 
2.43.0
Re: [PATCH] hw/i2c: pmbus: NUL-terminate strings received via block write
Posted by Peter Maydell 1 month ago
On Sun, 14 Jun 2026 at 23:22, imaginos <imaginos32@gmail.com> wrote:
>
> The PMBus string registers (e.g. MFR_MODEL on the ADM1266) are writable
> by the guest. pmbus_receive_block() can fill the destination field
> completely, leaving no NUL terminator. When the value is later read back,
> pmbus_send_string() calls strlen() on it, which reads past the end of the
> array and returns a length that trips
>
>     g_assert(len + pmdev->out_buf_len < SMBUS_DATA_MAX_LEN);
>
> aborting QEMU. This is guest-triggerable.
>
> Add pmbus_receive_string(), the write-side mirror of pmbus_send_string(),
> which reserves the last byte of the destination so the stored value is
> always NUL-terminated, and use it for the ADM1266 MFR string registers.
>
> Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3388
> Signed-off-by: imaginos <imaginos32@gmail.com>

Thanks for looking at this. Notably, the bug report in #3388 makes
a completely incorrect claim about what its repro case is actually
doing. The repro case does the "write 32 bytes to trash the NUL
terminator, then try to read back", which is what this patch addresses.

I'll let the pmbus maintainers review the patch from a technical
point of view.

-- PMM
Re: [PATCH] hw/i2c: pmbus: NUL-terminate strings received via block write
Posted by Peter Maydell 2 weeks, 6 days ago
Ping: Titus, could you review this patch, please? It would be
good to get a fix into the upcoming release.

I think on another thread you said also you had some patches
to upstream to fix the "guest asks for data multiple times
but never actually reads it" potential issue ?


thanks
-- PMM


On Mon, 22 Jun 2026 at 11:18, Peter Maydell <peter.maydell@linaro.org> wrote:
>
> On Sun, 14 Jun 2026 at 23:22, imaginos <imaginos32@gmail.com> wrote:
> >
> > The PMBus string registers (e.g. MFR_MODEL on the ADM1266) are writable
> > by the guest. pmbus_receive_block() can fill the destination field
> > completely, leaving no NUL terminator. When the value is later read back,
> > pmbus_send_string() calls strlen() on it, which reads past the end of the
> > array and returns a length that trips
> >
> >     g_assert(len + pmdev->out_buf_len < SMBUS_DATA_MAX_LEN);
> >
> > aborting QEMU. This is guest-triggerable.
> >
> > Add pmbus_receive_string(), the write-side mirror of pmbus_send_string(),
> > which reserves the last byte of the destination so the stored value is
> > always NUL-terminated, and use it for the ADM1266 MFR string registers.
> >
> > Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3388
> > Signed-off-by: imaginos <imaginos32@gmail.com>
>
> Thanks for looking at this. Notably, the bug report in #3388 makes
> a completely incorrect claim about what its repro case is actually
> doing. The repro case does the "write 32 bytes to trash the NUL
> terminator, then try to read back", which is what this patch addresses.
>
> I'll let the pmbus maintainers review the patch from a technical
> point of view.
>
> -- PMM
Re: [PATCH] hw/i2c: pmbus: NUL-terminate strings received via block write
Posted by Titus Rwantare 2 weeks, 5 days ago
The adm1266 has a strange access pattern for the MFR_* registers, I'm
creating a series with the patch that fixes it, but a quick summary:
the data written to the register is the length of the data to be read
back, the actual string contents are read only.

I'm sending the patches that update SMBus and the adm1266 up.

-Titus
Re: [PATCH] hw/i2c: pmbus: NUL-terminate strings received via block write
Posted by Imaginos 1 month ago
Thanks for catching that. I went through the repro myself and arrived at
the same root cause,
but I missed that the original report's description of it was inaccurate
and should have flagged that.
Thanks for the review, I appreciate you taking the time to look into it.

пон, 22. јун 2026. у 12:18 Peter Maydell <peter.maydell@linaro.org> је
написао/ла:

> On Sun, 14 Jun 2026 at 23:22, imaginos <imaginos32@gmail.com> wrote:
> >
> > The PMBus string registers (e.g. MFR_MODEL on the ADM1266) are writable
> > by the guest. pmbus_receive_block() can fill the destination field
> > completely, leaving no NUL terminator. When the value is later read back,
> > pmbus_send_string() calls strlen() on it, which reads past the end of the
> > array and returns a length that trips
> >
> >     g_assert(len + pmdev->out_buf_len < SMBUS_DATA_MAX_LEN);
> >
> > aborting QEMU. This is guest-triggerable.
> >
> > Add pmbus_receive_string(), the write-side mirror of pmbus_send_string(),
> > which reserves the last byte of the destination so the stored value is
> > always NUL-terminated, and use it for the ADM1266 MFR string registers.
> >
> > Resolves: https://gitlab.com/qemu-project/qemu/-/issues/3388
> > Signed-off-by: imaginos <imaginos32@gmail.com>
>
> Thanks for looking at this. Notably, the bug report in #3388 makes
> a completely incorrect claim about what its repro case is actually
> doing. The repro case does the "write 32 bytes to trash the NUL
> terminator, then try to read back", which is what this patch addresses.
>
> I'll let the pmbus maintainers review the patch from a technical
> point of view.
>
> -- PMM
>