From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from lgeamrelo03.lge.com (lgeamrelo03.lge.com [156.147.51.102]) (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 7869E2F8BEE for ; Tue, 11 Aug 2026 13:22:20 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=156.147.51.102 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786454543; cv=none; b=R3/NQ3scZMQxB6+eTDOf/bgMgnjzHZl3zWQsE9CsgXV90TlGOFdMmxBEOWc63GSXmT9o+WWENn41RBNEDMF1+RDaI+24qNhujIxbAXuwuDh7o5tmMuTb2SJwt9BAYx0pDUDvt50fxJBSRReT0mAlpYZkc3OlWo9+oggYQf6GWok= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1786454543; c=relaxed/simple; bh=gIvGudZKv/OPSsFraPrbTQSGlFISjutEMRaF2paXHUI=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version; b=G2Hq9pph76VB/l6Qn6PtpItIIDN/RD+n3yeC4clVS3DZl2reKy0KZxLe0tw5u1yj6rPm3Od8U09mlXxeCyRauGBRGRehIi7EEZ1d1tD5Id9ioZM3axu8xL6fdlPuP+Mf2VVH4abqByemfT07Cfwyv9+yibdJOf3p3vwsCfoFogE= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=lge.com; spf=pass smtp.mailfrom=lge.com; arc=none smtp.client-ip=156.147.51.102 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=lge.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=lge.com Received: from unknown (HELO yjaykim-PowerEdge-T330.lge.net) (10.177.112.156) by 156.147.51.102 with ESMTP; 11 Aug 2026 22:22:13 +0900 X-Original-SENDERIP: 10.177.112.156 X-Original-MAILFROM: youngjun.park@lge.com From: Youngjun Park To: Andrew Morton Cc: Chris Li , Kairui Song , Kemeng Shi , Nhat Pham , Baoquan He , Barry Song , Jianyue Wu , her0gyugyu@gmail.com, linux-mm@kvack.org, linux-kernel@vger.kernel.org, Youngjun Park Subject: [PATCH v3 2/4] mm, swap: only allow swapped-out slots into the swap cache Date: Tue, 11 Aug 2026 22:22:07 +0900 Message-Id: <20260811132209.2862708-3-youngjun.park@lge.com> X-Mailer: git-send-email 2.34.1 In-Reply-To: <20260811132209.2862708-1-youngjun.park@lge.com> References: <20260811132209.2862708-1-youngjun.park@lge.com> Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit __swap_cache_add_check() turns away folio entries and slots with no count and lets everything else in. That is safe only when the caller owns the slot. Cluster readahead owns nothing, it walks a raw page_cluster sized window of offsets around the faulting entry, so it can land on any slot. A bad slot gets in. The check reads the count with __swp_tb_get_count(), which shifts the count bits out without looking at the type, and SWP_TB_BAD has all of them set, so the slot reads as SWP_TB_COUNT_MAX. Readahead then allocates a folio and reads the offset off the device for a slot nothing will ever swap in, and the folio entry that replaces it drops the bad marker. Readahead used to be guarded by swap_entry_swapped(), which goes through swp_tb_get_count() and gets -EINVAL for a bad slot. That call went away when the swap cache checks moved into __swap_cache_add_check(), and the raw accessor there does not do the same type test. Require a shadow entry instead. A slot dropped from the swap cache always gets one, empty if there is no workingset value. The type test runs first, so the count is only read off a countable entry, and the check as a whole runs before the folio allocation in __swap_cache_alloc(). The large folio walk in the same function does the same raw reads. A bad slot cannot be in its range, but the range is not pinned, so a slot freed and then taken by hibernation still trips the countable assertion there. Give the walk the same shadow test, the folio check folds into it. Reproduced with a badpages list written into the swap header by hand. Readahead took over four bad slots before this patch and none after. It needs a crafted header, so a normal setup will not hit it. Fixes: e1e6750df3b4 ("mm, swap: add support for stable large allocation in swap cache directly") Acked-by: Kairui Song Signed-off-by: Youngjun Park --- mm/swap_state.c | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/mm/swap_state.c b/mm/swap_state.c index b76eb3d876fd..6341f1bfffa2 100644 --- a/mm/swap_state.c +++ b/mm/swap_state.c @@ -181,9 +181,14 @@ static int __swap_cache_add_check(struct swap_cluster_info *ci, old_tb = __swap_table_get(ci, ci_off); if (swp_tb_is_folio(old_tb)) return -EEXIST; - if (!__swp_tb_get_count(old_tb)) + /* + * Only a swapped-out slot may be brought into the swap cache. + * Cluster readahead walks raw offset ranges, so it can land on + * slots that are free, bad, or owned by hibernation. + */ + if (!swp_tb_is_shadow(old_tb) || !__swp_tb_get_count(old_tb)) return -ENOENT; - if (shadowp && swp_tb_is_shadow(old_tb)) + if (shadowp) *shadowp = swp_tb_to_shadow(old_tb); if (memcg_id) *memcg_id = __swap_cgroup_get(ci, ci_off); @@ -196,7 +201,7 @@ static int __swap_cache_add_check(struct swap_cluster_info *ci, ci_end = ci_off + nr; do { old_tb = __swap_table_get(ci, ci_off); - if (unlikely(swp_tb_is_folio(old_tb) || + if (unlikely(!swp_tb_is_shadow(old_tb) || !__swp_tb_get_count(old_tb) || is_zero != __swap_table_test_zero(ci, ci_off) || (memcg_id && *memcg_id != __swap_cgroup_get(ci, ci_off)))) -- 2.48.1