- Rework the inline directory verifier to avoid crashes on disk corruption
 - Don't change file size when punching holes w/ KEEP_SIZE
 - Close a kernel memory exposure bug
 -----BEGIN PGP SIGNATURE-----
 Version: GnuPG v1
 
 iQIcBAABCgAGBQJY4qGYAAoJEPh/dxk0SrTr3mwQAIDqc1RlZThYETn5Mru9BeQ0
 NmiDbgO1394OSxSxpBVZCVW1jU23j3eXbOgO0oD8iEOySdTOwAoCxb78aYUkHsbS
 wKKvix2kprIfsAYfGW264MZjX2JYiBUTjhV8dYw9UB5Kot3TPvc+IgC7ICJtcb08
 y+/ycjbgMwlWxOQwkReaPhVYhKXa3/vqLBNN0E9oacSdpT10Mkb95RDxwFpe5zX2
 RjPYode8RHsoI9+OdvVwvbmPrvylxEt3jKZdnStRMjmAX/X2aUNVDYSCOSpNg+sz
 9kXec96kkoJ4oQVfiaEo0k4LWlTZnDSFe25CX8Gxu7yPgGt8XsnDADkieGBdVcYq
 2q3AfEpmQrYgnLFRu4D3A8KEX8IrP8MLAh/zY/heVp9fTx9uhDvnjF+f+F6mMYXF
 wzEf27RN+DeaXRLbHp35NOKPriT63JpRB/Hsgew6ibEiVPdy8J1+VYoJSohixr4d
 yryDiwtph25spMYxeH6HIiPCeWa6fMr08vwuasWJTEZirxYmF/ZEHTK/bU+ydlDp
 R9/UDTmiP0V10xO09bjLgFiREIO/b1fiOoniAMhhrT4tW2FL31hRpzl8jPZ++2X/
 W2xLMJ1iyVw/Cf1NDWGFIOFX6Y+lnN6dZHftNUno6MFlmdmajNaTbRTBiSGnPH4U
 URH7Dxpmkgp7xuNs8xqv
 =QdXx
 -----END PGP SIGNATURE-----

Merge tag 'xfs-4.11-fixes-3' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux

Pull XFS fixes from Darrick Wong:
 "Here are three more fixes for 4.11.

  The first one reworks the inline directory verifier to check the
  working copy of the directory metadata and to avoid triggering a
  periodic crash in xfs/348. The second patch fixes a regression in hole
  punching at EOF that corrupts files; and the third patch closes a
  kernel memory disclosure bug.

  Summary:

   - rework the inline directory verifier to avoid crashes on disk
     corruption

   - don't change file size when punching holes w/ KEEP_SIZE

   - close a kernel memory exposure bug"

* tag 'xfs-4.11-fixes-3' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux:
  xfs: fix kernel memory exposure problems
  xfs: Honor FALLOC_FL_KEEP_SIZE when punching ends of files
  xfs: rework the inline directory verifiers
This commit is contained in:
Linus Torvalds 2017-04-06 14:42:05 -07:00
Родитель 977674c06b bf9216f922
Коммит 269c930e66
7 изменённых файлов: 76 добавлений и 58 удалений

Просмотреть файл

