2018-06-06 10:42:14 +08:00
|
|
|
// SPDX-License-Identifier: GPL-2.0
|
2013-08-12 18:49:35 +08:00
|
|
|
/*
|
|
|
|
* Copyright (c) 2000-2006 Silicon Graphics, Inc.
|
|
|
|
* All Rights Reserved.
|
|
|
|
*/
|
|
|
|
#include "xfs.h"
|
|
|
|
#include "xfs_fs.h"
|
2013-10-23 07:36:05 +08:00
|
|
|
#include "xfs_shared.h"
|
2013-10-23 07:50:10 +08:00
|
|
|
#include "xfs_format.h"
|
|
|
|
#include "xfs_log_format.h"
|
|
|
|
#include "xfs_trans_resv.h"
|
2013-08-12 18:49:35 +08:00
|
|
|
#include "xfs_mount.h"
|
2022-07-07 17:13:10 +08:00
|
|
|
#include "xfs_ag.h"
|
2013-08-12 18:49:35 +08:00
|
|
|
#include "xfs_inode.h"
|
2017-11-01 03:04:49 +08:00
|
|
|
#include "xfs_errortag.h"
|
2013-08-12 18:49:35 +08:00
|
|
|
#include "xfs_error.h"
|
|
|
|
#include "xfs_icache.h"
|
2013-10-23 07:50:10 +08:00
|
|
|
#include "xfs_trans.h"
|
2013-10-23 07:51:50 +08:00
|
|
|
#include "xfs_ialloc.h"
|
2017-01-18 03:41:44 +08:00
|
|
|
#include "xfs_dir2.h"
|
2013-08-12 18:49:35 +08:00
|
|
|
|
2017-12-11 19:35:19 +08:00
|
|
|
#include <linux/iversion.h>
|
|
|
|
|
2013-08-27 09:39:37 +08:00
|
|
|
/*
|
|
|
|
* If we are doing readahead on an inode buffer, we might be in log recovery
|
|
|
|
* reading an inode allocation buffer that hasn't yet been replayed, and hence
|
|
|
|
* has not had the inode cores stamped into it. Hence for readahead, the buffer
|
|
|
|
* may be potentially invalid.
|
|
|
|
*
|
2016-01-12 04:03:44 +08:00
|
|
|
* If the readahead buffer is invalid, we need to mark it with an error and
|
|
|
|
* clear the DONE status of the buffer so that a followup read will re-read it
|
|
|
|
* from disk. We don't report the error otherwise to avoid warnings during log
|
2020-06-30 05:44:35 +08:00
|
|
|
* recovery and we don't get unnecessary panics on debug kernels. We use EIO here
|
2016-01-12 04:03:44 +08:00
|
|
|
* because all we want to do is say readahead failed; there is no-one to report
|
|
|
|
* the error to, so this will distinguish it from a non-ra verifier failure.
|
2020-06-30 05:44:35 +08:00
|
|
|
* Changes to this readahead error behaviour also need to be reflected in
|
2016-01-12 04:04:01 +08:00
|
|
|
* xfs_dquot_buf_readahead_verify().
|
2013-08-27 09:39:37 +08:00
|
|
|
*/
|
2013-08-12 18:49:35 +08:00
|
|
|
static void
|
|
|
|
xfs_inode_buf_verify(
|
2013-08-27 09:39:37 +08:00
|
|
|
struct xfs_buf *bp,
|
|
|
|
bool readahead)
|
2013-08-12 18:49:35 +08:00
|
|
|
{
|
2019-06-29 10:27:29 +08:00
|
|
|
struct xfs_mount *mp = bp->b_mount;
|
2013-08-12 18:49:35 +08:00
|
|
|
int i;
|
|
|
|
int ni;
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate the magic number and version of every inode in the buffer
|
|
|
|
*/
|
|
|
|
ni = XFS_BB_TO_FSB(mp, bp->b_length) * mp->m_sb.sb_inopblock;
|
|
|
|
for (i = 0; i < ni; i++) {
|
2021-10-12 07:11:21 +08:00
|
|
|
struct xfs_dinode *dip;
|
|
|
|
xfs_agino_t unlinked_ino;
|
|
|
|
int di_ok;
|
2013-08-12 18:49:35 +08:00
|
|
|
|
2015-06-22 07:44:29 +08:00
|
|
|
dip = xfs_buf_offset(bp, (i << mp->m_sb.sb_inodelog));
|
2018-03-24 01:06:56 +08:00
|
|
|
unlinked_ino = be32_to_cpu(dip->di_next_unlinked);
|
2019-02-17 03:47:28 +08:00
|
|
|
di_ok = xfs_verify_magic16(bp, dip->di_magic) &&
|
2021-08-19 09:46:57 +08:00
|
|
|
xfs_dinode_good_version(mp, dip->di_version) &&
|
2022-07-07 17:13:10 +08:00
|
|
|
xfs_verify_agino_or_null(bp->b_pag, unlinked_ino);
|
2013-08-12 18:49:35 +08:00
|
|
|
if (unlikely(XFS_TEST_ERROR(!di_ok, mp,
|
2017-06-21 08:54:47 +08:00
|
|
|
XFS_ERRTAG_ITOBP_INOTOBP))) {
|
2013-08-27 09:39:37 +08:00
|
|
|
if (readahead) {
|
|
|
|
bp->b_flags &= ~XBF_DONE;
|
2016-01-12 04:03:44 +08:00
|
|
|
xfs_buf_ioerror(bp, -EIO);
|
2013-08-27 09:39:37 +08:00
|
|
|
return;
|
|
|
|
}
|
|
|
|
|
2013-08-12 18:49:35 +08:00
|
|
|
#ifdef DEBUG
|
2013-09-03 19:47:38 +08:00
|
|
|
xfs_alert(mp,
|
2013-08-12 18:49:35 +08:00
|
|
|
"bad inode magic/vsn daddr %lld #%d (magic=%x)",
|
2021-08-19 09:47:05 +08:00
|
|
|
(unsigned long long)xfs_buf_daddr(bp), i,
|
2013-08-12 18:49:35 +08:00
|
|
|
be16_to_cpu(dip->di_magic));
|
|
|
|
#endif
|
2018-03-24 01:06:53 +08:00
|
|
|
xfs_buf_verifier_error(bp, -EFSCORRUPTED,
|
|
|
|
__func__, dip, sizeof(*dip),
|
|
|
|
NULL);
|
2018-03-24 01:06:56 +08:00
|
|
|
return;
|
2013-08-12 18:49:35 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
static void
|
|
|
|
xfs_inode_buf_read_verify(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
2013-08-27 09:39:37 +08:00
|
|
|
xfs_inode_buf_verify(bp, false);
|
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xfs_inode_buf_readahead_verify(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
|
|
|
xfs_inode_buf_verify(bp, true);
|
2013-08-12 18:49:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
static void
|
|
|
|
xfs_inode_buf_write_verify(
|
|
|
|
struct xfs_buf *bp)
|
|
|
|
{
|
2013-08-27 09:39:37 +08:00
|
|
|
xfs_inode_buf_verify(bp, false);
|
2013-08-12 18:49:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
const struct xfs_buf_ops xfs_inode_buf_ops = {
|
2016-01-04 13:10:19 +08:00
|
|
|
.name = "xfs_inode",
|
2019-02-17 03:47:28 +08:00
|
|
|
.magic16 = { cpu_to_be16(XFS_DINODE_MAGIC),
|
|
|
|
cpu_to_be16(XFS_DINODE_MAGIC) },
|
2013-08-12 18:49:35 +08:00
|
|
|
.verify_read = xfs_inode_buf_read_verify,
|
|
|
|
.verify_write = xfs_inode_buf_write_verify,
|
|
|
|
};
|
|
|
|
|
2013-08-27 09:39:37 +08:00
|
|
|
const struct xfs_buf_ops xfs_inode_buf_ra_ops = {
|
2019-02-08 02:45:45 +08:00
|
|
|
.name = "xfs_inode_ra",
|
2019-02-17 03:47:28 +08:00
|
|
|
.magic16 = { cpu_to_be16(XFS_DINODE_MAGIC),
|
|
|
|
cpu_to_be16(XFS_DINODE_MAGIC) },
|
2013-08-27 09:39:37 +08:00
|
|
|
.verify_read = xfs_inode_buf_readahead_verify,
|
|
|
|
.verify_write = xfs_inode_buf_write_verify,
|
|
|
|
};
|
|
|
|
|
2013-08-12 18:49:35 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* This routine is called to map an inode to the buffer containing the on-disk
|
|
|
|
* version of the inode. It returns a pointer to the buffer containing the
|
2021-03-30 02:11:37 +08:00
|
|
|
* on-disk inode in the bpp parameter.
|
2013-08-12 18:49:35 +08:00
|
|
|
*/
|
|
|
|
int
|
|
|
|
xfs_imap_to_bp(
|
|
|
|
struct xfs_mount *mp,
|
|
|
|
struct xfs_trans *tp,
|
|
|
|
struct xfs_imap *imap,
|
2021-03-30 02:11:37 +08:00
|
|
|
struct xfs_buf **bpp)
|
2013-08-12 18:49:35 +08:00
|
|
|
{
|
2021-03-30 02:11:37 +08:00
|
|
|
return xfs_trans_read_buf(mp, tp, mp->m_ddev_targp, imap->im_blkno,
|
|
|
|
imap->im_len, XBF_UNMAPPED, bpp,
|
2013-08-12 18:49:35 +08:00
|
|
|
&xfs_inode_buf_ops);
|
|
|
|
}
|
|
|
|
|
2020-08-18 00:59:07 +08:00
|
|
|
static inline struct timespec64 xfs_inode_decode_bigtime(uint64_t ts)
|
|
|
|
{
|
|
|
|
struct timespec64 tv;
|
|
|
|
uint32_t n;
|
|
|
|
|
|
|
|
tv.tv_sec = xfs_bigtime_to_unix(div_u64_rem(ts, NSEC_PER_SEC, &n));
|
|
|
|
tv.tv_nsec = n;
|
|
|
|
|
|
|
|
return tv;
|
|
|
|
}
|
|
|
|
|
2020-08-25 06:15:46 +08:00
|
|
|
/* Convert an ondisk timestamp to an incore timestamp. */
|
|
|
|
struct timespec64
|
|
|
|
xfs_inode_from_disk_ts(
|
2020-08-18 00:59:07 +08:00
|
|
|
struct xfs_dinode *dip,
|
2020-08-25 06:15:46 +08:00
|
|
|
const xfs_timestamp_t ts)
|
|
|
|
{
|
|
|
|
struct timespec64 tv;
|
|
|
|
struct xfs_legacy_timestamp *lts;
|
|
|
|
|
2020-08-18 00:59:07 +08:00
|
|
|
if (xfs_dinode_has_bigtime(dip))
|
|
|
|
return xfs_inode_decode_bigtime(be64_to_cpu(ts));
|
|
|
|
|
2020-08-25 06:15:46 +08:00
|
|
|
lts = (struct xfs_legacy_timestamp *)&ts;
|
|
|
|
tv.tv_sec = (int)be32_to_cpu(lts->t_sec);
|
|
|
|
tv.tv_nsec = (int)be32_to_cpu(lts->t_nsec);
|
|
|
|
|
|
|
|
return tv;
|
|
|
|
}
|
|
|
|
|
2020-05-15 05:00:02 +08:00
|
|
|
int
|
2016-02-09 13:54:58 +08:00
|
|
|
xfs_inode_from_disk(
|
|
|
|
struct xfs_inode *ip,
|
2016-02-09 13:54:58 +08:00
|
|
|
struct xfs_dinode *from)
|
2013-08-12 18:49:35 +08:00
|
|
|
{
|
2016-02-09 13:54:58 +08:00
|
|
|
struct inode *inode = VFS_I(ip);
|
2020-05-15 05:01:17 +08:00
|
|
|
int error;
|
2020-05-15 05:01:18 +08:00
|
|
|
xfs_failaddr_t fa;
|
2020-05-15 05:01:17 +08:00
|
|
|
|
|
|
|
ASSERT(ip->i_cowfp == NULL);
|
2016-02-09 13:54:58 +08:00
|
|
|
|
2020-05-15 05:01:18 +08:00
|
|
|
fa = xfs_dinode_verify(ip->i_mount, ip->i_ino, from);
|
|
|
|
if (fa) {
|
|
|
|
xfs_inode_verifier_error(ip, -EFSCORRUPTED, "dinode", from,
|
|
|
|
sizeof(*from), fa);
|
|
|
|
return -EFSCORRUPTED;
|
|
|
|
}
|
|
|
|
|
2020-05-15 05:01:17 +08:00
|
|
|
/*
|
|
|
|
* First get the permanent information that is needed to allocate an
|
|
|
|
* inode. If the inode is unused, mode is zero and we shouldn't mess
|
2020-06-30 05:44:35 +08:00
|
|
|
* with the uninitialized part of it.
|
2020-05-15 05:01:17 +08:00
|
|
|
*/
|
2021-08-19 09:46:55 +08:00
|
|
|
if (!xfs_has_v3inodes(ip->i_mount))
|
2021-03-30 02:11:43 +08:00
|
|
|
ip->i_flushiter = be16_to_cpu(from->di_flushiter);
|
2020-05-15 05:01:17 +08:00
|
|
|
inode->i_generation = be32_to_cpu(from->di_gen);
|
|
|
|
inode->i_mode = be16_to_cpu(from->di_mode);
|
|
|
|
if (!inode->i_mode)
|
|
|
|
return 0;
|
|
|
|
|
2016-02-09 13:54:58 +08:00
|
|
|
/*
|
|
|
|
* Convert v1 inodes immediately to v2 inode format as this is the
|
|
|
|
* minimum inode version format we support in the rest of the code.
|
2020-03-18 23:15:11 +08:00
|
|
|
* They will also be unconditionally written back to disk as v2 inodes.
|
2016-02-09 13:54:58 +08:00
|
|
|
*/
|
2020-03-18 23:15:11 +08:00
|
|
|
if (unlikely(from->di_version == 1)) {
|
2016-02-09 13:54:58 +08:00
|
|
|
set_nlink(inode, be16_to_cpu(from->di_onlink));
|
2021-03-30 02:11:39 +08:00
|
|
|
ip->i_projid = 0;
|
2016-02-09 13:54:58 +08:00
|
|
|
} else {
|
2016-02-09 13:54:58 +08:00
|
|
|
set_nlink(inode, be32_to_cpu(from->di_nlink));
|
2021-03-30 02:11:39 +08:00
|
|
|
ip->i_projid = (prid_t)be16_to_cpu(from->di_projid_hi) << 16 |
|
2019-11-13 00:22:54 +08:00
|
|
|
be16_to_cpu(from->di_projid_lo);
|
2016-02-09 13:54:58 +08:00
|
|
|
}
|
|
|
|
|
2020-02-22 00:31:27 +08:00
|
|
|
i_uid_write(inode, be32_to_cpu(from->di_uid));
|
|
|
|
i_gid_write(inode, be32_to_cpu(from->di_gid));
|
2016-02-09 13:54:58 +08:00
|
|
|
|
|
|
|
/*
|
|
|
|
* Time is signed, so need to convert to signed 32 bit before
|
|
|
|
* storing in inode timestamp which may be 64 bit. Otherwise
|
|
|
|
* a time before epoch is converted to a time long after epoch
|
|
|
|
* on 64 bit systems.
|
|
|
|
*/
|
2020-08-18 00:59:07 +08:00
|
|
|
inode->i_atime = xfs_inode_from_disk_ts(from, from->di_atime);
|
|
|
|
inode->i_mtime = xfs_inode_from_disk_ts(from, from->di_mtime);
|
|
|
|
inode->i_ctime = xfs_inode_from_disk_ts(from, from->di_ctime);
|
2016-02-09 13:54:58 +08:00
|
|
|
|
2021-03-30 02:11:40 +08:00
|
|
|
ip->i_disk_size = be64_to_cpu(from->di_size);
|
2021-03-30 02:11:40 +08:00
|
|
|
ip->i_nblocks = be64_to_cpu(from->di_nblocks);
|
2021-03-30 02:11:41 +08:00
|
|
|
ip->i_extsize = be32_to_cpu(from->di_extsize);
|
2021-03-30 02:11:44 +08:00
|
|
|
ip->i_forkoff = from->di_forkoff;
|
2022-07-14 09:38:54 +08:00
|
|
|
ip->i_diflags = be16_to_cpu(from->di_flags);
|
|
|
|
ip->i_next_unlinked = be32_to_cpu(from->di_next_unlinked);
|
2013-08-12 18:49:35 +08:00
|
|
|
|
2021-03-30 02:11:38 +08:00
|
|
|
if (from->di_dmevmask || from->di_dmstate)
|
|
|
|
xfs_iflags_set(ip, XFS_IPRESERVE_DM_FIELDS);
|
|
|
|
|
2021-08-19 09:46:55 +08:00
|
|
|
if (xfs_has_v3inodes(ip->i_mount)) {
|
2017-12-11 19:35:19 +08:00
|
|
|
inode_set_iversion_queried(inode,
|
|
|
|
be64_to_cpu(from->di_changecount));
|
2021-03-30 02:11:45 +08:00
|
|
|
ip->i_crtime = xfs_inode_from_disk_ts(from, from->di_crtime);
|
2021-03-30 02:11:45 +08:00
|
|
|
ip->i_diflags2 = be64_to_cpu(from->di_flags2);
|
2021-03-30 02:11:42 +08:00
|
|
|
ip->i_cowextsize = be32_to_cpu(from->di_cowextsize);
|
2013-08-12 18:49:35 +08:00
|
|
|
}
|
2020-05-15 05:00:02 +08:00
|
|
|
|
2020-05-15 05:01:17 +08:00
|
|
|
error = xfs_iformat_data_fork(ip, from);
|
|
|
|
if (error)
|
|
|
|
return error;
|
2020-05-19 01:27:21 +08:00
|
|
|
if (from->di_forkoff) {
|
2020-05-15 05:01:17 +08:00
|
|
|
error = xfs_iformat_attr_fork(ip, from);
|
|
|
|
if (error)
|
|
|
|
goto out_destroy_data_fork;
|
|
|
|
}
|
|
|
|
if (xfs_is_reflink_inode(ip))
|
|
|
|
xfs_ifork_init_cow(ip);
|
|
|
|
return 0;
|
|
|
|
|
|
|
|
out_destroy_data_fork:
|
2020-05-19 01:29:27 +08:00
|
|
|
xfs_idestroy_fork(&ip->i_df);
|
2020-05-15 05:01:17 +08:00
|
|
|
return error;
|
2013-08-12 18:49:35 +08:00
|
|
|
}
|
|
|
|
|
2020-08-25 06:15:46 +08:00
|
|
|
/* Convert an incore timestamp to an ondisk timestamp. */
|
|
|
|
static inline xfs_timestamp_t
|
|
|
|
xfs_inode_to_disk_ts(
|
2020-08-18 00:59:07 +08:00
|
|
|
struct xfs_inode *ip,
|
2020-08-25 06:15:46 +08:00
|
|
|
const struct timespec64 tv)
|
|
|
|
{
|
|
|
|
struct xfs_legacy_timestamp *lts;
|
|
|
|
xfs_timestamp_t ts;
|
|
|
|
|
2020-08-18 00:59:07 +08:00
|
|
|
if (xfs_inode_has_bigtime(ip))
|
|
|
|
return cpu_to_be64(xfs_inode_encode_bigtime(tv));
|
|
|
|
|
2020-08-25 06:15:46 +08:00
|
|
|
lts = (struct xfs_legacy_timestamp *)&ts;
|
|
|
|
lts->t_sec = cpu_to_be32(tv.tv_sec);
|
|
|
|
lts->t_nsec = cpu_to_be32(tv.tv_nsec);
|
|
|
|
|
|
|
|
return ts;
|
|
|
|
}
|
|
|
|
|
2022-03-08 17:34:28 +08:00
|
|
|
static inline void
|
|
|
|
xfs_inode_to_disk_iext_counters(
|
|
|
|
struct xfs_inode *ip,
|
|
|
|
struct xfs_dinode *to)
|
|
|
|
{
|
|
|
|
if (xfs_inode_has_large_extent_counts(ip)) {
|
|
|
|
to->di_big_nextents = cpu_to_be64(xfs_ifork_nextents(&ip->i_df));
|
xfs: make inode attribute forks a permanent part of struct xfs_inode
Syzkaller reported a UAF bug a while back:
==================================================================
BUG: KASAN: use-after-free in xfs_ilock_attr_map_shared+0xe3/0xf6 fs/xfs/xfs_inode.c:127
Read of size 4 at addr ffff88802cec919c by task syz-executor262/2958
CPU: 2 PID: 2958 Comm: syz-executor262 Not tainted
5.15.0-0.30.3-20220406_1406 #3
Hardware name: Red Hat KVM, BIOS 1.13.0-2.module+el8.3.0+7860+a7792d29
04/01/2014
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:88 [inline]
dump_stack_lvl+0x82/0xa9 lib/dump_stack.c:106
print_address_description.constprop.9+0x21/0x2d5 mm/kasan/report.c:256
__kasan_report mm/kasan/report.c:442 [inline]
kasan_report.cold.14+0x7f/0x11b mm/kasan/report.c:459
xfs_ilock_attr_map_shared+0xe3/0xf6 fs/xfs/xfs_inode.c:127
xfs_attr_get+0x378/0x4c2 fs/xfs/libxfs/xfs_attr.c:159
xfs_xattr_get+0xe3/0x150 fs/xfs/xfs_xattr.c:36
__vfs_getxattr+0xdf/0x13d fs/xattr.c:399
cap_inode_need_killpriv+0x41/0x5d security/commoncap.c:300
security_inode_need_killpriv+0x4c/0x97 security/security.c:1408
dentry_needs_remove_privs.part.28+0x21/0x63 fs/inode.c:1912
dentry_needs_remove_privs+0x80/0x9e fs/inode.c:1908
do_truncate+0xc3/0x1e0 fs/open.c:56
handle_truncate fs/namei.c:3084 [inline]
do_open fs/namei.c:3432 [inline]
path_openat+0x30ab/0x396d fs/namei.c:3561
do_filp_open+0x1c4/0x290 fs/namei.c:3588
do_sys_openat2+0x60d/0x98c fs/open.c:1212
do_sys_open+0xcf/0x13c fs/open.c:1228
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x3a/0x7e arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0x0
RIP: 0033:0x7f7ef4bb753d
Code: 00 c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48
89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73
01 c3 48 8b 0d 1b 79 2c 00 f7 d8 64 89 01 48
RSP: 002b:00007f7ef52c2ed8 EFLAGS: 00000246 ORIG_RAX: 0000000000000055
RAX: ffffffffffffffda RBX: 0000000000404148 RCX: 00007f7ef4bb753d
RDX: 00007f7ef4bb753d RSI: 0000000000000000 RDI: 0000000020004fc0
RBP: 0000000000404140 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0030656c69662f2e
R13: 00007ffd794db37f R14: 00007ffd794db470 R15: 00007f7ef52c2fc0
</TASK>
Allocated by task 2953:
kasan_save_stack+0x19/0x38 mm/kasan/common.c:38
kasan_set_track mm/kasan/common.c:46 [inline]
set_alloc_info mm/kasan/common.c:434 [inline]
__kasan_slab_alloc+0x68/0x7c mm/kasan/common.c:467
kasan_slab_alloc include/linux/kasan.h:254 [inline]
slab_post_alloc_hook mm/slab.h:519 [inline]
slab_alloc_node mm/slub.c:3213 [inline]
slab_alloc mm/slub.c:3221 [inline]
kmem_cache_alloc+0x11b/0x3eb mm/slub.c:3226
kmem_cache_zalloc include/linux/slab.h:711 [inline]
xfs_ifork_alloc+0x25/0xa2 fs/xfs/libxfs/xfs_inode_fork.c:287
xfs_bmap_add_attrfork+0x3f2/0x9b1 fs/xfs/libxfs/xfs_bmap.c:1098
xfs_attr_set+0xe38/0x12a7 fs/xfs/libxfs/xfs_attr.c:746
xfs_xattr_set+0xeb/0x1a9 fs/xfs/xfs_xattr.c:59
__vfs_setxattr+0x11b/0x177 fs/xattr.c:180
__vfs_setxattr_noperm+0x128/0x5e0 fs/xattr.c:214
__vfs_setxattr_locked+0x1d4/0x258 fs/xattr.c:275
vfs_setxattr+0x154/0x33d fs/xattr.c:301
setxattr+0x216/0x29f fs/xattr.c:575
__do_sys_fsetxattr fs/xattr.c:632 [inline]
__se_sys_fsetxattr fs/xattr.c:621 [inline]
__x64_sys_fsetxattr+0x243/0x2fe fs/xattr.c:621
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x3a/0x7e arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0x0
Freed by task 2949:
kasan_save_stack+0x19/0x38 mm/kasan/common.c:38
kasan_set_track+0x1c/0x21 mm/kasan/common.c:46
kasan_set_free_info+0x20/0x30 mm/kasan/generic.c:360
____kasan_slab_free mm/kasan/common.c:366 [inline]
____kasan_slab_free mm/kasan/common.c:328 [inline]
__kasan_slab_free+0xe2/0x10e mm/kasan/common.c:374
kasan_slab_free include/linux/kasan.h:230 [inline]
slab_free_hook mm/slub.c:1700 [inline]
slab_free_freelist_hook mm/slub.c:1726 [inline]
slab_free mm/slub.c:3492 [inline]
kmem_cache_free+0xdc/0x3ce mm/slub.c:3508
xfs_attr_fork_remove+0x8d/0x132 fs/xfs/libxfs/xfs_attr_leaf.c:773
xfs_attr_sf_removename+0x5dd/0x6cb fs/xfs/libxfs/xfs_attr_leaf.c:822
xfs_attr_remove_iter+0x68c/0x805 fs/xfs/libxfs/xfs_attr.c:1413
xfs_attr_remove_args+0xb1/0x10d fs/xfs/libxfs/xfs_attr.c:684
xfs_attr_set+0xf1e/0x12a7 fs/xfs/libxfs/xfs_attr.c:802
xfs_xattr_set+0xeb/0x1a9 fs/xfs/xfs_xattr.c:59
__vfs_removexattr+0x106/0x16a fs/xattr.c:468
cap_inode_killpriv+0x24/0x47 security/commoncap.c:324
security_inode_killpriv+0x54/0xa1 security/security.c:1414
setattr_prepare+0x1a6/0x897 fs/attr.c:146
xfs_vn_change_ok+0x111/0x15e fs/xfs/xfs_iops.c:682
xfs_vn_setattr_size+0x5f/0x15a fs/xfs/xfs_iops.c:1065
xfs_vn_setattr+0x125/0x2ad fs/xfs/xfs_iops.c:1093
notify_change+0xae5/0x10a1 fs/attr.c:410
do_truncate+0x134/0x1e0 fs/open.c:64
handle_truncate fs/namei.c:3084 [inline]
do_open fs/namei.c:3432 [inline]
path_openat+0x30ab/0x396d fs/namei.c:3561
do_filp_open+0x1c4/0x290 fs/namei.c:3588
do_sys_openat2+0x60d/0x98c fs/open.c:1212
do_sys_open+0xcf/0x13c fs/open.c:1228
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x3a/0x7e arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0x0
The buggy address belongs to the object at ffff88802cec9188
which belongs to the cache xfs_ifork of size 40
The buggy address is located 20 bytes inside of
40-byte region [ffff88802cec9188, ffff88802cec91b0)
The buggy address belongs to the page:
page:00000000c3af36a1 refcount:1 mapcount:0 mapping:0000000000000000
index:0x0 pfn:0x2cec9
flags: 0xfffffc0000200(slab|node=0|zone=1|lastcpupid=0x1fffff)
raw: 000fffffc0000200 ffffea00009d2580 0000000600000006 ffff88801a9ffc80
raw: 0000000000000000 0000000080490049 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff88802cec9080: fb fb fb fc fc fa fb fb fb fb fc fc fb fb fb fb
ffff88802cec9100: fb fc fc fb fb fb fb fb fc fc fb fb fb fb fb fc
>ffff88802cec9180: fc fa fb fb fb fb fc fc fa fb fb fb fb fc fc fb
^
ffff88802cec9200: fb fb fb fb fc fc fb fb fb fb fb fc fc fb fb fb
ffff88802cec9280: fb fb fc fc fa fb fb fb fb fc fc fa fb fb fb fb
==================================================================
The root cause of this bug is the unlocked access to xfs_inode.i_afp
from the getxattr code paths while trying to determine which ILOCK mode
to use to stabilize the xattr data. Unfortunately, the VFS does not
acquire i_rwsem when vfs_getxattr (or listxattr) call into the
filesystem, which means that getxattr can race with a removexattr that's
tearing down the attr fork and crash:
xfs_attr_set: xfs_attr_get:
xfs_attr_fork_remove: xfs_ilock_attr_map_shared:
xfs_idestroy_fork(ip->i_afp);
kmem_cache_free(xfs_ifork_cache, ip->i_afp);
if (ip->i_afp &&
ip->i_afp = NULL;
xfs_need_iread_extents(ip->i_afp))
<KABOOM>
ip->i_forkoff = 0;
Regrettably, the VFS is much more lax about i_rwsem and getxattr than
is immediately obvious -- not only does it not guarantee that we hold
i_rwsem, it actually doesn't guarantee that we *don't* hold it either.
The getxattr system call won't acquire the lock before calling XFS, but
the file capabilities code calls getxattr with and without i_rwsem held
to determine if the "security.capabilities" xattr is set on the file.
Fixing the VFS locking requires a treewide investigation into every code
path that could touch an xattr and what i_rwsem state it expects or sets
up. That could take years or even prove impossible; fortunately, we
can fix this UAF problem inside XFS.
An earlier version of this patch used smp_wmb in xfs_attr_fork_remove to
ensure that i_forkoff is always zeroed before i_afp is set to null and
changed the read paths to use smp_rmb before accessing i_forkoff and
i_afp, which avoided these UAF problems. However, the patch author was
too busy dealing with other problems in the meantime, and by the time he
came back to this issue, the situation had changed a bit.
On a modern system with selinux, each inode will always have at least
one xattr for the selinux label, so it doesn't make much sense to keep
incurring the extra pointer dereference. Furthermore, Allison's
upcoming parent pointer patchset will also cause nearly every inode in
the filesystem to have extended attributes. Therefore, make the inode
attribute fork structure part of struct xfs_inode, at a cost of 40 more
bytes.
This patch adds a clunky if_present field where necessary to maintain
the existing logic of xattr fork null pointer testing in the existing
codebase. The next patch switches the logic over to XFS_IFORK_Q and it
all goes away.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
2022-07-10 01:56:06 +08:00
|
|
|
to->di_big_anextents = cpu_to_be32(xfs_ifork_nextents(&ip->i_af));
|
2022-03-08 17:34:28 +08:00
|
|
|
/*
|
|
|
|
* We might be upgrading the inode to use larger extent counters
|
|
|
|
* than was previously used. Hence zero the unused field.
|
|
|
|
*/
|
|
|
|
to->di_nrext64_pad = cpu_to_be16(0);
|
|
|
|
} else {
|
|
|
|
to->di_nextents = cpu_to_be32(xfs_ifork_nextents(&ip->i_df));
|
xfs: make inode attribute forks a permanent part of struct xfs_inode
Syzkaller reported a UAF bug a while back:
==================================================================
BUG: KASAN: use-after-free in xfs_ilock_attr_map_shared+0xe3/0xf6 fs/xfs/xfs_inode.c:127
Read of size 4 at addr ffff88802cec919c by task syz-executor262/2958
CPU: 2 PID: 2958 Comm: syz-executor262 Not tainted
5.15.0-0.30.3-20220406_1406 #3
Hardware name: Red Hat KVM, BIOS 1.13.0-2.module+el8.3.0+7860+a7792d29
04/01/2014
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:88 [inline]
dump_stack_lvl+0x82/0xa9 lib/dump_stack.c:106
print_address_description.constprop.9+0x21/0x2d5 mm/kasan/report.c:256
__kasan_report mm/kasan/report.c:442 [inline]
kasan_report.cold.14+0x7f/0x11b mm/kasan/report.c:459
xfs_ilock_attr_map_shared+0xe3/0xf6 fs/xfs/xfs_inode.c:127
xfs_attr_get+0x378/0x4c2 fs/xfs/libxfs/xfs_attr.c:159
xfs_xattr_get+0xe3/0x150 fs/xfs/xfs_xattr.c:36
__vfs_getxattr+0xdf/0x13d fs/xattr.c:399
cap_inode_need_killpriv+0x41/0x5d security/commoncap.c:300
security_inode_need_killpriv+0x4c/0x97 security/security.c:1408
dentry_needs_remove_privs.part.28+0x21/0x63 fs/inode.c:1912
dentry_needs_remove_privs+0x80/0x9e fs/inode.c:1908
do_truncate+0xc3/0x1e0 fs/open.c:56
handle_truncate fs/namei.c:3084 [inline]
do_open fs/namei.c:3432 [inline]
path_openat+0x30ab/0x396d fs/namei.c:3561
do_filp_open+0x1c4/0x290 fs/namei.c:3588
do_sys_openat2+0x60d/0x98c fs/open.c:1212
do_sys_open+0xcf/0x13c fs/open.c:1228
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x3a/0x7e arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0x0
RIP: 0033:0x7f7ef4bb753d
Code: 00 c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48
89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73
01 c3 48 8b 0d 1b 79 2c 00 f7 d8 64 89 01 48
RSP: 002b:00007f7ef52c2ed8 EFLAGS: 00000246 ORIG_RAX: 0000000000000055
RAX: ffffffffffffffda RBX: 0000000000404148 RCX: 00007f7ef4bb753d
RDX: 00007f7ef4bb753d RSI: 0000000000000000 RDI: 0000000020004fc0
RBP: 0000000000404140 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0030656c69662f2e
R13: 00007ffd794db37f R14: 00007ffd794db470 R15: 00007f7ef52c2fc0
</TASK>
Allocated by task 2953:
kasan_save_stack+0x19/0x38 mm/kasan/common.c:38
kasan_set_track mm/kasan/common.c:46 [inline]
set_alloc_info mm/kasan/common.c:434 [inline]
__kasan_slab_alloc+0x68/0x7c mm/kasan/common.c:467
kasan_slab_alloc include/linux/kasan.h:254 [inline]
slab_post_alloc_hook mm/slab.h:519 [inline]
slab_alloc_node mm/slub.c:3213 [inline]
slab_alloc mm/slub.c:3221 [inline]
kmem_cache_alloc+0x11b/0x3eb mm/slub.c:3226
kmem_cache_zalloc include/linux/slab.h:711 [inline]
xfs_ifork_alloc+0x25/0xa2 fs/xfs/libxfs/xfs_inode_fork.c:287
xfs_bmap_add_attrfork+0x3f2/0x9b1 fs/xfs/libxfs/xfs_bmap.c:1098
xfs_attr_set+0xe38/0x12a7 fs/xfs/libxfs/xfs_attr.c:746
xfs_xattr_set+0xeb/0x1a9 fs/xfs/xfs_xattr.c:59
__vfs_setxattr+0x11b/0x177 fs/xattr.c:180
__vfs_setxattr_noperm+0x128/0x5e0 fs/xattr.c:214
__vfs_setxattr_locked+0x1d4/0x258 fs/xattr.c:275
vfs_setxattr+0x154/0x33d fs/xattr.c:301
setxattr+0x216/0x29f fs/xattr.c:575
__do_sys_fsetxattr fs/xattr.c:632 [inline]
__se_sys_fsetxattr fs/xattr.c:621 [inline]
__x64_sys_fsetxattr+0x243/0x2fe fs/xattr.c:621
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x3a/0x7e arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0x0
Freed by task 2949:
kasan_save_stack+0x19/0x38 mm/kasan/common.c:38
kasan_set_track+0x1c/0x21 mm/kasan/common.c:46
kasan_set_free_info+0x20/0x30 mm/kasan/generic.c:360
____kasan_slab_free mm/kasan/common.c:366 [inline]
____kasan_slab_free mm/kasan/common.c:328 [inline]
__kasan_slab_free+0xe2/0x10e mm/kasan/common.c:374
kasan_slab_free include/linux/kasan.h:230 [inline]
slab_free_hook mm/slub.c:1700 [inline]
slab_free_freelist_hook mm/slub.c:1726 [inline]
slab_free mm/slub.c:3492 [inline]
kmem_cache_free+0xdc/0x3ce mm/slub.c:3508
xfs_attr_fork_remove+0x8d/0x132 fs/xfs/libxfs/xfs_attr_leaf.c:773
xfs_attr_sf_removename+0x5dd/0x6cb fs/xfs/libxfs/xfs_attr_leaf.c:822
xfs_attr_remove_iter+0x68c/0x805 fs/xfs/libxfs/xfs_attr.c:1413
xfs_attr_remove_args+0xb1/0x10d fs/xfs/libxfs/xfs_attr.c:684
xfs_attr_set+0xf1e/0x12a7 fs/xfs/libxfs/xfs_attr.c:802
xfs_xattr_set+0xeb/0x1a9 fs/xfs/xfs_xattr.c:59
__vfs_removexattr+0x106/0x16a fs/xattr.c:468
cap_inode_killpriv+0x24/0x47 security/commoncap.c:324
security_inode_killpriv+0x54/0xa1 security/security.c:1414
setattr_prepare+0x1a6/0x897 fs/attr.c:146
xfs_vn_change_ok+0x111/0x15e fs/xfs/xfs_iops.c:682
xfs_vn_setattr_size+0x5f/0x15a fs/xfs/xfs_iops.c:1065
xfs_vn_setattr+0x125/0x2ad fs/xfs/xfs_iops.c:1093
notify_change+0xae5/0x10a1 fs/attr.c:410
do_truncate+0x134/0x1e0 fs/open.c:64
handle_truncate fs/namei.c:3084 [inline]
do_open fs/namei.c:3432 [inline]
path_openat+0x30ab/0x396d fs/namei.c:3561
do_filp_open+0x1c4/0x290 fs/namei.c:3588
do_sys_openat2+0x60d/0x98c fs/open.c:1212
do_sys_open+0xcf/0x13c fs/open.c:1228
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x3a/0x7e arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0x0
The buggy address belongs to the object at ffff88802cec9188
which belongs to the cache xfs_ifork of size 40
The buggy address is located 20 bytes inside of
40-byte region [ffff88802cec9188, ffff88802cec91b0)
The buggy address belongs to the page:
page:00000000c3af36a1 refcount:1 mapcount:0 mapping:0000000000000000
index:0x0 pfn:0x2cec9
flags: 0xfffffc0000200(slab|node=0|zone=1|lastcpupid=0x1fffff)
raw: 000fffffc0000200 ffffea00009d2580 0000000600000006 ffff88801a9ffc80
raw: 0000000000000000 0000000080490049 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff88802cec9080: fb fb fb fc fc fa fb fb fb fb fc fc fb fb fb fb
ffff88802cec9100: fb fc fc fb fb fb fb fb fc fc fb fb fb fb fb fc
>ffff88802cec9180: fc fa fb fb fb fb fc fc fa fb fb fb fb fc fc fb
^
ffff88802cec9200: fb fb fb fb fc fc fb fb fb fb fb fc fc fb fb fb
ffff88802cec9280: fb fb fc fc fa fb fb fb fb fc fc fa fb fb fb fb
==================================================================
The root cause of this bug is the unlocked access to xfs_inode.i_afp
from the getxattr code paths while trying to determine which ILOCK mode
to use to stabilize the xattr data. Unfortunately, the VFS does not
acquire i_rwsem when vfs_getxattr (or listxattr) call into the
filesystem, which means that getxattr can race with a removexattr that's
tearing down the attr fork and crash:
xfs_attr_set: xfs_attr_get:
xfs_attr_fork_remove: xfs_ilock_attr_map_shared:
xfs_idestroy_fork(ip->i_afp);
kmem_cache_free(xfs_ifork_cache, ip->i_afp);
if (ip->i_afp &&
ip->i_afp = NULL;
xfs_need_iread_extents(ip->i_afp))
<KABOOM>
ip->i_forkoff = 0;
Regrettably, the VFS is much more lax about i_rwsem and getxattr than
is immediately obvious -- not only does it not guarantee that we hold
i_rwsem, it actually doesn't guarantee that we *don't* hold it either.
The getxattr system call won't acquire the lock before calling XFS, but
the file capabilities code calls getxattr with and without i_rwsem held
to determine if the "security.capabilities" xattr is set on the file.
Fixing the VFS locking requires a treewide investigation into every code
path that could touch an xattr and what i_rwsem state it expects or sets
up. That could take years or even prove impossible; fortunately, we
can fix this UAF problem inside XFS.
An earlier version of this patch used smp_wmb in xfs_attr_fork_remove to
ensure that i_forkoff is always zeroed before i_afp is set to null and
changed the read paths to use smp_rmb before accessing i_forkoff and
i_afp, which avoided these UAF problems. However, the patch author was
too busy dealing with other problems in the meantime, and by the time he
came back to this issue, the situation had changed a bit.
On a modern system with selinux, each inode will always have at least
one xattr for the selinux label, so it doesn't make much sense to keep
incurring the extra pointer dereference. Furthermore, Allison's
upcoming parent pointer patchset will also cause nearly every inode in
the filesystem to have extended attributes. Therefore, make the inode
attribute fork structure part of struct xfs_inode, at a cost of 40 more
bytes.
This patch adds a clunky if_present field where necessary to maintain
the existing logic of xattr fork null pointer testing in the existing
codebase. The next patch switches the logic over to XFS_IFORK_Q and it
all goes away.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
2022-07-10 01:56:06 +08:00
|
|
|
to->di_anextents = cpu_to_be16(xfs_ifork_nextents(&ip->i_af));
|
2022-03-08 17:34:28 +08:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2013-08-12 18:49:35 +08:00
|
|
|
void
|
2016-02-09 13:54:58 +08:00
|
|
|
xfs_inode_to_disk(
|
|
|
|
struct xfs_inode *ip,
|
2016-02-09 13:54:58 +08:00
|
|
|
struct xfs_dinode *to,
|
|
|
|
xfs_lsn_t lsn)
|
2016-02-09 13:54:58 +08:00
|
|
|
{
|
|
|
|
struct inode *inode = VFS_I(ip);
|
|
|
|
|
2016-02-09 13:54:58 +08:00
|
|
|
to->di_magic = cpu_to_be16(XFS_DINODE_MAGIC);
|
2016-02-09 13:54:58 +08:00
|
|
|
to->di_onlink = 0;
|
2016-02-09 13:54:58 +08:00
|
|
|
|
2020-05-19 01:28:05 +08:00
|
|
|
to->di_format = xfs_ifork_format(&ip->i_df);
|
2020-02-22 00:31:27 +08:00
|
|
|
to->di_uid = cpu_to_be32(i_uid_read(inode));
|
|
|
|
to->di_gid = cpu_to_be32(i_gid_read(inode));
|
2021-03-30 02:11:39 +08:00
|
|
|
to->di_projid_lo = cpu_to_be16(ip->i_projid & 0xffff);
|
|
|
|
to->di_projid_hi = cpu_to_be16(ip->i_projid >> 16);
|
2016-02-09 13:54:58 +08:00
|
|
|
|
2020-08-18 00:59:07 +08:00
|
|
|
to->di_atime = xfs_inode_to_disk_ts(ip, inode->i_atime);
|
|
|
|
to->di_mtime = xfs_inode_to_disk_ts(ip, inode->i_mtime);
|
|
|
|
to->di_ctime = xfs_inode_to_disk_ts(ip, inode->i_ctime);
|
2016-02-09 13:54:58 +08:00
|
|
|
to->di_nlink = cpu_to_be32(inode->i_nlink);
|
2016-02-09 13:54:58 +08:00
|
|
|
to->di_gen = cpu_to_be32(inode->i_generation);
|
2016-02-09 13:54:58 +08:00
|
|
|
to->di_mode = cpu_to_be16(inode->i_mode);
|
2016-02-09 13:54:58 +08:00
|
|
|
|
2021-03-30 02:11:40 +08:00
|
|
|
to->di_size = cpu_to_be64(ip->i_disk_size);
|
2021-03-30 02:11:40 +08:00
|
|
|
to->di_nblocks = cpu_to_be64(ip->i_nblocks);
|
2021-03-30 02:11:41 +08:00
|
|
|
to->di_extsize = cpu_to_be32(ip->i_extsize);
|
2021-03-30 02:11:44 +08:00
|
|
|
to->di_forkoff = ip->i_forkoff;
|
xfs: make inode attribute forks a permanent part of struct xfs_inode
Syzkaller reported a UAF bug a while back:
==================================================================
BUG: KASAN: use-after-free in xfs_ilock_attr_map_shared+0xe3/0xf6 fs/xfs/xfs_inode.c:127
Read of size 4 at addr ffff88802cec919c by task syz-executor262/2958
CPU: 2 PID: 2958 Comm: syz-executor262 Not tainted
5.15.0-0.30.3-20220406_1406 #3
Hardware name: Red Hat KVM, BIOS 1.13.0-2.module+el8.3.0+7860+a7792d29
04/01/2014
Call Trace:
<TASK>
__dump_stack lib/dump_stack.c:88 [inline]
dump_stack_lvl+0x82/0xa9 lib/dump_stack.c:106
print_address_description.constprop.9+0x21/0x2d5 mm/kasan/report.c:256
__kasan_report mm/kasan/report.c:442 [inline]
kasan_report.cold.14+0x7f/0x11b mm/kasan/report.c:459
xfs_ilock_attr_map_shared+0xe3/0xf6 fs/xfs/xfs_inode.c:127
xfs_attr_get+0x378/0x4c2 fs/xfs/libxfs/xfs_attr.c:159
xfs_xattr_get+0xe3/0x150 fs/xfs/xfs_xattr.c:36
__vfs_getxattr+0xdf/0x13d fs/xattr.c:399
cap_inode_need_killpriv+0x41/0x5d security/commoncap.c:300
security_inode_need_killpriv+0x4c/0x97 security/security.c:1408
dentry_needs_remove_privs.part.28+0x21/0x63 fs/inode.c:1912
dentry_needs_remove_privs+0x80/0x9e fs/inode.c:1908
do_truncate+0xc3/0x1e0 fs/open.c:56
handle_truncate fs/namei.c:3084 [inline]
do_open fs/namei.c:3432 [inline]
path_openat+0x30ab/0x396d fs/namei.c:3561
do_filp_open+0x1c4/0x290 fs/namei.c:3588
do_sys_openat2+0x60d/0x98c fs/open.c:1212
do_sys_open+0xcf/0x13c fs/open.c:1228
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x3a/0x7e arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0x0
RIP: 0033:0x7f7ef4bb753d
Code: 00 c3 66 2e 0f 1f 84 00 00 00 00 00 90 f3 0f 1e fa 48 89 f8 48 89 f7 48
89 d6 48 89 ca 4d 89 c2 4d 89 c8 4c 8b 4c 24 08 0f 05 <48> 3d 01 f0 ff ff 73
01 c3 48 8b 0d 1b 79 2c 00 f7 d8 64 89 01 48
RSP: 002b:00007f7ef52c2ed8 EFLAGS: 00000246 ORIG_RAX: 0000000000000055
RAX: ffffffffffffffda RBX: 0000000000404148 RCX: 00007f7ef4bb753d
RDX: 00007f7ef4bb753d RSI: 0000000000000000 RDI: 0000000020004fc0
RBP: 0000000000404140 R08: 0000000000000000 R09: 0000000000000000
R10: 0000000000000000 R11: 0000000000000246 R12: 0030656c69662f2e
R13: 00007ffd794db37f R14: 00007ffd794db470 R15: 00007f7ef52c2fc0
</TASK>
Allocated by task 2953:
kasan_save_stack+0x19/0x38 mm/kasan/common.c:38
kasan_set_track mm/kasan/common.c:46 [inline]
set_alloc_info mm/kasan/common.c:434 [inline]
__kasan_slab_alloc+0x68/0x7c mm/kasan/common.c:467
kasan_slab_alloc include/linux/kasan.h:254 [inline]
slab_post_alloc_hook mm/slab.h:519 [inline]
slab_alloc_node mm/slub.c:3213 [inline]
slab_alloc mm/slub.c:3221 [inline]
kmem_cache_alloc+0x11b/0x3eb mm/slub.c:3226
kmem_cache_zalloc include/linux/slab.h:711 [inline]
xfs_ifork_alloc+0x25/0xa2 fs/xfs/libxfs/xfs_inode_fork.c:287
xfs_bmap_add_attrfork+0x3f2/0x9b1 fs/xfs/libxfs/xfs_bmap.c:1098
xfs_attr_set+0xe38/0x12a7 fs/xfs/libxfs/xfs_attr.c:746
xfs_xattr_set+0xeb/0x1a9 fs/xfs/xfs_xattr.c:59
__vfs_setxattr+0x11b/0x177 fs/xattr.c:180
__vfs_setxattr_noperm+0x128/0x5e0 fs/xattr.c:214
__vfs_setxattr_locked+0x1d4/0x258 fs/xattr.c:275
vfs_setxattr+0x154/0x33d fs/xattr.c:301
setxattr+0x216/0x29f fs/xattr.c:575
__do_sys_fsetxattr fs/xattr.c:632 [inline]
__se_sys_fsetxattr fs/xattr.c:621 [inline]
__x64_sys_fsetxattr+0x243/0x2fe fs/xattr.c:621
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x3a/0x7e arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0x0
Freed by task 2949:
kasan_save_stack+0x19/0x38 mm/kasan/common.c:38
kasan_set_track+0x1c/0x21 mm/kasan/common.c:46
kasan_set_free_info+0x20/0x30 mm/kasan/generic.c:360
____kasan_slab_free mm/kasan/common.c:366 [inline]
____kasan_slab_free mm/kasan/common.c:328 [inline]
__kasan_slab_free+0xe2/0x10e mm/kasan/common.c:374
kasan_slab_free include/linux/kasan.h:230 [inline]
slab_free_hook mm/slub.c:1700 [inline]
slab_free_freelist_hook mm/slub.c:1726 [inline]
slab_free mm/slub.c:3492 [inline]
kmem_cache_free+0xdc/0x3ce mm/slub.c:3508
xfs_attr_fork_remove+0x8d/0x132 fs/xfs/libxfs/xfs_attr_leaf.c:773
xfs_attr_sf_removename+0x5dd/0x6cb fs/xfs/libxfs/xfs_attr_leaf.c:822
xfs_attr_remove_iter+0x68c/0x805 fs/xfs/libxfs/xfs_attr.c:1413
xfs_attr_remove_args+0xb1/0x10d fs/xfs/libxfs/xfs_attr.c:684
xfs_attr_set+0xf1e/0x12a7 fs/xfs/libxfs/xfs_attr.c:802
xfs_xattr_set+0xeb/0x1a9 fs/xfs/xfs_xattr.c:59
__vfs_removexattr+0x106/0x16a fs/xattr.c:468
cap_inode_killpriv+0x24/0x47 security/commoncap.c:324
security_inode_killpriv+0x54/0xa1 security/security.c:1414
setattr_prepare+0x1a6/0x897 fs/attr.c:146
xfs_vn_change_ok+0x111/0x15e fs/xfs/xfs_iops.c:682
xfs_vn_setattr_size+0x5f/0x15a fs/xfs/xfs_iops.c:1065
xfs_vn_setattr+0x125/0x2ad fs/xfs/xfs_iops.c:1093
notify_change+0xae5/0x10a1 fs/attr.c:410
do_truncate+0x134/0x1e0 fs/open.c:64
handle_truncate fs/namei.c:3084 [inline]
do_open fs/namei.c:3432 [inline]
path_openat+0x30ab/0x396d fs/namei.c:3561
do_filp_open+0x1c4/0x290 fs/namei.c:3588
do_sys_openat2+0x60d/0x98c fs/open.c:1212
do_sys_open+0xcf/0x13c fs/open.c:1228
do_syscall_x64 arch/x86/entry/common.c:50 [inline]
do_syscall_64+0x3a/0x7e arch/x86/entry/common.c:80
entry_SYSCALL_64_after_hwframe+0x44/0x0
The buggy address belongs to the object at ffff88802cec9188
which belongs to the cache xfs_ifork of size 40
The buggy address is located 20 bytes inside of
40-byte region [ffff88802cec9188, ffff88802cec91b0)
The buggy address belongs to the page:
page:00000000c3af36a1 refcount:1 mapcount:0 mapping:0000000000000000
index:0x0 pfn:0x2cec9
flags: 0xfffffc0000200(slab|node=0|zone=1|lastcpupid=0x1fffff)
raw: 000fffffc0000200 ffffea00009d2580 0000000600000006 ffff88801a9ffc80
raw: 0000000000000000 0000000080490049 00000001ffffffff 0000000000000000
page dumped because: kasan: bad access detected
Memory state around the buggy address:
ffff88802cec9080: fb fb fb fc fc fa fb fb fb fb fc fc fb fb fb fb
ffff88802cec9100: fb fc fc fb fb fb fb fb fc fc fb fb fb fb fb fc
>ffff88802cec9180: fc fa fb fb fb fb fc fc fa fb fb fb fb fc fc fb
^
ffff88802cec9200: fb fb fb fb fc fc fb fb fb fb fb fc fc fb fb fb
ffff88802cec9280: fb fb fc fc fa fb fb fb fb fc fc fa fb fb fb fb
==================================================================
The root cause of this bug is the unlocked access to xfs_inode.i_afp
from the getxattr code paths while trying to determine which ILOCK mode
to use to stabilize the xattr data. Unfortunately, the VFS does not
acquire i_rwsem when vfs_getxattr (or listxattr) call into the
filesystem, which means that getxattr can race with a removexattr that's
tearing down the attr fork and crash:
xfs_attr_set: xfs_attr_get:
xfs_attr_fork_remove: xfs_ilock_attr_map_shared:
xfs_idestroy_fork(ip->i_afp);
kmem_cache_free(xfs_ifork_cache, ip->i_afp);
if (ip->i_afp &&
ip->i_afp = NULL;
xfs_need_iread_extents(ip->i_afp))
<KABOOM>
ip->i_forkoff = 0;
Regrettably, the VFS is much more lax about i_rwsem and getxattr than
is immediately obvious -- not only does it not guarantee that we hold
i_rwsem, it actually doesn't guarantee that we *don't* hold it either.
The getxattr system call won't acquire the lock before calling XFS, but
the file capabilities code calls getxattr with and without i_rwsem held
to determine if the "security.capabilities" xattr is set on the file.
Fixing the VFS locking requires a treewide investigation into every code
path that could touch an xattr and what i_rwsem state it expects or sets
up. That could take years or even prove impossible; fortunately, we
can fix this UAF problem inside XFS.
An earlier version of this patch used smp_wmb in xfs_attr_fork_remove to
ensure that i_forkoff is always zeroed before i_afp is set to null and
changed the read paths to use smp_rmb before accessing i_forkoff and
i_afp, which avoided these UAF problems. However, the patch author was
too busy dealing with other problems in the meantime, and by the time he
came back to this issue, the situation had changed a bit.
On a modern system with selinux, each inode will always have at least
one xattr for the selinux label, so it doesn't make much sense to keep
incurring the extra pointer dereference. Furthermore, Allison's
upcoming parent pointer patchset will also cause nearly every inode in
the filesystem to have extended attributes. Therefore, make the inode
attribute fork structure part of struct xfs_inode, at a cost of 40 more
bytes.
This patch adds a clunky if_present field where necessary to maintain
the existing logic of xattr fork null pointer testing in the existing
codebase. The next patch switches the logic over to XFS_IFORK_Q and it
all goes away.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Dave Chinner <dchinner@redhat.com>
2022-07-10 01:56:06 +08:00
|
|
|
to->di_aformat = xfs_ifork_format(&ip->i_af);
|
2021-03-30 02:11:44 +08:00
|
|
|
to->di_flags = cpu_to_be16(ip->i_diflags);
|
2016-02-09 13:54:58 +08:00
|
|
|
|
2021-08-19 09:46:55 +08:00
|
|
|
if (xfs_has_v3inodes(ip->i_mount)) {
|
2020-03-18 23:15:11 +08:00
|
|
|
to->di_version = 3;
|
2017-12-11 19:35:19 +08:00
|
|
|
to->di_changecount = cpu_to_be64(inode_peek_iversion(inode));
|
2021-03-30 02:11:45 +08:00
|
|
|
to->di_crtime = xfs_inode_to_disk_ts(ip, ip->i_crtime);
|
2021-03-30 02:11:45 +08:00
|
|
|
to->di_flags2 = cpu_to_be64(ip->i_diflags2);
|
2021-03-30 02:11:42 +08:00
|
|
|
to->di_cowextsize = cpu_to_be32(ip->i_cowextsize);
|
2016-02-09 13:54:58 +08:00
|
|
|
to->di_ino = cpu_to_be64(ip->i_ino);
|
|
|
|
to->di_lsn = cpu_to_be64(lsn);
|
|
|
|
memset(to->di_pad2, 0, sizeof(to->di_pad2));
|
|
|
|
uuid_copy(&to->di_uuid, &ip->i_mount->m_sb.sb_meta_uuid);
|
2022-03-08 17:34:28 +08:00
|
|
|
to->di_v3_pad = 0;
|
2016-02-09 13:54:58 +08:00
|
|
|
} else {
|
2020-03-18 23:15:11 +08:00
|
|
|
to->di_version = 2;
|
2021-03-30 02:11:42 +08:00
|
|
|
to->di_flushiter = cpu_to_be16(ip->i_flushiter);
|
2022-03-08 17:34:28 +08:00
|
|
|
memset(to->di_v2_pad, 0, sizeof(to->di_v2_pad));
|
2016-02-09 13:54:58 +08:00
|
|
|
}
|
2022-03-08 17:34:28 +08:00
|
|
|
|
|
|
|
xfs_inode_to_disk_iext_counters(ip, to);
|
2016-02-09 13:54:58 +08:00
|
|
|
}
|
|
|
|
|
2018-06-22 14:25:57 +08:00
|
|
|
static xfs_failaddr_t
|
|
|
|
xfs_dinode_verify_fork(
|
|
|
|
struct xfs_dinode *dip,
|
|
|
|
struct xfs_mount *mp,
|
|
|
|
int whichfork)
|
|
|
|
{
|
2020-08-27 18:04:34 +08:00
|
|
|
xfs_extnum_t di_nextents;
|
2020-08-27 17:39:10 +08:00
|
|
|
xfs_extnum_t max_extents;
|
2022-05-04 10:13:53 +08:00
|
|
|
mode_t mode = be16_to_cpu(dip->di_mode);
|
|
|
|
uint32_t fork_size = XFS_DFORK_SIZE(dip, mp, whichfork);
|
|
|
|
uint32_t fork_format = XFS_DFORK_FORMAT(dip, whichfork);
|
2018-06-22 14:25:57 +08:00
|
|
|
|
2020-08-27 18:04:34 +08:00
|
|
|
di_nextents = xfs_dfork_nextents(dip, whichfork);
|
|
|
|
|
2022-05-04 10:13:53 +08:00
|
|
|
/*
|
|
|
|
* For fork types that can contain local data, check that the fork
|
|
|
|
* format matches the size of local data contained within the fork.
|
|
|
|
*
|
|
|
|
* For all types, check that when the size says the should be in extent
|
|
|
|
* or btree format, the inode isn't claiming it is in local format.
|
|
|
|
*/
|
|
|
|
if (whichfork == XFS_DATA_FORK) {
|
|
|
|
if (S_ISDIR(mode) || S_ISLNK(mode)) {
|
|
|
|
if (be64_to_cpu(dip->di_size) <= fork_size &&
|
|
|
|
fork_format != XFS_DINODE_FMT_LOCAL)
|
|
|
|
return __this_address;
|
|
|
|
}
|
|
|
|
|
|
|
|
if (be64_to_cpu(dip->di_size) > fork_size &&
|
|
|
|
fork_format == XFS_DINODE_FMT_LOCAL)
|
|
|
|
return __this_address;
|
|
|
|
}
|
|
|
|
|
|
|
|
switch (fork_format) {
|
2018-06-22 14:25:57 +08:00
|
|
|
case XFS_DINODE_FMT_LOCAL:
|
|
|
|
/*
|
2022-05-04 10:13:53 +08:00
|
|
|
* No local regular files yet.
|
2018-06-22 14:25:57 +08:00
|
|
|
*/
|
2022-05-04 10:13:53 +08:00
|
|
|
if (S_ISREG(mode) && whichfork == XFS_DATA_FORK)
|
|
|
|
return __this_address;
|
2018-06-22 14:25:57 +08:00
|
|
|
if (di_nextents)
|
|
|
|
return __this_address;
|
|
|
|
break;
|
|
|
|
case XFS_DINODE_FMT_EXTENTS:
|
|
|
|
if (di_nextents > XFS_DFORK_MAXEXT(dip, mp, whichfork))
|
|
|
|
return __this_address;
|
|
|
|
break;
|
|
|
|
case XFS_DINODE_FMT_BTREE:
|
2021-11-16 17:54:37 +08:00
|
|
|
max_extents = xfs_iext_max_nextents(
|
|
|
|
xfs_dinode_has_large_extent_counts(dip),
|
|
|
|
whichfork);
|
2020-08-27 17:39:10 +08:00
|
|
|
if (di_nextents > max_extents)
|
2018-06-22 14:25:57 +08:00
|
|
|
return __this_address;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return __this_address;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-09-29 11:50:13 +08:00
|
|
|
static xfs_failaddr_t
|
|
|
|
xfs_dinode_verify_forkoff(
|
|
|
|
struct xfs_dinode *dip,
|
|
|
|
struct xfs_mount *mp)
|
|
|
|
{
|
2020-05-19 01:27:21 +08:00
|
|
|
if (!dip->di_forkoff)
|
2018-09-29 11:50:13 +08:00
|
|
|
return NULL;
|
|
|
|
|
|
|
|
switch (dip->di_format) {
|
|
|
|
case XFS_DINODE_FMT_DEV:
|
|
|
|
if (dip->di_forkoff != (roundup(sizeof(xfs_dev_t), 8) >> 3))
|
|
|
|
return __this_address;
|
|
|
|
break;
|
|
|
|
case XFS_DINODE_FMT_LOCAL: /* fall through ... */
|
|
|
|
case XFS_DINODE_FMT_EXTENTS: /* fall through ... */
|
|
|
|
case XFS_DINODE_FMT_BTREE:
|
2020-03-18 23:15:10 +08:00
|
|
|
if (dip->di_forkoff >= (XFS_LITINO(mp) >> 3))
|
2018-09-29 11:50:13 +08:00
|
|
|
return __this_address;
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return __this_address;
|
|
|
|
}
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2022-03-08 17:34:28 +08:00
|
|
|
static xfs_failaddr_t
|
|
|
|
xfs_dinode_verify_nrext64(
|
|
|
|
struct xfs_mount *mp,
|
|
|
|
struct xfs_dinode *dip)
|
|
|
|
{
|
|
|
|
if (xfs_dinode_has_large_extent_counts(dip)) {
|
|
|
|
if (!xfs_has_large_extent_counts(mp))
|
|
|
|
return __this_address;
|
|
|
|
if (dip->di_nrext64_pad != 0)
|
|
|
|
return __this_address;
|
|
|
|
} else if (dip->di_version >= 3) {
|
|
|
|
if (dip->di_v3_pad != 0)
|
|
|
|
return __this_address;
|
|
|
|
}
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
2018-01-09 02:51:03 +08:00
|
|
|
xfs_failaddr_t
|
2013-08-12 18:49:35 +08:00
|
|
|
xfs_dinode_verify(
|
|
|
|
struct xfs_mount *mp,
|
2016-11-08 08:56:06 +08:00
|
|
|
xfs_ino_t ino,
|
2013-08-12 18:49:35 +08:00
|
|
|
struct xfs_dinode *dip)
|
|
|
|
{
|
2018-06-06 01:06:44 +08:00
|
|
|
xfs_failaddr_t fa;
|
2017-01-18 03:41:41 +08:00
|
|
|
uint16_t mode;
|
2016-10-04 00:11:50 +08:00
|
|
|
uint16_t flags;
|
|
|
|
uint64_t flags2;
|
2018-01-09 02:51:04 +08:00
|
|
|
uint64_t di_size;
|
2020-08-27 18:04:34 +08:00
|
|
|
xfs_extnum_t nextents;
|
|
|
|
xfs_extnum_t naextents;
|
|
|
|
xfs_filblks_t nblocks;
|
2016-10-04 00:11:50 +08:00
|
|
|
|
2013-08-12 18:49:35 +08:00
|
|
|
if (dip->di_magic != cpu_to_be16(XFS_DINODE_MAGIC))
|
2018-01-09 02:51:03 +08:00
|
|
|
return __this_address;
|
2013-08-12 18:49:35 +08:00
|
|
|
|
2018-01-09 02:51:04 +08:00
|
|
|
/* Verify v3 integrity information first */
|
|
|
|
if (dip->di_version >= 3) {
|
2021-08-19 09:46:55 +08:00
|
|
|
if (!xfs_has_v3inodes(mp))
|
2018-01-09 02:51:04 +08:00
|
|
|
return __this_address;
|
|
|
|
if (!xfs_verify_cksum((char *)dip, mp->m_sb.sb_inodesize,
|
|
|
|
XFS_DINODE_CRC_OFF))
|
|
|
|
return __this_address;
|
|
|
|
if (be64_to_cpu(dip->di_ino) != ino)
|
|
|
|
return __this_address;
|
|
|
|
if (!uuid_equal(&dip->di_uuid, &mp->m_sb.sb_meta_uuid))
|
|
|
|
return __this_address;
|
|
|
|
}
|
2013-08-12 18:49:35 +08:00
|
|
|
|
2016-12-05 09:38:38 +08:00
|
|
|
/* don't allow invalid i_size */
|
2018-01-09 02:51:04 +08:00
|
|
|
di_size = be64_to_cpu(dip->di_size);
|
|
|
|
if (di_size & (1ULL << 63))
|
2018-01-09 02:51:03 +08:00
|
|
|
return __this_address;
|
2016-12-05 09:38:38 +08:00
|
|
|
|
2017-01-18 03:41:41 +08:00
|
|
|
mode = be16_to_cpu(dip->di_mode);
|
2017-01-18 03:41:44 +08:00
|
|
|
if (mode && xfs_mode_to_ftype(mode) == XFS_DIR3_FT_UNKNOWN)
|
2018-01-09 02:51:03 +08:00
|
|
|
return __this_address;
|
2017-01-18 03:41:41 +08:00
|
|
|
|
|
|
|
/* No zero-length symlinks/dirs. */
|
2018-01-09 02:51:04 +08:00
|
|
|
if ((S_ISLNK(mode) || S_ISDIR(mode)) && di_size == 0)
|
2018-01-09 02:51:03 +08:00
|
|
|
return __this_address;
|
2016-12-05 09:38:38 +08:00
|
|
|
|
2022-03-08 17:34:28 +08:00
|
|
|
fa = xfs_dinode_verify_nrext64(mp, dip);
|
|
|
|
if (fa)
|
|
|
|
return fa;
|
|
|
|
|
2020-08-27 18:04:34 +08:00
|
|
|
nextents = xfs_dfork_data_extents(dip);
|
|
|
|
naextents = xfs_dfork_attr_extents(dip);
|
|
|
|
nblocks = be64_to_cpu(dip->di_nblocks);
|
|
|
|
|
2018-01-09 02:51:04 +08:00
|
|
|
/* Fork checks carried over from xfs_iformat_fork */
|
2020-08-27 18:04:34 +08:00
|
|
|
if (mode && nextents + naextents > nblocks)
|
2018-01-09 02:51:04 +08:00
|
|
|
return __this_address;
|
|
|
|
|
2022-03-29 14:14:00 +08:00
|
|
|
if (S_ISDIR(mode) && nextents > mp->m_dir_geo->max_extents)
|
|
|
|
return __this_address;
|
|
|
|
|
2018-01-09 02:51:04 +08:00
|
|
|
if (mode && XFS_DFORK_BOFF(dip) > mp->m_sb.sb_inodesize)
|
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
flags = be16_to_cpu(dip->di_flags);
|
|
|
|
|
|
|
|
if (mode && (flags & XFS_DIFLAG_REALTIME) && !mp->m_rtdev_targp)
|
|
|
|
return __this_address;
|
|
|
|
|
2018-09-29 11:50:13 +08:00
|
|
|
/* check for illegal values of forkoff */
|
|
|
|
fa = xfs_dinode_verify_forkoff(dip, mp);
|
|
|
|
if (fa)
|
|
|
|
return fa;
|
|
|
|
|
2018-01-09 02:51:04 +08:00
|
|
|
/* Do we have appropriate data fork formats for the mode? */
|
|
|
|
switch (mode & S_IFMT) {
|
|
|
|
case S_IFIFO:
|
|
|
|
case S_IFCHR:
|
|
|
|
case S_IFBLK:
|
|
|
|
case S_IFSOCK:
|
|
|
|
if (dip->di_format != XFS_DINODE_FMT_DEV)
|
|
|
|
return __this_address;
|
|
|
|
break;
|
|
|
|
case S_IFREG:
|
|
|
|
case S_IFLNK:
|
|
|
|
case S_IFDIR:
|
2018-06-22 14:25:57 +08:00
|
|
|
fa = xfs_dinode_verify_fork(dip, mp, XFS_DATA_FORK);
|
|
|
|
if (fa)
|
|
|
|
return fa;
|
2018-01-09 02:51:04 +08:00
|
|
|
break;
|
|
|
|
case 0:
|
|
|
|
/* Uninitialized inode ok. */
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return __this_address;
|
|
|
|
}
|
|
|
|
|
2020-05-19 01:27:21 +08:00
|
|
|
if (dip->di_forkoff) {
|
2018-06-22 14:25:57 +08:00
|
|
|
fa = xfs_dinode_verify_fork(dip, mp, XFS_ATTR_FORK);
|
|
|
|
if (fa)
|
|
|
|
return fa;
|
2018-04-17 14:06:53 +08:00
|
|
|
} else {
|
|
|
|
/*
|
|
|
|
* If there is no fork offset, this may be a freshly-made inode
|
|
|
|
* in a new disk cluster, in which case di_aformat is zeroed.
|
|
|
|
* Otherwise, such an inode must be in EXTENTS format; this goes
|
|
|
|
* for freed inodes as well.
|
|
|
|
*/
|
|
|
|
switch (dip->di_aformat) {
|
|
|
|
case 0:
|
|
|
|
case XFS_DINODE_FMT_EXTENTS:
|
|
|
|
break;
|
|
|
|
default:
|
|
|
|
return __this_address;
|
|
|
|
}
|
2020-08-27 18:04:34 +08:00
|
|
|
if (naextents)
|
2018-04-17 14:06:53 +08:00
|
|
|
return __this_address;
|
2018-01-09 02:51:04 +08:00
|
|
|
}
|
2016-12-05 09:38:38 +08:00
|
|
|
|
2018-06-06 01:06:44 +08:00
|
|
|
/* extent size hint validation */
|
|
|
|
fa = xfs_inode_validate_extsize(mp, be32_to_cpu(dip->di_extsize),
|
|
|
|
mode, flags);
|
|
|
|
if (fa)
|
|
|
|
return fa;
|
|
|
|
|
2013-08-12 18:49:35 +08:00
|
|
|
/* only version 3 or greater inodes are extensively verified here */
|
|
|
|
if (dip->di_version < 3)
|
2018-01-09 02:51:03 +08:00
|
|
|
return NULL;
|
2016-10-04 00:11:50 +08:00
|
|
|
|
|
|
|
flags2 = be64_to_cpu(dip->di_flags2);
|
|
|
|
|
|
|
|
/* don't allow reflink/cowextsize if we don't have reflink */
|
|
|
|
if ((flags2 & (XFS_DIFLAG2_REFLINK | XFS_DIFLAG2_COWEXTSIZE)) &&
|
2021-08-19 09:46:37 +08:00
|
|
|
!xfs_has_reflink(mp))
|
2018-01-09 02:51:03 +08:00
|
|
|
return __this_address;
|
2016-10-04 00:11:50 +08:00
|
|
|
|
2018-01-09 02:51:04 +08:00
|
|
|
/* only regular files get reflink */
|
|
|
|
if ((flags2 & XFS_DIFLAG2_REFLINK) && (mode & S_IFMT) != S_IFREG)
|
|
|
|
return __this_address;
|
2016-10-04 00:11:50 +08:00
|
|
|
|
|
|
|
/* don't let reflink and realtime mix */
|
|
|
|
if ((flags2 & XFS_DIFLAG2_REFLINK) && (flags & XFS_DIFLAG_REALTIME))
|
2018-01-09 02:51:03 +08:00
|
|
|
return __this_address;
|
2016-10-04 00:11:50 +08:00
|
|
|
|
2018-06-06 01:09:33 +08:00
|
|
|
/* COW extent size hint validation */
|
|
|
|
fa = xfs_inode_validate_cowextsize(mp, be32_to_cpu(dip->di_cowextsize),
|
|
|
|
mode, flags, flags2);
|
|
|
|
if (fa)
|
|
|
|
return fa;
|
|
|
|
|
2020-08-18 00:59:07 +08:00
|
|
|
/* bigtime iflag can only happen on bigtime filesystems */
|
|
|
|
if (xfs_dinode_has_bigtime(dip) &&
|
2021-08-19 09:46:55 +08:00
|
|
|
!xfs_has_bigtime(mp))
|
2020-08-18 00:59:07 +08:00
|
|
|
return __this_address;
|
|
|
|
|
2018-01-09 02:51:03 +08:00
|
|
|
return NULL;
|
2013-08-12 18:49:35 +08:00
|
|
|
}
|
|
|
|
|
|
|
|
void
|
|
|
|
xfs_dinode_calc_crc(
|
|
|
|
struct xfs_mount *mp,
|
|
|
|
struct xfs_dinode *dip)
|
|
|
|
{
|
2017-06-17 02:00:05 +08:00
|
|
|
uint32_t crc;
|
2013-08-12 18:49:35 +08:00
|
|
|
|
|
|
|
if (dip->di_version < 3)
|
|
|
|
return;
|
|
|
|
|
2021-08-19 09:46:37 +08:00
|
|
|
ASSERT(xfs_has_crc(mp));
|
2016-12-05 11:40:32 +08:00
|
|
|
crc = xfs_start_cksum_update((char *)dip, mp->m_sb.sb_inodesize,
|
2014-02-27 12:15:27 +08:00
|
|
|
XFS_DINODE_CRC_OFF);
|
2013-08-12 18:49:35 +08:00
|
|
|
dip->di_crc = xfs_end_cksum(crc);
|
|
|
|
}
|
|
|
|
|
2018-03-24 01:06:55 +08:00
|
|
|
/*
|
|
|
|
* Validate di_extsize hint.
|
|
|
|
*
|
2021-05-13 03:49:19 +08:00
|
|
|
* 1. Extent size hint is only valid for directories and regular files.
|
|
|
|
* 2. FS_XFLAG_EXTSIZE is only valid for regular files.
|
|
|
|
* 3. FS_XFLAG_EXTSZINHERIT is only valid for directories.
|
|
|
|
* 4. Hint cannot be larger than MAXTEXTLEN.
|
|
|
|
* 5. Can be changed on directories at any time.
|
|
|
|
* 6. Hint value of 0 turns off hints, clears inode flags.
|
|
|
|
* 7. Extent size must be a multiple of the appropriate block size.
|
|
|
|
* For realtime files, this is the rt extent size.
|
|
|
|
* 8. For non-realtime files, the extent size hint must be limited
|
|
|
|
* to half the AG size to avoid alignment extending the extent beyond the
|
|
|
|
* limits of the AG.
|
2018-03-24 01:06:55 +08:00
|
|
|
*/
|
|
|
|
xfs_failaddr_t
|
|
|
|
xfs_inode_validate_extsize(
|
|
|
|
struct xfs_mount *mp,
|
|
|
|
uint32_t extsize,
|
|
|
|
uint16_t mode,
|
|
|
|
uint16_t flags)
|
|
|
|
{
|
|
|
|
bool rt_flag;
|
|
|
|
bool hint_flag;
|
|
|
|
bool inherit_flag;
|
|
|
|
uint32_t extsize_bytes;
|
|
|
|
uint32_t blocksize_bytes;
|
|
|
|
|
|
|
|
rt_flag = (flags & XFS_DIFLAG_REALTIME);
|
|
|
|
hint_flag = (flags & XFS_DIFLAG_EXTSIZE);
|
|
|
|
inherit_flag = (flags & XFS_DIFLAG_EXTSZINHERIT);
|
|
|
|
extsize_bytes = XFS_FSB_TO_B(mp, extsize);
|
|
|
|
|
xfs: validate extsz hints against rt extent size when rtinherit is set
The RTINHERIT bit can be set on a directory so that newly created
regular files will have the REALTIME bit set to store their data on the
realtime volume. If an extent size hint (and EXTSZINHERIT) are set on
the directory, the hint will also be copied into the new file.
As pointed out in previous patches, for realtime files we require the
extent size hint be an integer multiple of the realtime extent, but we
don't perform the same validation on a directory with both RTINHERIT and
EXTSZINHERIT set, even though the only use-case of that combination is
to propagate extent size hints into new realtime files. This leads to
inode corruption errors when the bad values are propagated.
Because there may be existing filesystems with such a configuration, we
cannot simply amend the inode verifier to trip on these directories and
call it a day because that will cause previously "working" filesystems
to start throwing errors abruptly. Note that it's valid to have
directories with rtinherit set even if there is no realtime volume, in
which case the problem does not manifest because rtinherit is ignored if
there's no realtime device; and it's possible that someone set the flag,
crashed, repaired the filesystem (which clears the hint on the realtime
file) and continued.
Therefore, mitigate this issue in several ways: First, if we try to
write out an inode with both rtinherit/extszinherit set and an unaligned
extent size hint, turn off the hint to correct the error. Second, if
someone tries to misconfigure a directory via the fssetxattr ioctl, fail
the ioctl. Third, reverify both extent size hint values when we
propagate heritable inode attributes from parent to child, to prevent
misconfigurations from spreading.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
2021-05-13 03:51:26 +08:00
|
|
|
/*
|
|
|
|
* This comment describes a historic gap in this verifier function.
|
|
|
|
*
|
2021-07-13 03:58:50 +08:00
|
|
|
* For a directory with both RTINHERIT and EXTSZINHERIT flags set, this
|
|
|
|
* function has never checked that the extent size hint is an integer
|
|
|
|
* multiple of the realtime extent size. Since we allow users to set
|
|
|
|
* this combination on non-rt filesystems /and/ to change the rt
|
|
|
|
* extent size when adding a rt device to a filesystem, the net effect
|
|
|
|
* is that users can configure a filesystem anticipating one rt
|
|
|
|
* geometry and change their minds later. Directories do not use the
|
|
|
|
* extent size hint, so this is harmless for them.
|
xfs: validate extsz hints against rt extent size when rtinherit is set
The RTINHERIT bit can be set on a directory so that newly created
regular files will have the REALTIME bit set to store their data on the
realtime volume. If an extent size hint (and EXTSZINHERIT) are set on
the directory, the hint will also be copied into the new file.
As pointed out in previous patches, for realtime files we require the
extent size hint be an integer multiple of the realtime extent, but we
don't perform the same validation on a directory with both RTINHERIT and
EXTSZINHERIT set, even though the only use-case of that combination is
to propagate extent size hints into new realtime files. This leads to
inode corruption errors when the bad values are propagated.
Because there may be existing filesystems with such a configuration, we
cannot simply amend the inode verifier to trip on these directories and
call it a day because that will cause previously "working" filesystems
to start throwing errors abruptly. Note that it's valid to have
directories with rtinherit set even if there is no realtime volume, in
which case the problem does not manifest because rtinherit is ignored if
there's no realtime device; and it's possible that someone set the flag,
crashed, repaired the filesystem (which clears the hint on the realtime
file) and continued.
Therefore, mitigate this issue in several ways: First, if we try to
write out an inode with both rtinherit/extszinherit set and an unaligned
extent size hint, turn off the hint to correct the error. Second, if
someone tries to misconfigure a directory via the fssetxattr ioctl, fail
the ioctl. Third, reverify both extent size hint values when we
propagate heritable inode attributes from parent to child, to prevent
misconfigurations from spreading.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
2021-05-13 03:51:26 +08:00
|
|
|
*
|
|
|
|
* If a directory with a misaligned extent size hint is allowed to
|
|
|
|
* propagate that hint into a new regular realtime file, the result
|
|
|
|
* is that the inode cluster buffer verifier will trigger a corruption
|
2021-07-13 03:58:50 +08:00
|
|
|
* shutdown the next time it is run, because the verifier has always
|
|
|
|
* enforced the alignment rule for regular files.
|
xfs: validate extsz hints against rt extent size when rtinherit is set
The RTINHERIT bit can be set on a directory so that newly created
regular files will have the REALTIME bit set to store their data on the
realtime volume. If an extent size hint (and EXTSZINHERIT) are set on
the directory, the hint will also be copied into the new file.
As pointed out in previous patches, for realtime files we require the
extent size hint be an integer multiple of the realtime extent, but we
don't perform the same validation on a directory with both RTINHERIT and
EXTSZINHERIT set, even though the only use-case of that combination is
to propagate extent size hints into new realtime files. This leads to
inode corruption errors when the bad values are propagated.
Because there may be existing filesystems with such a configuration, we
cannot simply amend the inode verifier to trip on these directories and
call it a day because that will cause previously "working" filesystems
to start throwing errors abruptly. Note that it's valid to have
directories with rtinherit set even if there is no realtime volume, in
which case the problem does not manifest because rtinherit is ignored if
there's no realtime device; and it's possible that someone set the flag,
crashed, repaired the filesystem (which clears the hint on the realtime
file) and continued.
Therefore, mitigate this issue in several ways: First, if we try to
write out an inode with both rtinherit/extszinherit set and an unaligned
extent size hint, turn off the hint to correct the error. Second, if
someone tries to misconfigure a directory via the fssetxattr ioctl, fail
the ioctl. Third, reverify both extent size hint values when we
propagate heritable inode attributes from parent to child, to prevent
misconfigurations from spreading.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
2021-05-13 03:51:26 +08:00
|
|
|
*
|
2021-07-13 03:58:50 +08:00
|
|
|
* Because we allow administrators to set a new rt extent size when
|
|
|
|
* adding a rt section, we cannot add a check to this verifier because
|
|
|
|
* that will result a new source of directory corruption errors when
|
|
|
|
* reading an existing filesystem. Instead, we rely on callers to
|
|
|
|
* decide when alignment checks are appropriate, and fix things up as
|
|
|
|
* needed.
|
xfs: validate extsz hints against rt extent size when rtinherit is set
The RTINHERIT bit can be set on a directory so that newly created
regular files will have the REALTIME bit set to store their data on the
realtime volume. If an extent size hint (and EXTSZINHERIT) are set on
the directory, the hint will also be copied into the new file.
As pointed out in previous patches, for realtime files we require the
extent size hint be an integer multiple of the realtime extent, but we
don't perform the same validation on a directory with both RTINHERIT and
EXTSZINHERIT set, even though the only use-case of that combination is
to propagate extent size hints into new realtime files. This leads to
inode corruption errors when the bad values are propagated.
Because there may be existing filesystems with such a configuration, we
cannot simply amend the inode verifier to trip on these directories and
call it a day because that will cause previously "working" filesystems
to start throwing errors abruptly. Note that it's valid to have
directories with rtinherit set even if there is no realtime volume, in
which case the problem does not manifest because rtinherit is ignored if
there's no realtime device; and it's possible that someone set the flag,
crashed, repaired the filesystem (which clears the hint on the realtime
file) and continued.
Therefore, mitigate this issue in several ways: First, if we try to
write out an inode with both rtinherit/extszinherit set and an unaligned
extent size hint, turn off the hint to correct the error. Second, if
someone tries to misconfigure a directory via the fssetxattr ioctl, fail
the ioctl. Third, reverify both extent size hint values when we
propagate heritable inode attributes from parent to child, to prevent
misconfigurations from spreading.
Signed-off-by: Darrick J. Wong <djwong@kernel.org>
Reviewed-by: Carlos Maiolino <cmaiolino@redhat.com>
Reviewed-by: Brian Foster <bfoster@redhat.com>
2021-05-13 03:51:26 +08:00
|
|
|
*/
|
|
|
|
|
2018-03-24 01:06:55 +08:00
|
|
|
if (rt_flag)
|
2021-06-01 02:31:56 +08:00
|
|
|
blocksize_bytes = XFS_FSB_TO_B(mp, mp->m_sb.sb_rextsize);
|
2018-03-24 01:06:55 +08:00
|
|
|
else
|
|
|
|
blocksize_bytes = mp->m_sb.sb_blocksize;
|
|
|
|
|
|
|
|
if ((hint_flag || inherit_flag) && !(S_ISDIR(mode) || S_ISREG(mode)))
|
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
if (hint_flag && !S_ISREG(mode))
|
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
if (inherit_flag && !S_ISDIR(mode))
|
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
if ((hint_flag || inherit_flag) && extsize == 0)
|
|
|
|
return __this_address;
|
|
|
|
|
2018-07-25 02:34:52 +08:00
|
|
|
/* free inodes get flags set to zero but extsize remains */
|
|
|
|
if (mode && !(hint_flag || inherit_flag) && extsize != 0)
|
2018-03-24 01:06:55 +08:00
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
if (extsize_bytes % blocksize_bytes)
|
|
|
|
return __this_address;
|
|
|
|
|
2021-08-09 14:35:22 +08:00
|
|
|
if (extsize > XFS_MAX_BMBT_EXTLEN)
|
2018-03-24 01:06:55 +08:00
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
if (!rt_flag && extsize > mp->m_sb.sb_agblocks / 2)
|
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|
|
|
|
|
|
|
|
/*
|
|
|
|
* Validate di_cowextsize hint.
|
|
|
|
*
|
2021-05-13 03:49:19 +08:00
|
|
|
* 1. CoW extent size hint can only be set if reflink is enabled on the fs.
|
|
|
|
* The inode does not have to have any shared blocks, but it must be a v3.
|
|
|
|
* 2. FS_XFLAG_COWEXTSIZE is only valid for directories and regular files;
|
|
|
|
* for a directory, the hint is propagated to new files.
|
|
|
|
* 3. Can be changed on files & directories at any time.
|
|
|
|
* 4. Hint value of 0 turns off hints, clears inode flags.
|
|
|
|
* 5. Extent size must be a multiple of the appropriate block size.
|
|
|
|
* 6. The extent size hint must be limited to half the AG size to avoid
|
|
|
|
* alignment extending the extent beyond the limits of the AG.
|
2018-03-24 01:06:55 +08:00
|
|
|
*/
|
|
|
|
xfs_failaddr_t
|
|
|
|
xfs_inode_validate_cowextsize(
|
|
|
|
struct xfs_mount *mp,
|
|
|
|
uint32_t cowextsize,
|
|
|
|
uint16_t mode,
|
|
|
|
uint16_t flags,
|
|
|
|
uint64_t flags2)
|
|
|
|
{
|
|
|
|
bool rt_flag;
|
|
|
|
bool hint_flag;
|
|
|
|
uint32_t cowextsize_bytes;
|
|
|
|
|
|
|
|
rt_flag = (flags & XFS_DIFLAG_REALTIME);
|
|
|
|
hint_flag = (flags2 & XFS_DIFLAG2_COWEXTSIZE);
|
|
|
|
cowextsize_bytes = XFS_FSB_TO_B(mp, cowextsize);
|
|
|
|
|
2021-08-19 09:46:37 +08:00
|
|
|
if (hint_flag && !xfs_has_reflink(mp))
|
2018-03-24 01:06:55 +08:00
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
if (hint_flag && !(S_ISDIR(mode) || S_ISREG(mode)))
|
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
if (hint_flag && cowextsize == 0)
|
|
|
|
return __this_address;
|
|
|
|
|
2018-07-25 02:34:52 +08:00
|
|
|
/* free inodes get flags set to zero but cowextsize remains */
|
|
|
|
if (mode && !hint_flag && cowextsize != 0)
|
2018-03-24 01:06:55 +08:00
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
if (hint_flag && rt_flag)
|
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
if (cowextsize_bytes % mp->m_sb.sb_blocksize)
|
|
|
|
return __this_address;
|
|
|
|
|
2021-08-09 14:35:22 +08:00
|
|
|
if (cowextsize > XFS_MAX_BMBT_EXTLEN)
|
2018-03-24 01:06:55 +08:00
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
if (cowextsize > mp->m_sb.sb_agblocks / 2)
|
|
|
|
return __this_address;
|
|
|
|
|
|
|
|
return NULL;
|
|
|
|
}
|