Devicetree
 help / color / mirror / Atom feed
From: sashiko-bot@kernel.org
To: "Dumitru Ceclan via B4 Relay"
	<devnull+dumitru.ceclan.analog.com@kernel.org>
Cc: devicetree@vger.kernel.org, conor+dt@kernel.org, robh@kernel.org
Subject: Re: [PATCH v12 14/22] media: i2c: add Maxim GMSL2/3 serializer framework
Date: Thu, 21 May 2026 13:23:39 +0000	[thread overview]
Message-ID: <20260521132339.5914C1F000E9@smtp.kernel.org> (raw)
In-Reply-To: <20260521-gmsl2-3_serdes-v12-14-b26d92931196@analog.com>

Thank you for your contribution! Sashiko AI review found 12 potential issue(s) to consider:
- [Critical] Uninitialized stack memory usage and stack buffer overflow with `v4l2_mbus_frame_desc`.
- [High] Heap buffer overflow in `max_ser_get_pipe_vcs_dts()` when storing unique Data Types.
- [High] Array out-of-bounds write in `max_ser_i2c_atr_detach_addr()`.
- [High] Use of uninitialized stack variable and Undefined Behavior via `__ffs(0)` / `__fls(0)`.
- [High] Lockless iteration in `max_ser_log_status` leads to Use-After-Free.
- [High] Error rollback paths rely on infallible dynamic memory allocations.
- [High] Use of stack-allocated buffers for `i2c_transfer()` violates DMA safety.
- [Medium] Multiple fwnode reference count leaks.
- [Medium] V4L2 control handler memory is leaked upon driver unregistration.
- [Medium] Memory leak of the I2C ATR structure on adapter initialization failure.
- [Medium] Missing `.get_mbus_config()` API implementation despite commit message claims.
- [Medium] Inappropriate use of devres functions in runtime hot paths.
--

commit 210e31826495bb37471b675b425310f6af76310b
Author: Cosmin Tanislav <demonsingur@gmail.com>
Date:   Thu May 21 12:44:20 2026 +0000

    media: i2c: add Maxim GMSL2/3 serializer framework
    
    These drivers are meant to be used as a common framework for Maxim
    GMSL2/3 serializers.
    
    This framework enables support for the following new features across
    all the chips:
     * Full Streams API support
     * .get_frame_desc()
     * .get_mbus_config()
     * I2C ATR

[Severity: Medium]
The commit message states that .get_mbus_config() is supported. However, it
does not appear to be implemented or linked in the max_ser_pad_ops structure.
Was this feature intentionally removed during development, or did it get missed?

[ ... ]

diff --git a/drivers/media/i2c/maxim-serdes/max_ser.c b/drivers/media/i2c/maxim-serdes/max_ser.c
new file mode 100644
index 0000000000000..9eb33cf83c076
--- /dev/null
+++ b/drivers/media/i2c/maxim-serdes/max_ser.c
@@ -0,0 +1,2155 @@

[ ... ]