@ -125,8 +125,7 @@ extern int xfs_dir2_sf_create(struct xfs_da_args *args, xfs_ino_t pino);
extern int xfs_dir2_sf_lookup(struct xfs_da_args *args); extern int xfs_dir2_sf_lookup(struct xfs_da_args *args);
extern int xfs_dir2_sf_removename(struct xfs_da_args *args); extern int xfs_dir2_sf_removename(struct xfs_da_args *args);
extern int xfs_dir2_sf_replace(struct xfs_da_args *args); extern int xfs_dir2_sf_replace(struct xfs_da_args *args);
extern int xfs_dir2_sf_verify(struct xfs_mount *mp, struct xfs_dir2_sf_hdr *sfp, extern int xfs_dir2_sf_verify(struct xfs_inode *ip);
int size);
/* xfs_dir2_readdir.c */ /* xfs_dir2_readdir.c */
extern int xfs_readdir(struct xfs_inode *dp, struct dir_context *ctx, extern int xfs_readdir(struct xfs_inode *dp, struct dir_context *ctx,

Просмотреть файл

@ -632,36 +632,49 @@ xfs_dir2_sf_check(
/* Verify the consistency of an inline directory. */ /* Verify the consistency of an inline directory. */
int int
xfs_dir2_sf_verify( xfs_dir2_sf_verify(
struct xfs_mount *mp, struct xfs_inode *ip)
struct xfs_dir2_sf_hdr *sfp,
int size)
{ {
struct xfs_mount *mp = ip->i_mount;
struct xfs_dir2_sf_hdr *sfp;
struct xfs_dir2_sf_entry *sfep; struct xfs_dir2_sf_entry *sfep;
struct xfs_dir2_sf_entry *next_sfep; struct xfs_dir2_sf_entry *next_sfep;
char *endp; char *endp;
const struct xfs_dir_ops *dops; const struct xfs_dir_ops *dops;
struct xfs_ifork *ifp;
xfs_ino_t ino; xfs_ino_t ino;
int i; int i;
int i8count; int i8count;
int offset; int offset;
int size;
int error;
__uint8_t filetype; __uint8_t filetype;
ASSERT(ip->i_d.di_format == XFS_DINODE_FMT_LOCAL);
/*
* xfs_iread calls us before xfs_setup_inode sets up ip->d_ops,
* so we can only trust the mountpoint to have the right pointer.
*/
dops = xfs_dir_get_ops(mp, NULL); dops = xfs_dir_get_ops(mp, NULL);
ifp = XFS_IFORK_PTR(ip, XFS_DATA_FORK);
sfp = (struct xfs_dir2_sf_hdr *)ifp->if_u1.if_data;
size = ifp->if_bytes;
/* /*
* Give up if the directory is way too short. * Give up if the directory is way too short.
*/ */
XFS_WANT_CORRUPTED_RETURN(mp, size > if (size <= offsetof(struct xfs_dir2_sf_hdr, parent) ||
offsetof(struct xfs_dir2_sf_hdr, parent)); size < xfs_dir2_sf_hdr_size(sfp->i8count))
XFS_WANT_CORRUPTED_RETURN(mp, size >= return -EFSCORRUPTED;
xfs_dir2_sf_hdr_size(sfp->i8count));
endp = (char *)sfp + size; endp = (char *)sfp + size;
/* Check .. entry */ /* Check .. entry */
ino = dops->sf_get_parent_ino(sfp); ino = dops->sf_get_parent_ino(sfp);
i8count = ino > XFS_DIR2_MAX_SHORT_INUM; i8count = ino > XFS_DIR2_MAX_SHORT_INUM;
XFS_WANT_CORRUPTED_RETURN(mp, !xfs_dir_ino_validate(mp, ino)); error = xfs_dir_ino_validate(mp, ino);
if (error)
return error;
offset = dops->data_first_offset; offset = dops->data_first_offset;
/* Check all reported entries */ /* Check all reported entries */
@ -672,12 +685,12 @@ xfs_dir2_sf_verify(
* Check the fixed-offset parts of the structure are * Check the fixed-offset parts of the structure are
* within the data buffer. * within the data buffer.
*/ */
XFS_WANT_CORRUPTED_RETURN(mp, if (((char *)sfep + sizeof(*sfep)) >= endp)
((char *)sfep + sizeof(*sfep)) < endp); return -EFSCORRUPTED;
/* Don't allow names with known bad length. */ /* Don't allow names with known bad length. */
XFS_WANT_CORRUPTED_RETURN(mp, sfep->namelen > 0); if (sfep->namelen == 0)
XFS_WANT_CORRUPTED_RETURN(mp, sfep->namelen < MAXNAMELEN); return -EFSCORRUPTED;
/* /*
* Check that the variable-length part of the structure is * Check that the variable-length part of the structure is
@ -685,33 +698,39 @@ xfs_dir2_sf_verify(
* name component, so nextentry is an acceptable test. * name component, so nextentry is an acceptable test.
*/ */
next_sfep = dops->sf_nextentry(sfp, sfep); next_sfep = dops->sf_nextentry(sfp, sfep);
XFS_WANT_CORRUPTED_RETURN(mp, endp >= (char *)next_sfep); if (endp < (char *)next_sfep)
return -EFSCORRUPTED;
/* Check that the offsets always increase. */ /* Check that the offsets always increase. */
XFS_WANT_CORRUPTED_RETURN(mp, if (xfs_dir2_sf_get_offset(sfep) < offset)
xfs_dir2_sf_get_offset(sfep) >= offset); return -EFSCORRUPTED;
/* Check the inode number. */ /* Check the inode number. */
ino = dops->sf_get_ino(sfp, sfep); ino = dops->sf_get_ino(sfp, sfep);
i8count += ino > XFS_DIR2_MAX_SHORT_INUM; i8count += ino > XFS_DIR2_MAX_SHORT_INUM;
XFS_WANT_CORRUPTED_RETURN(mp, !xfs_dir_ino_validate(mp, ino)); error = xfs_dir_ino_validate(mp, ino);
if (error)
return error;
/* Check the file type. */ /* Check the file type. */
filetype = dops->sf_get_ftype(sfep); filetype = dops->sf_get_ftype(sfep);
XFS_WANT_CORRUPTED_RETURN(mp, filetype < XFS_DIR3_FT_MAX); if (filetype >= XFS_DIR3_FT_MAX)
return -EFSCORRUPTED;
offset = xfs_dir2_sf_get_offset(sfep) + offset = xfs_dir2_sf_get_offset(sfep) +
dops->data_entsize(sfep->namelen); dops->data_entsize(sfep->namelen);
sfep = next_sfep; sfep = next_sfep;
} }
XFS_WANT_CORRUPTED_RETURN(mp, i8count == sfp->i8count); if (i8count != sfp->i8count)
XFS_WANT_CORRUPTED_RETURN(mp, (void *)sfep == (void *)endp); return -EFSCORRUPTED;
if ((void *)sfep != (void *)endp)
return -EFSCORRUPTED;
/* Make sure this whole thing ought to be in local format. */ /* Make sure this whole thing ought to be in local format. */
XFS_WANT_CORRUPTED_RETURN(mp, offset + if (offset + (sfp->count + 2) * (uint)sizeof(xfs_dir2_leaf_entry_t) +
(sfp->count + 2) * (uint)sizeof(xfs_dir2_leaf_entry_t) + (uint)sizeof(xfs_dir2_block_tail_t) > mp->m_dir_geo->blksize)
(uint)sizeof(xfs_dir2_block_tail_t) <= mp->m_dir_geo->blksize); return -EFSCORRUPTED;
return 0; return 0;
} }

Просмотреть файл

@ -212,6 +212,16 @@ xfs_iformat_fork(
if (error) if (error)
return error; return error;
/* Check inline dir contents. */
if (S_ISDIR(VFS_I(ip)->i_mode) &&
dip->di_format == XFS_DINODE_FMT_LOCAL) {
error = xfs_dir2_sf_verify(ip);
if (error) {
xfs_idestroy_fork(ip, XFS_DATA_FORK);
return error;
}
}
if (xfs_is_reflink_inode(ip)) { if (xfs_is_reflink_inode(ip)) {
ASSERT(ip->i_cowfp == NULL); ASSERT(ip->i_cowfp == NULL);
xfs_ifork_init_cow(ip); xfs_ifork_init_cow(ip);
@ -322,8 +332,6 @@ xfs_iformat_local(
int whichfork, int whichfork,
int size) int size)
{ {
int error;
/* /*
* If the size is unreasonable, then something * If the size is unreasonable, then something
* is wrong and we just bail out rather than crash in * is wrong and we just bail out rather than crash in
@ -339,14 +347,6 @@ xfs_iformat_local(
return -EFSCORRUPTED; return -EFSCORRUPTED;
} }
if (S_ISDIR(VFS_I(ip)->i_mode) && whichfork == XFS_DATA_FORK) {
error = xfs_dir2_sf_verify(ip->i_mount,
(struct xfs_dir2_sf_hdr *)XFS_DFORK_DPTR(dip),
size);
if (error)
return error;
}
xfs_init_local_fork(ip, whichfork, XFS_DFORK_PTR(dip, whichfork), size); xfs_init_local_fork(ip, whichfork, XFS_DFORK_PTR(dip, whichfork), size);
return 0; return 0;
} }
@ -867,7 +867,7 @@ xfs_iextents_copy(
* In these cases, the format always takes precedence, because the * In these cases, the format always takes precedence, because the
* format indicates the current state of the fork. * format indicates the current state of the fork.
*/ */
int void
xfs_iflush_fork( xfs_iflush_fork(
xfs_inode_t *ip, xfs_inode_t *ip,
xfs_dinode_t *dip, xfs_dinode_t *dip,
@ -877,7 +877,6 @@ xfs_iflush_fork(
char *cp; char *cp;
xfs_ifork_t *ifp; xfs_ifork_t *ifp;
xfs_mount_t *mp; xfs_mount_t *mp;
int error;
static const short brootflag[2] = static const short brootflag[2] =
{ XFS_ILOG_DBROOT, XFS_ILOG_ABROOT }; { XFS_ILOG_DBROOT, XFS_ILOG_ABROOT };
static const short dataflag[2] = static const short dataflag[2] =
@ -886,7 +885,7 @@ xfs_iflush_fork(
{ XFS_ILOG_DEXT, XFS_ILOG_AEXT }; { XFS_ILOG_DEXT, XFS_ILOG_AEXT };
if (!iip) if (!iip)
return 0; return;
ifp = XFS_IFORK_PTR(ip, whichfork); ifp = XFS_IFORK_PTR(ip, whichfork);
/* /*
* This can happen if we gave up in iformat in an error path, * This can happen if we gave up in iformat in an error path,
@ -894,19 +893,12 @@ xfs_iflush_fork(
*/ */
if (!ifp) { if (!ifp) {
ASSERT(whichfork == XFS_ATTR_FORK); ASSERT(whichfork == XFS_ATTR_FORK);
return 0; return;
} }
cp = XFS_DFORK_PTR(dip, whichfork); cp = XFS_DFORK_PTR(dip, whichfork);
mp = ip->i_mount; mp = ip->i_mount;
switch (XFS_IFORK_FORMAT(ip, whichfork)) { switch (XFS_IFORK_FORMAT(ip, whichfork)) {
case XFS_DINODE_FMT_LOCAL: case XFS_DINODE_FMT_LOCAL:
if (S_ISDIR(VFS_I(ip)->i_mode) && whichfork == XFS_DATA_FORK) {
error = xfs_dir2_sf_verify(mp,
(struct xfs_dir2_sf_hdr *)ifp->if_u1.if_data,
ifp->if_bytes);
if (error)
return error;
}
if ((iip->ili_fields & dataflag[whichfork]) && if ((iip->ili_fields & dataflag[whichfork]) &&
(ifp->if_bytes > 0)) { (ifp->if_bytes > 0)) {
ASSERT(ifp->if_u1.if_data != NULL); ASSERT(ifp->if_u1.if_data != NULL);
@ -959,7 +951,6 @@ xfs_iflush_fork(
ASSERT(0); ASSERT(0);
break; break;
} }
return 0;
} }
/* /*

Просмотреть файл

@ -140,7 +140,7 @@ typedef struct xfs_ifork {
struct xfs_ifork *xfs_iext_state_to_fork(struct xfs_inode *ip, int state); struct xfs_ifork *xfs_iext_state_to_fork(struct xfs_inode *ip, int state);
int xfs_iformat_fork(struct xfs_inode *, struct xfs_dinode *); int xfs_iformat_fork(struct xfs_inode *, struct xfs_dinode *);
int xfs_iflush_fork(struct xfs_inode *, struct xfs_dinode *, void xfs_iflush_fork(struct xfs_inode *, struct xfs_dinode *,
struct xfs_inode_log_item *, int); struct xfs_inode_log_item *, int);
void xfs_idestroy_fork(struct xfs_inode *, int); void xfs_idestroy_fork(struct xfs_inode *, int);
void xfs_idata_realloc(struct xfs_inode *, int, int); void xfs_idata_realloc(struct xfs_inode *, int, int);

Просмотреть файл

@ -1311,8 +1311,16 @@ xfs_free_file_space(
/* /*
* Now that we've unmap all full blocks we'll have to zero out any * Now that we've unmap all full blocks we'll have to zero out any
* partial block at the beginning and/or end. xfs_zero_range is * partial block at the beginning and/or end. xfs_zero_range is
* smart enough to skip any holes, including those we just created. * smart enough to skip any holes, including those we just created,
* but we must take care not to zero beyond EOF and enlarge i_size.
*/ */
if (offset >= XFS_ISIZE(ip))
return 0;
if (offset + len > XFS_ISIZE(ip))
len = XFS_ISIZE(ip) - offset;
return xfs_zero_range(ip, offset, len, NULL); return xfs_zero_range(ip, offset, len, NULL);
} }

Просмотреть файл

@ -50,6 +50,7 @@
#include "xfs_log.h" #include "xfs_log.h"
#include "xfs_bmap_btree.h" #include "xfs_bmap_btree.h"
#include "xfs_reflink.h" #include "xfs_reflink.h"
#include "xfs_dir2_priv.h"
kmem_zone_t *xfs_inode_zone; kmem_zone_t *xfs_inode_zone;
@ -3475,7 +3476,6 @@ xfs_iflush_int(
struct xfs_inode_log_item *iip = ip->i_itemp; struct xfs_inode_log_item *iip = ip->i_itemp;
struct xfs_dinode *dip; struct xfs_dinode *dip;
struct xfs_mount *mp = ip->i_mount; struct xfs_mount *mp = ip->i_mount;
int error;
ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL|XFS_ILOCK_SHARED)); ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL|XFS_ILOCK_SHARED));
ASSERT(xfs_isiflocked(ip)); ASSERT(xfs_isiflocked(ip));
@ -3547,6 +3547,12 @@ xfs_iflush_int(
if (ip->i_d.di_version < 3) if (ip->i_d.di_version < 3)
ip->i_d.di_flushiter++; ip->i_d.di_flushiter++;
/* Check the inline directory data. */
if (S_ISDIR(VFS_I(ip)->i_mode) &&
ip->i_d.di_format == XFS_DINODE_FMT_LOCAL &&
xfs_dir2_sf_verify(ip))
goto corrupt_out;
/* /*
* Copy the dirty parts of the inode into the on-disk inode. We always * Copy the dirty parts of the inode into the on-disk inode. We always
* copy out the core of the inode, because if the inode is dirty at all * copy out the core of the inode, because if the inode is dirty at all
@ -3558,14 +3564,9 @@ xfs_iflush_int(
if (ip->i_d.di_flushiter == DI_MAX_FLUSH) if (ip->i_d.di_flushiter == DI_MAX_FLUSH)
ip->i_d.di_flushiter = 0; ip->i_d.di_flushiter = 0;
error = xfs_iflush_fork(ip, dip, iip, XFS_DATA_FORK); xfs_iflush_fork(ip, dip, iip, XFS_DATA_FORK);
if (error) if (XFS_IFORK_Q(ip))
return error; xfs_iflush_fork(ip, dip, iip, XFS_ATTR_FORK);
if (XFS_IFORK_Q(ip)) {
error = xfs_iflush_fork(ip, dip, iip, XFS_ATTR_FORK);
if (error)
return error;
}
xfs_inobp_check(mp, bp); xfs_inobp_check(mp, bp);
/* /*

Просмотреть файл

@ -583,7 +583,7 @@ xfs_inumbers(
return error; return error;
bcount = MIN(left, (int)(PAGE_SIZE / sizeof(*buffer))); bcount = MIN(left, (int)(PAGE_SIZE / sizeof(*buffer)));
buffer = kmem_alloc(bcount * sizeof(*buffer), KM_SLEEP); buffer = kmem_zalloc(bcount * sizeof(*buffer), KM_SLEEP);
do { do {
struct xfs_inobt_rec_incore r; struct xfs_inobt_rec_incore r;
int stat; int stat;