[PATCH] hwmon: occ: validate poll response sensor blocks
From: Pengpeng Hou
Date: Mon Jul 06 2026 - 06:26:58 EST
The OCC poll response parser walks a counted list of sensor data blocks.
It used the static response buffer size as the parse boundary, but the
transport only guarantees the bytes declared by the current response
length. A malformed or truncated response can therefore make the parser
read a block header or block payload beyond the current response data.
Use the response data_length as the parent boundary for the poll payload.
Reject responses that cannot contain the fixed poll header, prove that
each sensor block header is present before reading its item fields, and
then prove that the complete block fits before advancing to the next
block.
Signed-off-by: Pengpeng Hou <pengpeng@xxxxxxxxxxx>
---
drivers/hwmon/occ/common.c | 32 +++++++++++++++++++++++++-------
1 file changed, 25 insertions(+), 7 deletions(-)
diff --git a/drivers/hwmon/occ/common.c b/drivers/hwmon/occ/common.c
index e18e80e832fd..fa92ae6ec4f0 100644
--- a/drivers/hwmon/occ/common.c
+++ b/drivers/hwmon/occ/common.c
@@ -1052,9 +1052,10 @@ static int occ_setup_sensor_attrs(struct occ *occ)
}
/* only need to do this once at startup, as OCC won't change sensors on us */
-static void occ_parse_poll_response(struct occ *occ)
+static int occ_parse_poll_response(struct occ *occ)
{
unsigned int i, old_offset, offset = 0, size = 0;
+ u16 data_length;
struct occ_sensor *sensor;
struct occ_sensors *sensors = &occ->sensors;
struct occ_response *resp = &occ->resp;
@@ -1063,21 +1064,34 @@ static void occ_parse_poll_response(struct occ *occ)
struct occ_poll_response_header *header = &poll->header;
struct occ_sensor_data_block *block = &poll->block;
+ data_length = get_unaligned_be16(&resp->data_length);
+ if (data_length < sizeof(*header) || data_length > OCC_RESP_DATA_BYTES) {
+ dev_warn(occ->bus_dev, "invalid OCC poll response length %u\n",
+ data_length);
+ return -EMSGSIZE;
+ }
+
dev_info(occ->bus_dev, "OCC found, code level: %.16s\n",
header->occ_code_level);
for (i = 0; i < header->num_sensor_data_blocks; ++i) {
block = (struct occ_sensor_data_block *)((u8 *)block + offset);
+ if (size + sizeof(*header) + sizeof(block->header) >
+ data_length) {
+ dev_warn(occ->bus_dev, "truncated OCC sensor block header\n");
+ return -EMSGSIZE;
+ }
+
old_offset = offset;
offset = (block->header.num_sensors *
block->header.sensor_length) + sizeof(block->header);
- size += offset;
/* validate all the length/size fields */
- if ((size + sizeof(*header)) >= OCC_RESP_DATA_BYTES) {
- dev_warn(occ->bus_dev, "exceeded response buffer\n");
- return;
+ if (size + sizeof(*header) + offset > data_length) {
+ dev_warn(occ->bus_dev, "exceeded OCC poll response length\n");
+ return -EMSGSIZE;
}
+ size += offset;
dev_dbg(occ->bus_dev, " %04x..%04x: %.4s (%d sensors)\n",
old_offset, offset - 1, block->header.eye_catcher,
@@ -1107,6 +1121,8 @@ static void occ_parse_poll_response(struct occ *occ)
dev_dbg(occ->bus_dev, "Max resp size: %u+%zd=%zd\n", size,
sizeof(*header), size + sizeof(*header));
+
+ return 0;
}
int occ_active(struct occ *occ, bool active)
@@ -1138,10 +1154,12 @@ int occ_active(struct occ *occ, bool active)
goto unlock;
}
- occ->active = true;
occ->next_update = jiffies + OCC_UPDATE_FREQUENCY;
- occ_parse_poll_response(occ);
+ rc = occ_parse_poll_response(occ);
+ if (rc)
+ goto unlock;
+ occ->active = true;
rc = occ_setup_sensor_attrs(occ);
if (rc) {
dev_err(occ->bus_dev,
--
2.43.0