[PATCH] staging: nvec: fix out-of-bounds string copies

Dang Vu Duc Hien <[email protected]> Thu, 30 Jul 2026 11:12:20 +0700
Newsgroups org.kernel.vger.linux-tegra,dev.linux.lists.linux-staging,org.kernel.vger.linux-kernel
Message-ID <[email protected]>
The function `nvec_power_bat_notifier()` assumes that `res->length` is
both >= 2 and within the battery info buffers.
Technically, `res->length` is provided by the embedded controller (EC)
and is unverified (ie. a malformed response). If `res->length` < 2, the
expression `res->length - 2` evaluates to a massive size_t value which
is passed to `memcpy()`, causing severe memory corruption. Conversely,
`res->length - 2` being larger than the buffer also causes an
out-of-bounds write.

Introduce helper function `nvec_bat_str_copy()` to safely handle these
string copies by:
1. Ensuring that it returns an error to the upper layer along with a
   `dev_warn()` message when `res->length` < 2.
2. Ensuring that it provides toleration by copying as much as it can
   along with a `dev_warn()` when `res->length - 2` is too big.
3. Guaranteeing a properly sized, null-terminated destination string if
   the copy succeeds.

Signed-off-by: Dang Vu Duc Hien <[email protected]>
---
Note: Coincidentally, I have been working on this exact issue earlier
and had this patch prepared just before Lucas Jeffrey submitted his RFC.
I am sending it here to answer his protocol validation question with code.
---
 drivers/staging/nvec/nvec_power.c | 41 ++++++++++++++++++++++++++-----
 1 file changed, 35 insertions(+), 6 deletions(-)

diff --git a/drivers/staging/nvec/nvec_power.c b/drivers/staging/nvec/nvec_power.c
index c514d51a9e57..12699515356d 100644
--- a/drivers/staging/nvec/nvec_power.c
+++ b/drivers/staging/nvec/nvec_power.c
@@ -117,6 +117,32 @@ static void get_bat_mfg_data(struct nvec_power *power)
 	}
 }
 
+static int nvec_bat_str_copy(struct nvec_power *power,
+			     struct bat_response *res, char *dst,
+			     unsigned int dst_size, const char *prop_name)
+{
+	unsigned int len;
+
+	if (res->length < 2) {
+		dev_warn(power->nvec->dev,
+			 "battery %s response length too short (%u)\n",
+			 prop_name, res->length);
+		return -EINVAL;
+	}
+
+	len = res->length - 2;
+	if (len >= dst_size) {
+		dev_warn(power->nvec->dev,
+			 "battery %s string too long (%u)\n",
+			 prop_name, len);
+		len = dst_size - 1;
+	}
+
+	memcpy(dst, &res->plc, len);
+	dst[len] = '\0';
+	return 0;
+}
+
 static int nvec_power_bat_notifier(struct notifier_block *nb,
 				   unsigned long event_type, void *data)
 {
@@ -193,16 +219,19 @@ static int nvec_power_bat_notifier(struct notifier_block *nb,
 		power->bat_temperature = res->plu - 2732;
 		break;
 	case MANUFACTURER:
-		memcpy(power->bat_manu, &res->plc, res->length - 2);
-		power->bat_manu[res->length - 2] = '\0';
+		if (nvec_bat_str_copy(power, res, power->bat_manu,
+				      sizeof(power->bat_manu), "manufacturer") < 0)
+			break;
 		break;
 	case MODEL:
-		memcpy(power->bat_model, &res->plc, res->length - 2);
-		power->bat_model[res->length - 2] = '\0';
+		if (nvec_bat_str_copy(power, res, power->bat_model,
+				      sizeof(power->bat_model), "model") < 0)
+			break;
 		break;
 	case TYPE:
-		memcpy(power->bat_type, &res->plc, res->length - 2);
-		power->bat_type[res->length - 2] = '\0';
+		if (nvec_bat_str_copy(power, res, power->bat_type,
+				      sizeof(power->bat_type), "type") < 0)
+			break;
 		/*
 		 * This differs a little from the spec fill in more if you find
 		 * some.
-- 
2.55.0