> +static int max_ser_route_to_hw(struct max_ser_priv *priv,
> +			       struct v4l2_subdev_state *state,
> +			       struct v4l2_subdev_route *route,
> +			       struct max_ser_route_hw *hw)
> +{
> +	struct max_ser *ser = priv->ser;
> +	struct v4l2_mbus_frame_desc fd;
> +	struct max_ser_phy *phy;

[Severity: Critical]
Does this leave the num_entries field of fd uninitialized? 

Since fd is declared on the stack, it may contain random garbage. When passed
to the source subdev's get_frame_desc pad op, the target function frequently
executes fd->entry[fd->num_entries++] = hw.entry; which could result in
out-of-bounds writes on the stack buffer. Would it be better to
zero-initialize it here?

> +	unsigned int i;
> +	int ret;
> +
> +	memset(hw, 0, sizeof(*hw));

[ ... ]

> +static void max_ser_i2c_atr_detach_addr(struct i2c_atr *atr, u32 chan_id, u16 addr)
> +{
> +	struct max_ser_priv *priv = i2c_atr_get_driver_data(atr);
> +	struct max_ser *ser = priv->ser;
> +	struct max_serdes_i2c_xlate xlate = { 0 };
> +	unsigned int i;
> +
> +	/* Find index of matching I2C translation. */
> +	for (i = 0; i < ser->ops->num_i2c_xlates; i++)
> +		if (ser->i2c_xlates[i].dst == addr)
> +			break;
> +
> +	WARN_ON(i == ser->ops->num_i2c_xlates);
> +
> +	ser->ops->set_i2c_xlate(ser, i, &xlate);
> +	ser->i2c_xlates[i] = xlate;
> +}

[Severity: High]
If the loop finishes without finding a match, i will be exactly equal to
ser->ops->num_i2c_xlates. While the WARN_ON flags this case, execution continues
and accesses ser->i2c_xlates[i]. Can this cause an out-of-bounds write past
the end of the i2c_xlates array? Should the function return early if the
address is not found?

[ ... ]

> +static int max_ser_i2c_atr_init(struct max_ser_priv *priv)
> +{
> +	struct i2c_atr_adap_desc desc = {
> +		.chan_id = 0,
> +	};
> +
> +	if (!i2c_check_functionality(priv->client->adapter,
> +				     I2C_FUNC_SMBUS_WRITE_BYTE_DATA))
> +		return -ENODEV;
> +
> +	priv->atr = i2c_atr_new(priv->client->adapter, priv->dev,
> +				&max_ser_i2c_atr_ops, 1, 0);
> +	if (IS_ERR(priv->atr))
> +		return PTR_ERR(priv->atr);
> +
> +	i2c_atr_set_driver_data(priv->atr, priv);
> +
> +	return i2c_atr_add_adapter(priv->atr, &desc);
> +}

[Severity: Medium]
If i2c_atr_add_adapter() fails, does this permanently leak the memory
allocated by i2c_atr_new()?

[ ... ]

> +static int max_ser_i2c_adapter_init(struct max_ser_priv *priv)
> +{
> +	if (device_get_named_child_node(priv->dev, "i2c-gate"))
> +		return max_ser_i2c_mux_init(priv);
> +	else
> +		return max_ser_i2c_atr_init(priv);
> +}

[Severity: Medium]
Does device_get_named_child_node() return a reference-counted fwnode
pointer? It appears the pointer is used here only for a truthiness check and
is immediately discarded without calling fwnode_handle_put(). Can this lead
to a reference count leak during driver probe?

[ ... ]

> +static int max_ser_log_status(struct v4l2_subdev *sd)
> +{
> +	struct max_ser_priv *priv = sd_to_priv(sd);
> +	struct max_ser *ser = priv->ser;
> +	unsigned int i, j;
> +	int ret;

[ ... ]

> +	for (i = 0; i < ser->ops->num_pipes; i++) {
> +		struct max_ser_pipe *pipe = &ser->pipes[i];
> +
> +		v4l2_info(sd, "pipe: %u\n", pipe->index);
> +		v4l2_info(sd, "\tenabled: %u\n", pipe->enabled);
> +
> +		if (!pipe->enabled) {
> +			v4l2_info(sd, "\n");
> +			continue;
> +		}
> +
> +		v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id);
> +		v4l2_info(sd, "\tstream_id: %u\n", pipe->stream_id);
> +		if (ser->ops->set_pipe_phy)
> +			v4l2_info(sd, "\tphy_id: %u\n", pipe->phy_id);
> +		if (ser->ops->set_pipe_dt) {
> +			v4l2_info(sd, "\tdts: %u\n", pipe->num_dts);
> +			for (j = 0; j < pipe->num_dts; j++)
> +				v4l2_info(sd, "\t\tdt: 0x%02x\n", pipe->dts[j]);
> +		}

[Severity: High]
Is this iteration over pipe->dts safe from data races? 

It looks like max_ser_log_status() iterates locklessly here, but 
max_ser_update_pipe() dynamically frees and reallocates pipe->dts 
using devm_kfree(priv->dev, pipe->dts) during runtime stream configuration. 
Could concurrent execution of these two code paths trigger a use-after-free?

[ ... ]

> +static int max_ser_get_pipe_vcs_dts(struct max_ser_priv *priv,
> +				    struct v4l2_subdev_state *state,
> +				    struct max_ser_pipe *pipe,
> +				    unsigned int *vcs,
> +				    unsigned int *dts, unsigned int *num_dts,
> +				    u64 *streams_masks)
> +{
> +	struct v4l2_subdev_route *route;
> +	struct max_ser *ser = priv->ser;
> +	unsigned int i;
> +	int ret;
> +
> +	*vcs = 0;
> +	*num_dts = 0;

[ ... ]

> +		/* Skip already added DT. */
> +		for (i = 0; i < *num_dts; i++)
> +			if (dts[i] == dt)
> +				break;
> +
> +		if (i < *num_dts)
> +			continue;
> +
> +		dts[*num_dts] = dt;
> +		(*num_dts)++;
> +	}

[Severity: High]
Is there a risk of an out-of-bounds array write here? 

The dts array is dynamically allocated with a fixed capacity of 
ser->ops->num_dts_per_pipe. If a userspace-configured routing table supplies 
more unique streams than this limit, it seems *num_dts could exceed the 
bounds of the array. Should we add a bounds check?

[ ... ]

> +static int max_ser_get_pipe_mode(struct max_ser_priv *priv,
> +				 struct v4l2_subdev_state *state,
> +				 struct max_ser_pipe *pipe,
> +				 struct max_ser_pipe_mode *mode)
> +{
> +	struct v4l2_subdev_route *route;
> +	struct max_ser *ser = priv->ser;
> +	bool force_set_bpp = false;
> +	unsigned int doubled_bpp;
> +	unsigned int min_bpp;
> +	unsigned int max_bpp;
> +	u32 bpps = 0;
> +	int ret;
> +
> +	if (ser->mode != MAX_SERDES_GMSL_PIXEL_MODE)
> +		return 0;
> +
> +	for_each_active_route(&state->routing, route) {

[ ... ]

> +	}
> +
> +	ret = max_serdes_process_bpps(priv->dev, bpps, priv->double_bpps, &doubled_bpp);
> +	if (ret)
> +		return ret;
> +
> +	if (doubled_bpp == 8)
> +		mode->dbl8 = true;
> +	else if (doubled_bpp == 10)
> +		mode->dbl10 = true;
> +	else if (doubled_bpp == 12)
> +		mode->dbl12 = true;
> +
> +	if (doubled_bpp) {
> +		bpps &= ~BIT(doubled_bpp);
> +		bpps |= BIT(doubled_bpp * 2);
> +	}
> +
> +	min_bpp = __ffs(bpps);
> +	max_bpp = __fls(bpps);

[Severity: High]
If the pipe has no active routes, bpps will remain 0. Calling 
max_serdes_process_bpps() with bpps = 0 appears to return 0 immediately 
without setting doubled_bpp, leaving it uninitialized for later use.
        
Furthermore, if bpps is 0, does calling __ffs(bpps) and __fls(bpps) 
result in undefined behavior? These typically map to __builtin_ctzl(), which 
may cause faults or unpredictable behavior when called with zero.

[ ... ]

> +static int max_ser_update_pipe(struct max_ser_priv *priv,
> +			       struct max_ser_pipe *pipe,
> +			       struct v4l2_subdev_state *state,
> +			       u64 *streams_masks)
> +{
> +	struct max_ser *ser = priv->ser;
> +	struct max_ser_pipe_mode mode = { 0 };
> +	unsigned int num_dts;
> +	unsigned int *dts;
> +	unsigned int vcs;
> +	int ret;
> +
> +	if (!ser->ops->num_dts_per_pipe)
> +		return 0;
> +
> +	dts = devm_kcalloc(priv->dev, ser->ops->num_dts_per_pipe, sizeof(*dts),
> +			   GFP_KERNEL);

[Severity: Medium]
Is devm_kcalloc() suitable for use in a runtime hot path like stream 
enablement/disablement? 
        
Using devres functions here requires taking a spinlock and performing an O(N) 
list traversal for every allocation and free, which can degrade performance 
and fragment the list.

[Severity: High]
Additionally, error rollback paths like max_ser_update_phys() call 
max_ser_update_pipe() to restore previous states. If this devm_kcalloc() 
fails during a rollback, the error is ignored, leaving the hardware and 
software states out of sync. Could this be mitigated by pre-allocating the 
arrays during probe?

[ ... ]

> +static int max_ser_update_streams(struct v4l2_subdev *sd,
> +				  struct v4l2_subdev_state *state,
> +				  u32 pad, u64 updated_streams_mask, bool enable)
> +{
> +	struct max_ser_priv *priv = v4l2_get_subdevdata(sd);
> +	struct max_ser *ser = priv->ser;
> +	unsigned int num_pads = max_ser_num_pads(ser);
> +	u64 *streams_masks;
> +	int ret;
> +
> +	ret = max_serdes_get_streams_masks(priv->dev, state, pad, updated_streams_mask,
> +					   num_pads, priv->streams_masks, &streams_masks,
> +					   enable);

[ ... ]

> +	devm_kfree(priv->dev, priv->streams_masks);
> +	priv->streams_masks = streams_masks;
> +	ser->active = !!streams_masks[pad];
> +
> +	return 0;

[Severity: Medium]
Similar to the dts array allocation above, is it acceptable to continually
allocate and free streams_masks via devres APIs during stream start/stop
operations?

[ ... ]

> +static void max_ser_v4l2_unregister(struct max_ser_priv *priv)
> +{
> +	struct v4l2_subdev *sd = &priv->sd;
> +
> +	max_ser_v4l2_notifier_unregister(priv);
> +	v4l2_async_unregister_subdev(sd);
> +	v4l2_subdev_cleanup(sd);
> +	media_entity_cleanup(&sd->entity);
> +}

[Severity: Medium]
The V4L2 control handler is initialized during probe with 
v4l2_ctrl_handler_init(), but it doesn't seem to be freed here. Should we 
call v4l2_ctrl_handler_free(&priv->ctrl_handler) to prevent memory leaks 
upon module unregistration?

[ ... ]

> +static int max_ser_parse_sink_dt_endpoint(struct max_ser_priv *priv,
> +					  struct max_ser_phy *phy,
> +					  struct max_serdes_source *source,
> +					  struct fwnode_handle *fwnode)
> +{
> +	struct max_ser *ser = priv->ser;
> +	u32 pad = max_ser_phy_to_pad(ser, phy);
> +	struct v4l2_fwnode_endpoint v4l2_ep = { .bus_type = V4L2_MBUS_CSI2_DPHY };
> +	struct fwnode_handle *ep;
> +	int ret;
> +
> +	ep = fwnode_graph_get_endpoint_by_id(fwnode, pad, 0, 0);
> +	if (!ep)
> +		return 0;
> +
> +	source->ep_fwnode = fwnode_graph_get_remote_endpoint(ep);
> +	if (!source->ep_fwnode) {
> +		dev_err(priv->dev,
> +			"Failed to get remote endpoint on port %u\n", pad);
> +		return -EINVAL;
> +	}

[Severity: Medium]
If fwnode_graph_get_remote_endpoint() fails, the function returns -EINVAL 
without putting the ep fwnode pointer. Could this cause a fwnode reference 
count leak?

[ ... ]

> +static int max_ser_read_reg(struct i2c_adapter *adapter, u8 addr,
> +			    u16 reg, u8 *val)
> +{
> +	u8 buf[2] = { reg >> 8, reg & 0xff };
> +	struct i2c_msg msg[2] = {
> +		{
> +			.addr = addr,
> +			.flags = 0,
> +			.buf = buf,
> +			.len = sizeof(buf),
> +		},
> +		{
> +			.addr = addr,
> +			.flags = I2C_M_RD,
> +			.buf = buf,
> +			.len = 1,
> +		},
> +	};
> +	int ret;
> +
> +	ret = i2c_transfer(adapter, msg, ARRAY_SIZE(msg));
> +	if (ret < 0)
> +		return ret;
> +
> +	*val = buf[0];
> +
> +	return 0;
> +}

[Severity: High]
Is it safe to pass a stack-allocated buffer to i2c_transfer()? 
        
Because many underlying I2C controller drivers utilize DMA for data transfers, 
passing stack-allocated memory can be unsafe and trigger panics or data 
corruption, especially with CONFIG_VMAP_STACK enabled.

> +static int max_ser_write_reg(struct i2c_adapter *adapter, u8 addr,
> +			     u16 reg, u8 val)
> +{
> +	u8 buf[3] = { reg >> 8, reg & 0xff, val };
> +	struct i2c_msg msg[1] = {
> +		{
> +			.addr = addr,
> +			.flags = 0,
> +			.buf = buf,
> +			.len = sizeof(buf),
> +		},
> +	};
> +	int ret;
> +
> +	ret = i2c_transfer(adapter, msg, ARRAY_SIZE(msg));
> +	if (ret < 0)
> +		return ret;
> +
> +	return 0;
> +}

[Severity: High]
Similarly, does this stack-allocated buffer face the same DMA safety issues 
when passed to i2c_transfer()?

-- 
Sashiko AI review · https://sashiko.dev/#/patchset/20260521-gmsl2-3_serdes-v12-0-b26d92931196@analog.com?part=14

  reply	other threads:[~2026-05-21 13:23 UTC|newest]

Thread overview: 35+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2026-05-21 12:44 [PATCH v12 00/22] media: i2c: add Maxim GMSL2/3 serializer and deserializer drivers Dumitru Ceclan via B4 Relay
2026-05-21 12:44 ` [PATCH v12 01/22] media: mc: Add INTERNAL pad flag Dumitru Ceclan via B4 Relay
2026-05-21 13:19   ` sashiko-bot
2026-05-21 12:44 ` [PATCH v12 02/22] dt-bindings: media: i2c: max96717: add support for I2C ATR Dumitru Ceclan via B4 Relay
2026-05-21 13:20   ` sashiko-bot
2026-05-21 12:44 ` [PATCH v12 03/22] dt-bindings: media: i2c: max96717: add support for pinctrl/pinconf Dumitru Ceclan via B4 Relay
2026-05-21 12:44 ` [PATCH v12 04/22] dt-bindings: media: i2c: max96717: add support for MAX9295A Dumitru Ceclan via B4 Relay
2026-05-21 12:44 ` [PATCH v12 05/22] dt-bindings: media: i2c: max96717: add support for MAX96793 Dumitru Ceclan via B4 Relay
2026-05-21 12:44 ` [PATCH v12 06/22] dt-bindings: media: i2c: max96712: use pattern properties for ports Dumitru Ceclan via B4 Relay
2026-05-21 12:44 ` [PATCH v12 07/22] dt-bindings: media: i2c: max96712: add support for I2C ATR Dumitru Ceclan via B4 Relay
2026-05-21 13:19   ` sashiko-bot
2026-05-21 12:44 ` [PATCH v12 08/22] dt-bindings: media: i2c: max96712: add support for POC supplies Dumitru Ceclan via B4 Relay
2026-05-21 12:44 ` [PATCH v12 09/22] dt-bindings: media: i2c: max96712: add support for MAX96724F/R Dumitru Ceclan via B4 Relay
2026-05-21 12:44 ` [PATCH v12 10/22] dt-bindings: media: i2c: max96712: add control-channel-port property Dumitru Ceclan via B4 Relay
2026-05-21 13:02   ` sashiko-bot
2026-05-21 12:44 ` [PATCH v12 11/22] dt-bindings: media: i2c: max96714: add support for MAX96714R Dumitru Ceclan via B4 Relay
2026-05-21 12:44 ` [PATCH v12 12/22] dt-bindings: media: i2c: add MAX9296A, MAX96716A, MAX96792A Dumitru Ceclan via B4 Relay
2026-05-21 13:01   ` sashiko-bot
2026-05-21 12:44 ` [PATCH v12 13/22] media: i2c: add Maxim GMSL2/3 serializer and deserializer framework Dumitru Ceclan via B4 Relay
2026-05-21 13:12   ` sashiko-bot
2026-05-21 12:44 ` [PATCH v12 14/22] media: i2c: add Maxim GMSL2/3 serializer framework Dumitru Ceclan via B4 Relay
2026-05-21 13:23   ` sashiko-bot [this message]
2026-05-21 12:44 ` [PATCH v12 15/22] media: i2c: add Maxim GMSL2/3 deserializer framework Dumitru Ceclan via B4 Relay
2026-05-21 13:27   ` sashiko-bot
2026-05-21 12:44 ` [PATCH v12 16/22] media: i2c: maxim-serdes: add MAX96717 driver Dumitru Ceclan via B4 Relay
2026-05-21 13:33   ` sashiko-bot
2026-05-21 12:44 ` [PATCH v12 17/22] media: i2c: maxim-serdes: add MAX96724 driver Dumitru Ceclan via B4 Relay
2026-05-21 13:36   ` sashiko-bot
2026-05-21 12:44 ` [PATCH v12 18/22] media: i2c: maxim-serdes: add MAX9296A driver Dumitru Ceclan via B4 Relay
2026-05-21 13:34   ` sashiko-bot
2026-05-21 12:44 ` [PATCH v12 19/22] arm64: defconfig: disable deprecated MAX96712 driver Dumitru Ceclan via B4 Relay
2026-05-21 13:14   ` sashiko-bot
2026-05-21 12:44 ` [PATCH v12 20/22] staging: media: remove " Dumitru Ceclan via B4 Relay
2026-05-21 12:44 ` [PATCH v12 21/22] media: i2c: remove MAX96717 driver Dumitru Ceclan via B4 Relay
2026-05-21 12:44 ` [PATCH v12 22/22] media: i2c: remove MAX96714 driver Dumitru Ceclan via B4 Relay

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=20260521132339.5914C1F000E9@smtp.kernel.org \
    --to=sashiko-bot@kernel.org \
    --cc=conor+dt@kernel.org \
    --cc=devicetree@vger.kernel.org \
    --cc=devnull+dumitru.ceclan.analog.com@kernel.org \
    --cc=robh@kernel.org \
    --cc=sashiko-reviews@lists.linux.dev \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox