[PATCH] iio: adc: xilinx-ams: fix out-of-bounds accesses when parsing channels

From: Weigang He

Date: Sat Sep 26 2026 - 01:55:22 EST


ams_parse_firmware() allocates room for ARRAY_SIZE(ams_ps_channels) +
ARRAY_SIZE(ams_pl_channels) + ARRAY_SIZE(ams_ctrl_channels) = 51
channel specs and lets ams_init_module() fill them for the AMS node and
each of its children, without telling it how much room is left.

For the PL-SYSMON node, ams_get_ext_chan() appends one channel per
"channel@N" child after the 10 fixed PL channels. The binding allows
reg values 20..50, so a PL node can have up to 31 such children, i.e.
up to 41 PL channels, while only 31 are budgeted for it. Together with
the 13 PS and 7 AMS control channels, this writes past the end of the
buffer. Since the modules are filled in device tree order, the fixed
channel blocks copied after the PL node can overflow as well.

ams_get_ext_chan() also only checks the upper bound of reg. ext_chan is
unsigned, so a reg below 20 makes it wrap and ams_pl_channels[] is read
far out of bounds.

Pass the remaining capacity down to ams_init_module() and
ams_get_ext_chan() and fail with -EINVAL when a module does not fit,
instead of writing past the buffer. Also reject reg values below 20, as
the binding requires.

Found by static analysis tool CodeQL.

Fixes: d5c70627a794 ("iio: adc: Add Xilinx AMS driver")
Cc: stable@xxxxxxxxxxxxxxx
Assisted-by: LLM codeql
Signed-off-by: Weigang He <geoffreyhe2@xxxxxxxxx>
---

Notes:
Compile-tested only (ARCH=arm64 allmodconfig, W=1). Not tested on
hardware, and there is no reproducer.

The CodeQL query behind this report was synthesized with LLM assistance,
and the fix and changelog were drafted with LLM assistance; I have
reviewed them.

drivers/iio/adc/xilinx-ams.c | 29 +++++++++++++++++++++++------
1 file changed, 23 insertions(+), 6 deletions(-)

diff --git a/drivers/iio/adc/xilinx-ams.c b/drivers/iio/adc/xilinx-ams.c
index 158e6133abf50..373a9e799ee63 100644
--- a/drivers/iio/adc/xilinx-ams.c
+++ b/drivers/iio/adc/xilinx-ams.c
@@ -1142,7 +1142,8 @@ static const struct iio_chan_spec ams_ctrl_channels[] = {
};

static int ams_get_ext_chan(struct fwnode_handle *chan_node,
- struct iio_chan_spec *channels, int num_channels)
+ struct iio_chan_spec *channels, int num_channels,
+ int max_channels)
{
struct iio_chan_spec *chan;
struct fwnode_handle *child;
@@ -1151,9 +1152,15 @@ static int ams_get_ext_chan(struct fwnode_handle *chan_node,

fwnode_for_each_child_node(chan_node, child) {
ret = fwnode_property_read_u32(child, "reg", &reg);
- if (ret || reg > AMS_PL_MAX_EXT_CHANNEL + 30)
+ if (ret || reg < 30 - AMS_PL_MAX_FIXED_CHANNEL ||
+ reg > AMS_PL_MAX_EXT_CHANNEL + 30)
continue;

+ if (num_channels >= max_channels) {
+ fwnode_handle_put(child);
+ return -EINVAL;
+ }
+
chan = &channels[num_channels];
ext_chan = reg + AMS_PL_MAX_FIXED_CHANNEL - 30;
memcpy(chan, &ams_pl_channels[ext_chan], sizeof(*channels));
@@ -1183,7 +1190,7 @@ static void ams_iounmap_pl(void *data)

static int ams_init_module(struct iio_dev *indio_dev,
struct fwnode_handle *fwnode,
- struct iio_chan_spec *channels)
+ struct iio_chan_spec *channels, int max_channels)
{
struct device *dev = indio_dev->dev.parent;
struct ams *ams = iio_priv(indio_dev);
@@ -1191,6 +1198,9 @@ static int ams_init_module(struct iio_dev *indio_dev,
int ret;

if (fwnode_device_is_compatible(fwnode, "xlnx,zynqmp-ams-ps")) {
+ if (max_channels < ARRAY_SIZE(ams_ps_channels))
+ return -EINVAL;
+
ams->ps_base = fwnode_iomap(fwnode, 0);
if (!ams->ps_base)
return -ENXIO;
@@ -1202,6 +1212,9 @@ static int ams_init_module(struct iio_dev *indio_dev,
memcpy(channels, ams_ps_channels, sizeof(ams_ps_channels));
num_channels = ARRAY_SIZE(ams_ps_channels);
} else if (fwnode_device_is_compatible(fwnode, "xlnx,zynqmp-ams-pl")) {
+ if (max_channels < AMS_PL_MAX_FIXED_CHANNEL)
+ return -EINVAL;
+
ams->pl_base = fwnode_iomap(fwnode, 0);
if (!ams->pl_base)
return -ENXIO;
@@ -1214,8 +1227,11 @@ static int ams_init_module(struct iio_dev *indio_dev,
memcpy(channels, ams_pl_channels, AMS_PL_MAX_FIXED_CHANNEL * sizeof(*channels));
num_channels += AMS_PL_MAX_FIXED_CHANNEL;
num_channels = ams_get_ext_chan(fwnode, channels,
- num_channels);
+ num_channels, max_channels);
} else if (fwnode_device_is_compatible(fwnode, "xlnx,zynqmp-ams")) {
+ if (max_channels < ARRAY_SIZE(ams_ctrl_channels))
+ return -EINVAL;
+
/* add AMS channels to iio device channels */
memcpy(channels, ams_ctrl_channels, sizeof(ams_ctrl_channels));
num_channels += ARRAY_SIZE(ams_ctrl_channels);
@@ -1245,7 +1261,7 @@ static int ams_parse_firmware(struct iio_dev *indio_dev)
return -ENOMEM;

if (fwnode_device_is_available(fwnode)) {
- ret = ams_init_module(indio_dev, fwnode, ams_channels);
+ ret = ams_init_module(indio_dev, fwnode, ams_channels, ams_size);
if (ret < 0)
return ret;

@@ -1253,7 +1269,8 @@ static int ams_parse_firmware(struct iio_dev *indio_dev)
}

device_for_each_child_node_scoped(dev, child) {
- ret = ams_init_module(indio_dev, child, ams_channels + num_channels);
+ ret = ams_init_module(indio_dev, child, ams_channels + num_channels,
+ ams_size - num_channels);
if (ret < 0)
return ret;


base-commit: 165768bb70265b5c38cf0b73fafd75be235f8b14
--
2.43.0