From nobody Fri Oct 2 08:26:27 2026 Received: from stravinsky.debian.org (stravinsky.debian.org [82.195.75.108]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id CFB1540801D for ; Mon, 3 Aug 2026 12:43:51 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=82.195.75.108 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785761033; cv=none; b=CtIw1X35w7W7T+8kgF0eQeBgXskTj/XtSQPFSf9Bn/0HccxmmJnZvrYywkWO7kEWtbr+2pn//FA2JQr3o2quDtY3ufUt6p9zxdO8LSuvzD4owM/ws/1u5InfCg4CY/4MEb8leVkJ0D2rvt/czgPpuQzIXbd00FU3eaESsg/MIZA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1785761033; c=relaxed/simple; bh=pomWKg7l4noDyqRugbLncVBJpDkQOVQ2vMRlBLBRWdg=; h=From:Date:Subject:MIME-Version:Content-Type:Message-Id:To:Cc; b=Rz9qbL0wPTS+V1DZgmKLuBzn9T4o0uj70V8k7MaL2catc3VSNqDpedOfwEm3TEby7zgFpl+jMK9cBvrpZ+DOFrsVCcfohLqEYWRKECmKa7wZA1HoKcsxtSUGj5geOr2/wHL5F0HWQk7jgcJE6Lk47DFJvY+4zYwuSDQoAO0WYbc= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org; spf=pass smtp.mailfrom=debian.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b=eG0P7Sgp; arc=none smtp.client-ip=82.195.75.108 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=debian.org Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=debian.org Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=debian.org header.i=@debian.org header.b="eG0P7Sgp" DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debian.org; s=smtpauto.stravinsky; h=X-Debian-User:Cc:To:Message-Id: Content-Transfer-Encoding:Content-Type:MIME-Version:Subject:Date:From: Reply-To:Content-ID:Content-Description:In-Reply-To:References; bh=pozz8qpib4iafMr8P/yl3aNa9zYQsoROn+obz5UP7zw=; b=eG0P7Sgpi6kaMXPiTxV+xUE4+f 8Lv/au4TOBz/KASPHNJUhUKBuLCKSVKb97MUPfsIan3h9nxR8PUUL77iqR89ylwpPuMieOFCQ4LOR U20T0icIIJywK28+fCBPG6v8keZl30uD3BJuWGS8RC34keJeXh6b01VVZYiYKADWlPBRpKSuhHd3L QL8TTebtogi3McRT3GXqZmjw0Tdx23siT1+jlwLlmae27oNwHB3JWvqWSsLhQ52bgLQ3kE7ima8OY NN5aejsCURpQ0BrGk6z1gBwS1xJbVTyKMATqymO00DXV2LkL0cpT6dW9jPkZ3++QJX7c1eV8DTG7N FpBGZVAQ==; Received: from authenticated-user by stravinsky.debian.org with esmtpsa (TLS1.3:ECDHE_X25519__RSA_PSS_RSAE_SHA256__AES_256_GCM:256) (Exim 4.96) (envelope-from ) id 1wqs0R-00CMfi-3C; Mon, 03 Aug 2026 12:43:00 +0000 From: Breno Leitao Date: Mon, 03 Aug 2026 05:41:10 -0700 Subject: [PATCH v3] kexec: keep the next kernel off hardware-poisoned pages Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Type: text/plain; charset="utf-8" Content-Transfer-Encoding: quoted-printable Message-Id: <20260803-kexec_posioned-v3-1-83aa6ede0351@debian.org> X-B4-Tracking: v=1; b=H4sIAGWMcGoC/2XMywqDMBCF4VcJszYlE2+pK9+jlBLNREPBSFKCR Xz3olDoZXs4/7dCpOAoQsNWCJRcdH6ChuUZg37U00DcGWgYSCErUcua32mh/jb7/UiG17LrhC6 wyLWAjMEcyLrlAC/XjMHo4sOH5+En3Nc3pX6phBw5VqJXaFAJS62hzunp5MMAu5XkR5+Lv15y5 PYsDaqyRFsVX/22bS8l9rSr7QAAAA== X-Change-ID: 20260727-kexec_posioned-72bb0a4143a0 To: Andrew Morton , David Hildenbrand , Lorenzo Stoakes , "Liam R. Howlett" , Vlastimil Babka , Mike Rapoport , Suren Baghdasaryan , Michal Hocko , Baoquan He , Pasha Tatashin , Pratyush Yadav , Miaohe Lin , Naoya Horiguchi , pratyush@kernel.org Cc: linux-mm@kvack.org, linux-kernel@vger.kernel.org, kexec@lists.infradead.org, rmikey@meta.com, riel@surriel.com, kernel-team@meta.com, Kiryl Shutsemau , Breno Leitao X-Mailer: b4 0.16-dev-d5d98 X-Developer-Signature: v=1; a=openpgp-sha256; l=7480; i=leitao@debian.org; h=from:subject:message-id; bh=pomWKg7l4noDyqRugbLncVBJpDkQOVQ2vMRlBLBRWdg=; b=owEBbQKS/ZANAwAIATWjk5/8eHdtAcsmYgBqcIzPPRv+oxN927PSThtT3svnsDpyR4vSBgZnt F7W26Ybx9eJAjMEAAEIAB0WIQSshTmm6PRnAspKQ5s1o5Of/Hh3bQUCanCMzwAKCRA1o5Of/Hh3 bV4gD/9+v93z62DBiX+Nv30ybtlwF+ZciqZ3sIK6yQqEEFdTwytF5eADkjD/uIIO91asdFXY+0m Ef2IbY9JgVdLjV/mPZF+0TJbk+Cu4bo5wfFaziBp3RexVZ1twXrIx7Veaf4guWNpIacy7gTXPgh Y7q5SVJxJNZKIgZvSO3dX6jry/lXbS7pL2iy7ZGXxZS4TCnmD/5fIVx0jYGhrtozd4DzE1r5GvM sxNrrHvUTX0X5z305QKML4aW4d/PKoShWpZWaXrc7nJCh9QttElbpEb+jng/ZKJm3zQ0gBTy/KG 1U6/tZKFcTTD9oS8FCtPPI+UHgTEeCY8w9MUD4ef168xtt8g265F6V3HKFZuBIDQWHvDoNYmZEy Nsf7z7QO16dQCFk+uXHbpaOa6hMNbxE/x5OuwvlGcMoEQ6EG5UExZoBkVWZjoG12XWoaXKGW+Mq gkBKFoxPwPvlK/1KFkfskN3yoYZvVU9mv/ON1QKRbjZtpVjRQ0QUIlc3HYs1cnSgE+aBfEelitl EQE/Cb3eK3CZEoXNGJDd7QVQeLSVVYUKl6keigzVvZ5jT0NudiUq5GU0pq7Dwe7laByOxvJl2Do vL8guPnrken8ktRZ4NSz5hwcZfbLAPpW8q5q8rs8dZl5ZQ7P4DMMh8/Hn66aHiNX+6ZUewwnyMl ecKrym23SAxir/A== X-Developer-Key: i=leitao@debian.org; a=openpgp; fpr=AC8539A6E8F46702CA4A439B35A3939FFC78776D X-Debian-User: leitao Memory failures (such as unrecoverable ECCs errors) are getting more and more common. The kernel knows how to handle it while running, marking it as poisoned (and SIGBUS user tasks). Poisoned memory is removed from the buddy allocator, but, not from other places. A current problem is that kexec will load new kernel on top of a bad/poisoned memory, which is undesirable. If the next kernel's image, initrd or purgatory lands on poisoned frame, the relocation copy writes to the bad memory and the machine checks during the kexec. Skip hardware-poisoned frames when placing segments: check them in the kexec_file hole finder so it lays the next kernel down on good memory, and reject a poisoned destination in sanity_check_segment_list() for the kexec_load path, which cannot relocate. Suggested-by: Kiryl Shutsemau Signed-off-by: Breno Leitao --- Tested on x86_64 under virtme-ng, poisoning frames through /sys/kernel/debug/hwpoison/corrupt-pfn: - kexec_file_load(): poisoning a page inside the range the previous load picked moves the kernel from 0x13b000000 to 0x138000000, and moves the crash kernel's elfcorehdr, the only bottom-up user on x86, from 0xaf000000 to 0xaf001000. - kexec_load(): a one page segment aimed at a poisoned frame fails with EADDRNOTAVAIL, while the same segment on a clean frame still loads. - No splat with CONFIG_DEBUG_ATOMIC_SLEEP=3Dy, and the box kexecs into the relocated kernel. --- Changes in v3: - Return the address of the last poisoned page in the range, or PHYS_ADDR_MAX when it is clean, instead of a bool plus an output parameter. Renamed to range_last_hwpoison(). (Pratyush Yadav) - Add cond_resched() to the scan loop, as a segment can span half of memory. (Sashiko) - Link to v2: https://patch.msgid.link/20260730-kexec_posioned-v2-1-f92d185= 51f64@debian.org Changes in v2: - Change from pfn_to_page() to pfn_to_online_page(). (Miaohe Lin) - Return the poisoned address once we find a hit, to avoid the O(n^2) rescan. (Sashiko) - Link to v1: https://patch.msgid.link/20260728-kexec_posioned-v1-1-160c81d= 180fe@debian.org To: Andrew Morton To: David Hildenbrand To: Lorenzo Stoakes To: "Liam R. Howlett" To: Vlastimil Babka To: Mike Rapoport To: Suren Baghdasaryan To: Michal Hocko To: Baoquan He To: Pasha Tatashin To: Pratyush Yadav To: Miaohe Lin To: Naoya Horiguchi Cc: linux-mm@kvack.org Cc: linux-kernel@vger.kernel.org Cc: kexec@lists.infradead.org --- include/linux/mm.h | 7 +++++++ kernel/kexec_core.c | 13 +++++++++++++ kernel/kexec_file.c | 18 ++++++++++++++++++ mm/memory-failure.c | 25 +++++++++++++++++++++++++ 4 files changed, 63 insertions(+) diff --git a/include/linux/mm.h b/include/linux/mm.h index 7fabe6c66b4b7..fa4f55eff2415 100644 --- a/include/linux/mm.h +++ b/include/linux/mm.h @@ -5192,6 +5192,7 @@ extern const struct attribute_group memory_failure_at= tr_group; extern void memory_failure_queue(unsigned long pfn, int flags); void num_poisoned_pages_inc(unsigned long pfn); void num_poisoned_pages_sub(unsigned long pfn, long i); +phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size); #else static inline void memory_failure_queue(unsigned long pfn, int flags) { @@ -5204,6 +5205,12 @@ static inline void num_poisoned_pages_inc(unsigned l= ong pfn) static inline void num_poisoned_pages_sub(unsigned long pfn, long i) { } + +static inline phys_addr_t range_last_hwpoison(phys_addr_t start, + unsigned long size) +{ + return PHYS_ADDR_MAX; +} #endif =20 #if defined(CONFIG_MEMORY_FAILURE) && defined(CONFIG_MEMORY_HOTPLUG) diff --git a/kernel/kexec_core.c b/kernel/kexec_core.c index dc770b9a6d053..f2a519a4a6d93 100644 --- a/kernel/kexec_core.c +++ b/kernel/kexec_core.c @@ -212,6 +212,19 @@ int sanity_check_segment_list(struct kimage *image) } #endif =20 + /* + * Reject destinations that land on hardware-poisoned memory: the + * relocation copy would machine-check on the bad frame. + */ + for (i =3D 0; i < nr_segments; i++) { + phys_addr_t poison; + + poison =3D range_last_hwpoison(image->segment[i].mem, + image->segment[i].memsz); + if (poison !=3D PHYS_ADDR_MAX) + return -EADDRNOTAVAIL; + } + /* * The destination addresses are searched from system RAM rather than * being allocated from the buddy allocator, so they are not guaranteed diff --git a/kernel/kexec_file.c b/kernel/kexec_file.c index 59fb9d71e9d86..530c919412a92 100644 --- a/kernel/kexec_file.c +++ b/kernel/kexec_file.c @@ -475,6 +475,7 @@ static int locate_mem_hole_top_down(unsigned long start= , unsigned long end, { struct kimage *image =3D kbuf->image; unsigned long temp_start, temp_end; + phys_addr_t poison; =20 temp_end =3D min(end, kbuf->buf_max); temp_start =3D temp_end - kbuf->memsz + 1; @@ -504,6 +505,15 @@ static int locate_mem_hole_top_down(unsigned long star= t, unsigned long end, continue; } =20 + poison =3D range_last_hwpoison(temp_start, kbuf->memsz); + if (poison !=3D PHYS_ADDR_MAX) { + /* we hit a poisoned page */ + if (poison < kbuf->memsz) + return 0; + temp_start =3D poison - kbuf->memsz; + continue; + } + /* We found a suitable memory range */ break; } while (1); @@ -520,6 +530,7 @@ static int locate_mem_hole_bottom_up(unsigned long star= t, unsigned long end, { struct kimage *image =3D kbuf->image; unsigned long temp_start, temp_end; + phys_addr_t poison; =20 temp_start =3D max(start, kbuf->buf_min); =20 @@ -546,6 +557,13 @@ static int locate_mem_hole_bottom_up(unsigned long sta= rt, unsigned long end, continue; } =20 + poison =3D range_last_hwpoison(temp_start, kbuf->memsz); + if (poison !=3D PHYS_ADDR_MAX) { + /* we hit a poisoned page */ + temp_start =3D poison + PAGE_SIZE; + continue; + } + /* We found a suitable memory range */ break; } while (1); diff --git a/mm/memory-failure.c b/mm/memory-failure.c index a8b03e2920ba8..ef0e989c25d93 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -96,6 +96,31 @@ void num_poisoned_pages_sub(unsigned long pfn, long i) memblk_nr_poison_sub(pfn, i); } =20 +/* + * Return the address of the last hardware-poisoned online page in + * [start, start + size), or PHYS_ADDR_MAX if the range is clean. + */ +phys_addr_t range_last_hwpoison(phys_addr_t start, unsigned long size) +{ + phys_addr_t poison =3D PHYS_ADDR_MAX; + unsigned long pfn, end_pfn; + + if (!size || !atomic_long_read(&num_poisoned_pages)) + return poison; + + end_pfn =3D PHYS_PFN(start + size - 1); + for (pfn =3D PHYS_PFN(start); pfn <=3D end_pfn; pfn++) { + struct page *page =3D pfn_to_online_page(pfn); + + if (page && PageHWPoison(page)) + poison =3D PFN_PHYS(pfn); + + cond_resched(); + } + + return poison; +} + /** * MF_ATTR_RO - Create sysfs entry for each memory failure statistics. * @_name: name of the file in the per NUMA sysfs directory. --- base-commit: c5e32e86ca02b003f86e095d379b38148999293d change-id: 20260727-kexec_posioned-72bb0a4143a0 Best regards, -- =20 Breno Leitao