From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from mail-pl1-f182.google.com (mail-pl1-f182.google.com [209.85.214.182]) (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 434DA22689C for ; Tue, 9 Jun 2026 05:00:23 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=209.85.214.182 ARC-Seal:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780981225; cv=none; b=s4UyB0uxYTo3vhwcI+gh+wARfcEmtRE7gncRBgaBdy8OvKf6TGw8V3FCrmEOIswauWX2qYLQtyk5gyfGYZF484vVQ9lXC1FusnMWE9vLOxHFvSKusvi3nunWv8cA+RdhmLIS143LTGmPlpeqxntbkC3fsmP3N1P2InOntar3EZU= ARC-Message-Signature:i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1780981225; c=relaxed/simple; bh=yaKP7/UvUzQTM1UAwBovHUg+etg/wRkptdb8tOq3nbY=; h=From:To:Cc:Subject:Date:Message-ID:MIME-Version; b=uQbkcx2viL7ufRdYBWb2/uGrD2Zsy7i17Pve6Csam4kimCvZB4A45fwN7FzcSjQzKMOsOYFOAtJdg3cu1uDSxhNQ0LbsYJNPlVXYxx5qqJ/5d7UAUM8rqI1pSga9V5lQGkn9My2FE8XxInQ03FVrO5eXT6W/KcecZtR5ftzkC40= ARC-Authentication-Results:i=1; smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com; spf=pass smtp.mailfrom=gmail.com; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b=jzVc6rjC; arc=none smtp.client-ip=209.85.214.182 Authentication-Results: smtp.subspace.kernel.org; dmarc=pass (p=none dis=none) header.from=gmail.com Authentication-Results: smtp.subspace.kernel.org; spf=pass smtp.mailfrom=gmail.com Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=gmail.com header.i=@gmail.com header.b="jzVc6rjC" Received: by mail-pl1-f182.google.com with SMTP id d9443c01a7336-2c132ac5ec2so49777935ad.1 for ; Mon, 08 Jun 2026 22:00:23 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20251104; t=1780981222; x=1781586022; 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=fBlgRbs5zbK19LMQRjJDNeRT5MSpRwsmVLifXE9+D5M=; b=jzVc6rjC0SBPXPqQOelC5hGH7ZUJmM4PX5cmBb0YAqhdsSmQacD+HgjOvaGTpQ6P7P pSHpt0SKKgb30G4H1KFCVJ2AgLo8cgXavP6uwtc4DObnVR2DXRil4rAaw3Jju9AukT1S xeagvvf5PYY0pgOEFBLn/p5JAI4B8aizXrAMsUoiHj4KAiZ3u1CrPTbJwBg/HqSgE/+3 Z+A6Xxx7jbJGchIALeM1i3Ryer0mGTGFbRzrAr+56jDtzR/Vx5XcPqDWvAf8Yv5HPj9w jkBM5d9p/gudqkOZGWd5lIZkHEdQoAX/8XVM2MojTI+MqsXz1PwHhU9Gb9XZgfMzU5cJ Ghxg== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20251104; t=1780981222; x=1781586022; h=content-transfer-encoding:mime-version:message-id:date:subject:cc :to:from:x-gm-gg:x-gm-message-state:from:to:cc:subject:date :message-id:reply-to; bh=fBlgRbs5zbK19LMQRjJDNeRT5MSpRwsmVLifXE9+D5M=; b=eCeqD36tN2IU1qRFf3Olm3anlePY6oVPaPjYogizw/LZVTS4nRlQH3xSwqgUof6L3B 73iEYpJo2ujjiTw+Kh8RP7+o4HqJenerC3jX0K6jzfdSLiVNjPeGNIt/uEkZaVst+NWY C8L1fhd/5oKKvoHiy6eEeiX1q4GCm+k1zQaMr5mlwS4uxYlN024mur7ltq+aljJz0bVD g+9TerzKtbKDwB1gDGc9V0IqM0E1nD7g3oazRVJtjy4SRgiOvZ8fZESDFmk2VqrU2KOT jlsUi/Ry62ufZfXG4oqeQjjemwTDTQS30XSLNEobc1we0iE388BZwNOHnFHqturYesjO s2Bw== X-Forwarded-Encrypted: i=1; AFNElJ8zBEBNZD2QtTeorUAvrQ4RjAQK927xhVpsPx45jbXdav4SZQ9Aj2CBsYbSx2xvUcfJIxMHzV/sNil9LFg=@vger.kernel.org X-Gm-Message-State: AOJu0Yyb+qXuBk9AlEmbkVl/T5UJAAJOpSTOQ05SXFkt9qATuHBPQAjC uwegcSiszmwrG+5itNSR2nRuyRjqwTwxU9u8tYpfVhQWwXZieYG/r1ay X-Gm-Gg: Acq92OHv2263HHe8k95j6JI/8gN3IZhI+Wxsizd5AZwsD02JSpJFvKWYkl1kGM5JcbI XekciTYW5BSUTpVTorVYjKQGMILisnHBPTfY6Fktvc4479XXNNYO35HiphC2GX72vJJsg/mkAvT XoLdiBpntuvJGG9l77H+4H1NuQBeQBrlA26UEZOk6RRhP/R7ijFaK4qrL2a3rFygVIQtrYzXrCj /DRBiopmPQXmRsQxP78XOH0kOjxMrZVUToZAhmMOnBj845ftGfVIHJBcLMCD85OB78q4fJsV4o/ 4juFEACkiKwdZRIvHYIvXaEuNYbkboigtjCH1YKn73vhoxLFx9pUNUCC63MBditVTocXNFfCD55 4f5rjW5hruxvaGNtR35Xs9zN6bQUwDMlmVbu09MB5o9ovH52NqogDGdWakOLlyOubUo6YBYcczs CKkCraf3DjcaLaQBEERwxhXvwgnTQpEgU2eVzHtwiRJs9LXjNpLKq/qpaZfk2WUIjcXy9pUtMEP ClRKQcYoppJlUcAaO3EPhWpIYFP2BrrdXm/cQ94I6yxzQ7/IY6XoPkp1pTwNKudBrdUkTpO/CLM A9CpSt8+P9hRaQen89KxTLBR9hRfvQFLHwhhrvMkaLmEY4qqJg== X-Received: by 2002:a17:902:fc4c:b0:2c2:1982:527a with SMTP id d9443c01a7336-2c2198254f0mr152300195ad.16.1780981222155; Mon, 08 Jun 2026 22:00:22 -0700 (PDT) Received: from cs-1047136853211-default.asia-southeast1-b.c.d33bddc1d573818c7-tp.internal (192.152.126.34.bc.googleusercontent.com. [34.126.152.192]) by smtp.gmail.com with ESMTPSA id d9443c01a7336-2c166391d53sm254648635ad.65.2026.06.08.22.00.18 (version=TLS1_3 cipher=TLS_AES_256_GCM_SHA384 bits=256/256); Mon, 08 Jun 2026 22:00:21 -0700 (PDT) From: Aditya Prakash Srivastava To: Theodore Ts'o , Andreas Dilger Cc: Jan Kara , Baokun Li , Ojaswin Mujoo , Ritesh Harjani , Zhang Yi , sashiko-reviews@lists.linux.dev, linux-ext4@vger.kernel.org, linux-kernel@vger.kernel.org, Aditya Prakash Srivastava , syzbot+0c89d865531d053abb2d@syzkaller.appspotmail.com Subject: [PATCH v3] ext4: fix kernel BUG in ext4_write_inline_data_end Date: Tue, 9 Jun 2026 04:59:55 +0000 Message-ID: <20260609045956.1910-1-aditya.ansh182@gmail.com> X-Mailer: git-send-email 2.43.0 Precedence: bulk X-Mailing-List: linux-kernel@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Content-Transfer-Encoding: 8bit When the data=journal mount option is used, the ext4_journalled_write_end() function incorrectly calls ext4_write_inline_data_end() without checking if the EXT4_STATE_MAY_INLINE_DATA flag is still set on the inode. If a previous attempt to convert the inline data to an extent failed (e.g. due to ENOSPC), the EXT4_STATE_MAY_INLINE_DATA flag is cleared, but the EXT4_INODE_INLINE_DATA flag remains set. In this scenario, the next call to ext4_write_begin() will not prepare the inline data xattr for writing, but ext4_journalled_write_end() will incorrectly attempt to write to it, triggering a BUG_ON(pos + len > EXT4_I(inode)->i_inline_size) in ext4_write_inline_data() since i_inline_size was not expanded. Additionally, a concurrent ext4_page_mkwrite() can execute ext4_convert_inline_data() between ext4_write_begin() and ext4_write_end() since it only takes xattr_sem. This concurrent path converts the inline data to an extent and clears both the EXT4_INODE_INLINE_DATA and EXT4_STATE_MAY_INLINE_DATA flags. Since the block buffers were not allocated in ext4_write_begin(), falling through to the block fallback path causes a NULL pointer dereference because folio_buffers(folio) is NULL. Fix this by ensuring that the write_end functions (including ext4_write_end() and ext4_da_do_write_end()) safely return 0 to trigger a VFS retry if they detect folio_buffers(folio) is NULL after falling through from the inline data check. Returning 0 signals generic_perform_write() to revert the write iterator and retry the entire write operation, which will then properly allocate block buffers. To prevent transaction handle leaks and subsequent filesystem hangs during retries, we stop any active journal handle via ext4_journal_stop() before returning 0. We also explicitly check that the retrieved handle is non-NULL prior to stopping it, preventing a potential NULL pointer crash inside ext4_put_nojournal(). Reported-by: syzbot+0c89d865531d053abb2d@syzkaller.appspotmail.com Closes: https://syzkaller.appspot.com/bug?extid=0c89d865531d053abb2d Fixes: 3fdcfb668fd7 ("ext4: add journalled write support for inline data") Signed-off-by: Aditya Prakash Srivastava --- v3: - Fix journal handle leak in ext4_da_do_write_end() by stopping the active handle before returning 0 on the fallback path. - Safely check if the retrieved handle is non-NULL before calling ext4_journal_stop() in ext4_write_end() and ext4_journalled_write_end() to prevent a kernel crash inside ext4_put_nojournal(). v2: - Address TOCTOU race condition (reported by Sashiko AI review): A concurrent ext4_page_mkwrite() can execute ext4_convert_inline_data() which takes xattr_sem and converts the inline data to extents, clearing both EXT4_INODE_INLINE_DATA and EXT4_STATE_MAY_INLINE_DATA flags between ext4_write_begin() and write_end(). - Safely return 0 from write_end functions if folio_buffers(folio) is NULL to trigger a VFS retry. This prevents a NULL pointer dereference in ext4_journalled_zero_new_buffers() and ext4_walk_page_buffers(). - Add check for ext4_write_end() and ext4_da_do_write_end(). fs/ext4/inode.c | 24 ++++++++++++++++++++++-- 1 file changed, 22 insertions(+), 2 deletions(-) diff --git a/fs/ext4/inode.c b/fs/ext4/inode.c index c2c2d6ac7f3d..bc2688e03c19 100644 --- a/fs/ext4/inode.c +++ b/fs/ext4/inode.c @@ -1455,6 +1455,14 @@ static int ext4_write_end(const struct kiocb *iocb, return ext4_write_inline_data_end(inode, pos, len, copied, folio); + if (unlikely(!folio_buffers(folio))) { + folio_unlock(folio); + folio_put(folio); + if (handle) + ext4_journal_stop(handle); + return 0; + } + copied = block_write_end(pos, len, copied, folio); /* * it's important to update i_size while still holding folio lock: @@ -1560,10 +1568,19 @@ static int ext4_journalled_write_end(const struct kiocb *iocb, BUG_ON(!ext4_handle_valid(handle)); - if (ext4_has_inline_data(inode)) + if (ext4_has_inline_data(inode) && + ext4_test_inode_state(inode, EXT4_STATE_MAY_INLINE_DATA)) return ext4_write_inline_data_end(inode, pos, len, copied, folio); + if (unlikely(!folio_buffers(folio))) { + folio_unlock(folio); + folio_put(folio); + if (handle) + ext4_journal_stop(handle); + return 0; + } + if (unlikely(copied < len) && !folio_test_uptodate(folio)) { copied = 0; ext4_journalled_zero_new_buffers(handle, inode, folio, @@ -3231,7 +3248,10 @@ static int ext4_da_do_write_end(struct address_space *mapping, if (unlikely(!folio_buffers(folio))) { folio_unlock(folio); folio_put(folio); - return -EIO; + handle = ext4_journal_current_handle(); + if (handle) + ext4_journal_stop(handle); + return 0; } /* * block_write_end() will mark the inode as dirty with I_DIRTY_PAGES -- 2.47.3