From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-ej1-f51.google.com (mail-ej1-f51.google.com [209.85.218.51]) (using TLSv1.2 with cipher ECDHE-RSA-AES128-GCM-SHA256 (128/128 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 9BB9623817D for ; Tue, 14 Oct 2025 07:31:58 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.218.51 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760427124; cv=none; b=OHynxremo3Mq9tBp0KniQAByQEuqbIJMy/5maoN750RtLmBQIzoh5xA8sdYbPz1GbgqUN+Oj8Tl659DSmgnVrT4tmd4d2PHeWkOLJCRVW/F4VoOO9c5iDVxEpm6Blh9IvE0X6iThYBpsu108SInF0ppMjtDQDQdl33fVZK8wQiQ= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1760427124; c=relaxed/simple; bh=dVCZrCuq+ZctBuff8YlX99sbsYb/Y49Q+wQM+I7d7wo=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=miJbF/lDbCYw6HBUrx7QwupxjMeMXrBccW34q7/4607OJN/A40/ptE69eCv4Xqs5bPmp0fhU9vRHWKp2HO0paZBKd80ZOCe3m9jCr9upCNb1Y0JRJurvQttqC31E5CvSmjfhbH7chSJbPw23maev4rjYm+w2umqgreu+C9iFIQg= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com; spf=pass smtp.mailfrom=suse.com; dkim=pass (2048-bit key) header.d=suse.com header.i=@suse.com header.b=PEDUWIiB; arc=none smtp.client-ip=209.85.218.51 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=quarantine dis=none) header.from=suse.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=suse.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=suse.com header.i=@suse.com header.b="PEDUWIiB" Received: by mail-ej1-f51.google.com with SMTP id a640c23a62f3a-b5a8184144dso197178566b.1 for ; Tue, 14 Oct 2025 00:31:58 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=suse.com; s=google; t=1760427117; x=1761031917; darn=vger.kernel.org; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:from:to:cc:subject:date:message-id:reply-to; bh=DXZZGW1uWPjZtrugE9+8JJyhpGziKLqQludy7yN6pDk=; b=PEDUWIiB0rpNXVzxThn6ZSjTGHuWxtsGzfq9nHct+xtJK1RAYcPRWoG9RZGwvk4xOW CZUmykrSphqB+NteNfSsv3iuHXyG4XIsLKi2kBKpIkCUI9JDGPrVCYn29VdsLOHB9SHt bPoGy+KXfdWwGJWgnbvspe1W4oogzv4od4Va/hKkpO4VxolRo7flYi3Kr9oGzlzElh3+ 31N1cr6km49qF/RSEE44EVQPMeTCRTTmBRP5jPFliMpixkmtbfMGnwyEu3d+WM1guKRg pM4NtRg+oyEMmBFPcN5S0emQFBRzq6Gt5/GW8K7SSNJeU3ewVBqbKxMNT1xyvMFcelcC MKZA== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1760427117; x=1761031917; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=DXZZGW1uWPjZtrugE9+8JJyhpGziKLqQludy7yN6pDk=; b=Y8bNg3Bho/B4xxIVgd7/4IzzQta27mngeDb9m+qRPqptdvBIYblXfqORLdiSSn/oSw Y+1MOs0263Y9hycEHsMrPWlaBp8sBJWlu2/J/FAYgDXRH/C9QOpiRudbSAAMUyULZdTG EnDS1uTQT3muHJK6P9zCCmsaOo5t7S9wDiQICakZWGo9buGeR5zgBuiRkujEAvkg/Mbx Dcn+coUYIhfKPAmw489rbSq+sg4BdRMqNAnUNvZ/2OXT6pIOMM8NWeUI/6jj9gapfoqz OXrjVko8pOwFM6FYpLFbGWgNzkOrE31cVin8vnZ+95pYgUm1tCXqYL7sOQR5L5rMZPs7 ilyA== X-Forwarded-Encrypted: i=1; AJvYcCWnJdn/K/QNwifkl4j7xUgy59Oo2zBbcXOMiqbQeoyzzFeTLBJa2kV2meXN0fIEZK1TdE492TZ8xlwc@vger.kernel.org X-Gm-Message-State: AOJu0YxuOz71PXAclT+S2sLDE7PGcqvU5++IOiIxW795SGQ+NF0i7LR5 Bjb7D2284MbbyFW3MLg33BoqhtXKB4aBR8LWUX1UQmKmwRknnLUHo8qDuYBqNLTpZsg= X-Gm-Gg: ASbGncuRaOTh2//P2wzeHXUABN+jufCaw1y/lvU7RILoAp7WdmEdAnJUxKd9Zcl2hof 2qP8OSv4ueXKRfnf2X5VnuRd1zlUJRC7tKJLhAXzxQyU+MhscEum9zkpo2JXkCg8WEUBORqHj6v QpbxgauRAHFWrkO+JGR0EIrtyRAv7q7pC2s93ze1y68CVCyIHb/2YB3pz9bT4eYeb0ni8RNfV0L 1+8pGpdo64XDnE4F2MYnysymVtjnFW/9BOOlfQWpvsr2PN86yd30ELDeWEwwWCZGkg5tslosmaQ q13Xwha8sFIxLPq3PNC1EcoUdSms7O0CjVkM6HEuiIGvrtP6gsMTF7R8OzQ3LwUQsbxgPzRt4DT KpHgWuVZjxAciLOI/9cmGhSlWFFg1pW4CGLBkIIaT5kYKz/EURKgdeqPFbww6GO3HBxme3pZAAb qV057lkKYrHt4= X-Google-Smtp-Source: AGHT+IHKJefVpKUeKL/wI+HS+0ovCB1d0pVYRlAQVBL4u1wtot93EtcuA0IP5nStKOmwdm/Nsj0N5A== X-Received: by 2002:a17:907:7213:b0:b41:a571:21b0 with SMTP id a640c23a62f3a-b50ac1cc421mr2370839166b.39.1760427116641; Tue, 14 Oct 2025 00:31:56 -0700 (PDT) Received: from localhost (host-95-247-55-253.retail.telecomitalia.it. [95.247.55.253]) by smtp.gmail.com with ESMTPSA id a640c23a62f3a-b55d8c12ccbsm1083131366b.46.2025.10.14.00.31.55 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Tue, 14 Oct 2025 00:31:56 -0700 (PDT) From: Andrea della Porta To: Rob Herring , Saravana Kannan , devicetree@vger.kernel.org, linux-kernel@vger.kernel.org, iivanov@suse.de, svarbanov@suse.de, mbrugger@suse.com, Phil Elwell Cc: Andrea della Porta Subject: [PATCH] of: reserved_mem: Add heuristic to validate reserved memory regions Date: Tue, 14 Oct 2025 09:34:03 +0200 Message-ID: <20251014073403.32134-1-andrea.porta@suse.com> X-Mailer: git-send-email 2.51.0 Precedence: bulk X-Mailing-List: devicetree@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit When parsing static reserved-memory DT nodes, any node with a reg property length that is not perfectly conformant is discarded. Specifically, any reg property whose length is not a multiple of the parent's (#address-cells + #size-cells) is dropped. Relax this condition (while still treating perfect multiples as having higher precedence) by allowing regions that are subsets of the parent's addressable space to be considered for inclusion. For example, in the following scenario: / { #address-cells = <0x02>; #size-cells = <0x02>; ... reserved-memory { #address-cells = <0x02>; #size-cells = <0x02>; ... nvram { reg = <0x00 0x3fd16d00 0x37>; ... }; }; }; Even though the reg property of the nvram node is not well-formed from a DT syntax perspective, it still references a perfectly valid memory region of 0x37 bytes that should be reserved. This has at least one real-world equivalent on the Raspberry Pi 5, for example, on which the firmware incorrectly overwrites the nvram node's reg property without taking into account the actual value of the parent's #size-cells. Signed-off-by: Andrea della Porta --- The aforementioned heuristic has been tested with several combo of #address, #size and reg length and the results are shown in the following table: Testing #address-cells 1, #size-cells 1 len |t_len |addr |size |#ignore 0 |8 | INVALID 4 |8 | INVALID 8 |8 |1 |1 |0 12 |8 | INVALID 16 |8 |1 |1 |1 20 |8 | INVALID 24 |8 |1 |1 |2 28 |8 | INVALID 32 |8 |1 |1 |3 36 |8 | INVALID 40 |8 |1 |1 |4 44 |8 | INVALID 48 |8 |1 |1 |5 52 |8 | INVALID 56 |8 |1 |1 |6 60 |8 | INVALID 64 |8 |1 |1 |7 68 |8 | INVALID 72 |8 |1 |1 |8 76 |8 | INVALID 80 |8 |1 |1 |9 84 |8 | INVALID 88 |8 |1 |1 |10 92 |8 | INVALID 96 |8 |1 |1 |11 Testing #address-cells 2, #size-cells 1 len |t_len |addr |size |#ignore 0 |12 | INVALID 4 |12 | INVALID 8 |12 | INVALID 12 |12 |2 |1 |0 16 |12 | INVALID 20 |12 | INVALID 24 |12 |2 |1 |1 28 |12 | INVALID 32 |12 | INVALID 36 |12 |2 |1 |2 40 |12 | INVALID 44 |12 | INVALID 48 |12 |2 |1 |3 52 |12 | INVALID 56 |12 | INVALID 60 |12 |2 |1 |4 64 |12 | INVALID 68 |12 | INVALID 72 |12 |2 |1 |5 76 |12 | INVALID 80 |12 | INVALID 84 |12 |2 |1 |6 88 |12 | INVALID 92 |12 | INVALID 96 |12 |2 |1 |7 Testing #address-cells 2, #size-cells 2 len |t_len |addr |size |#ignore 0 |16 | INVALID 4 |16 | INVALID 8 |16 | INVALID 12 |16 |2 |1 |0 16 |16 |2 |2 |0 20 |16 | INVALID 24 |16 |2 |1 |1 28 |16 | INVALID 32 |16 |2 |2 |1 36 |16 |2 |1 |2 40 |16 | INVALID 44 |16 | INVALID 48 |16 |2 |2 |2 52 |16 | INVALID 56 |16 | INVALID 60 |16 |2 |1 |4 64 |16 |2 |2 |3 68 |16 | INVALID 72 |16 |2 |1 |5 76 |16 | INVALID 80 |16 |2 |2 |4 84 |16 |2 |1 |6 88 |16 | INVALID 92 |16 | INVALID 96 |16 |2 |2 |5 drivers/of/of_reserved_mem.c | 88 ++++++++++++++++++++++++++++++++---- 1 file changed, 78 insertions(+), 10 deletions(-) diff --git a/drivers/of/of_reserved_mem.c b/drivers/of/of_reserved_mem.c index 2e9ea751ed2d..f94069ef988e 100644 --- a/drivers/of/of_reserved_mem.c +++ b/drivers/of/of_reserved_mem.c @@ -130,6 +130,68 @@ static void __init fdt_reserved_mem_save_node(unsigned long node, const char *un return; } +/** + * reg_len_valid() - scan for a suitable #size-cells value and validate + * the reg property length + * @len: Length of the reg property to be validated (bytes). Can be composed + * by more than one reg addr+size pairs + * @calc_addr: the number of address cells expected by the parent of the node + * containing the reg property. Currently it's just used as in param + * @calc_size: the number of size cells expected by the parent of the node + * containing the reg property (out param) + * + * This function tries to find the correct #size-cells number for the size portion + * of a reg property, assuming the #address-cells passed in (calc_addr param) + * is valid to avoid ambiguity. + * Parent_len is the length in bytes of a single reg property * as expected by the + * parent. + * Either len is a multiple of parent_len, in which case there's no adjustment to + * be made to calc_size and the region is automatically valid (this choice has the + * priority), or it is not a multiple. + * In the latter case, it finds the smallest calc_addr+calc_size for which len + * is still a multiple and adjust calc_size accordingly. + * The rationale is to avoid nonsensical combo e.g. #adress-cells 1 and #size-cells + * 2 since it's not fully addressable, and to promote any other combo that is a + * subset of the original space, e.g. with calc_addr=2 and calc_size=2, returning + * calc_size=1 still makes sense since the region is included in the parent space. + * The reason for that is to avoid dropping perfectly valid memory regions that + * could just have been passed with the wrong format in the reg property (some fw + * are reportedly doing that when updating the DT at boot). + * + * Returns: true if the region is valid and can be further processed, + * false otherwise. If valid, calc_size is filled with the actual + * length (in cells) of the size part. + * + */ +static bool reg_len_valid(int len, const int *calc_addr, int *calc_size) +{ + int parent_len = (*calc_addr + *calc_size) * sizeof(__be32); + bool parent_multiple = (len % parent_len) / sizeof(__be32); + int row_n, calc_row_len = parent_len / sizeof(__be32); + int len_b = len / sizeof(__be32); + + if (!len || !parent_len) + return false; + + for (row_n = len_b / 2; row_n > 0; row_n--) { + int tmp_row_len = len_b / row_n; + + if (calc_row_len > tmp_row_len && + tmp_row_len > *calc_addr && + (len_b % tmp_row_len == 0)) + calc_row_len = tmp_row_len; + } + + if (parent_multiple && calc_row_len != parent_len / sizeof(__be32)) { + *calc_size = calc_row_len - *calc_addr; + return true; + } else if (!parent_multiple) { + return true; + } + + return false; +} + static int __init early_init_dt_reserve_memory(phys_addr_t base, phys_addr_t size, bool nomap) { @@ -154,9 +216,9 @@ static int __init early_init_dt_reserve_memory(phys_addr_t base, static int __init __reserved_mem_reserve_reg(unsigned long node, const char *uname) { - int t_len = (dt_root_addr_cells + dt_root_size_cells) * sizeof(__be32); + int calc_addr, calc_size; phys_addr_t base, size; - int len; + int len, t_len; const __be32 *prop; bool nomap; @@ -164,17 +226,20 @@ static int __init __reserved_mem_reserve_reg(unsigned long node, if (!prop) return -ENOENT; - if (len && len % t_len != 0) { + calc_addr = dt_root_addr_cells; + calc_size = dt_root_size_cells; + if (!reg_len_valid(len, &calc_addr, &calc_size)) { pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n", uname); return -EINVAL; } nomap = of_get_flat_dt_prop(node, "no-map", NULL) != NULL; + t_len = (calc_addr + calc_size) * (int)sizeof(__be32); while (len >= t_len) { - base = dt_mem_next_cell(dt_root_addr_cells, &prop); - size = dt_mem_next_cell(dt_root_size_cells, &prop); + base = dt_mem_next_cell(calc_addr, &prop); + size = dt_mem_next_cell(calc_size, &prop); if (size && early_init_dt_reserve_memory(base, size, nomap) == 0) { /* Architecture specific contiguous memory fixup. */ @@ -255,6 +320,7 @@ void __init fdt_scan_reserved_mem_reg_nodes(void) } fdt_for_each_subnode(child, fdt, node) { + int calc_addr, calc_size; const char *uname; prop = of_get_flat_dt_prop(child, "reg", &len); @@ -263,19 +329,21 @@ void __init fdt_scan_reserved_mem_reg_nodes(void) if (!of_fdt_device_is_available(fdt, child)) continue; + calc_addr = dt_root_addr_cells; + calc_size = dt_root_size_cells; uname = fdt_get_name(fdt, child, NULL); - if (len && len % t_len != 0) { + if (!reg_len_valid(len, &calc_addr, &calc_size)) { pr_err("Reserved memory: invalid reg property in '%s', skipping node.\n", uname); continue; } if (len > t_len) - pr_warn("%s() ignores %d regions in node '%s'\n", - __func__, len / t_len - 1, uname); + pr_warn("%s() ignores %d regions in node '%s'\n", __func__, + len / ((calc_addr + calc_size) * (int)sizeof(__be32)) - 1, uname); - base = dt_mem_next_cell(dt_root_addr_cells, &prop); - size = dt_mem_next_cell(dt_root_size_cells, &prop); + base = dt_mem_next_cell(calc_addr, &prop); + size = dt_mem_next_cell(calc_size, &prop); if (size) fdt_reserved_mem_save_node(child, uname, base, size); -- 2.35.3