Changes since last time:
- Validate inline directory data to prevent buffer overruns due to corrupt metadata. -----BEGIN PGP SIGNATURE----- Version: GnuPG v1 iQIcBAABCgAGBQJYyt2WAAoJEPh/dxk0SrTrEfQP+weMA4/j0HwV7zA6FK1BhWWU IwDcJI14UGWQt59RAAhGj8lFnsXogslSecGtkxgb9DJFA1rK05CMyE58uAahEvE5 BJhTQz27Cq3vEjmSNLbogpZwr/Z7a3DI+1OA2o0GXrPzzc4trFghhIifssUwYUWm 698gjaqQ2yY+FxjHKrX0oggkLM+XdDbbEmiWHvr4B4dOZG088IN9DZsccgax5BEy 1Zt02Jxg1v9R+72SytipcNmN8gvO9Pht+gojRgIwg7yU0bfg8gNHwoVdzo/QUeXp EppNhkv4c0UnFYttSDTqB9e10d/oyfph2JcBHCnFLyA1xFL01cnGwrEhTEVYQYpq PwUX01Wu16BTEgFDcbiSLr5Lm5U0EWn+Bugm2/1f+8clJNBpzNfoy2UlIXAoDSFF nSBNczmBXajfhkfbTvMm1QGhN2jLkSLzO1rSj80iaBjEjXRpD42PE43/F7sJZyN3 +G9ghd3QmVw74yp629i3nx3C/wVe8qu0gbgYwKf9KRJgNd42zILrKD3y7o/QWvZN PTrdsJegD2CvzS1JKZJeLsMygwHNTmvA3mI5l9f+JBuOeHmniNfhpNH3uPKv1r0r i8Urir2dnWOIvIPCSuV3CA5PQM5AaaLloBVCbt7h1vEARzaFSxhjxKi4PyzmhooX 8K9E1+PJVVRV0IxRgsAS =+p2p -----END PGP SIGNATURE----- Merge tag 'xfs-4.11-fixes-2' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux Pull xfs fix from Darrick Wong: "Here's a single fix for -rc3 to improve input validation on inline directory data to prevent buffer overruns due to corrupt metadata" * tag 'xfs-4.11-fixes-2' of git://git.kernel.org/pub/scm/fs/xfs/xfs-linux: xfs: verify inline directory data forks
This commit is contained in:
commit
d11507e197
@ -125,6 +125,8 @@ 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_removename(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,
|
||||
int size);
|
||||
|
||||
/* xfs_dir2_readdir.c */
|
||||
extern int xfs_readdir(struct xfs_inode *dp, struct dir_context *ctx,
|
||||
|
@ -629,6 +629,93 @@ xfs_dir2_sf_check(
|
||||
}
|
||||
#endif /* DEBUG */
|
||||
|
||||
/* Verify the consistency of an inline directory. */
|
||||
int
|
||||
xfs_dir2_sf_verify(
|
||||
struct xfs_mount *mp,
|
||||
struct xfs_dir2_sf_hdr *sfp,
|
||||
int size)
|
||||
{
|
||||
struct xfs_dir2_sf_entry *sfep;
|
||||
struct xfs_dir2_sf_entry *next_sfep;
|
||||
char *endp;
|
||||
const struct xfs_dir_ops *dops;
|
||||
xfs_ino_t ino;
|
||||
int i;
|
||||
int i8count;
|
||||
int offset;
|
||||
__uint8_t filetype;
|
||||
|
||||
dops = xfs_dir_get_ops(mp, NULL);
|
||||
|
||||
/*
|
||||
* Give up if the directory is way too short.
|
||||
*/
|
||||
XFS_WANT_CORRUPTED_RETURN(mp, size >
|
||||
offsetof(struct xfs_dir2_sf_hdr, parent));
|
||||
XFS_WANT_CORRUPTED_RETURN(mp, size >=
|
||||
xfs_dir2_sf_hdr_size(sfp->i8count));
|
||||
|
||||
endp = (char *)sfp + size;
|
||||
|
||||
/* Check .. entry */
|
||||
ino = dops->sf_get_parent_ino(sfp);
|
||||
i8count = ino > XFS_DIR2_MAX_SHORT_INUM;
|
||||
XFS_WANT_CORRUPTED_RETURN(mp, !xfs_dir_ino_validate(mp, ino));
|
||||
offset = dops->data_first_offset;
|
||||
|
||||
/* Check all reported entries */
|
||||
sfep = xfs_dir2_sf_firstentry(sfp);
|
||||
for (i = 0; i < sfp->count; i++) {
|
||||
/*
|
||||
* struct xfs_dir2_sf_entry has a variable length.
|
||||
* Check the fixed-offset parts of the structure are
|
||||
* within the data buffer.
|
||||
*/
|
||||
XFS_WANT_CORRUPTED_RETURN(mp,
|
||||
((char *)sfep + sizeof(*sfep)) < endp);
|
||||
|
||||
/* Don't allow names with known bad length. */
|
||||
XFS_WANT_CORRUPTED_RETURN(mp, sfep->namelen > 0);
|
||||
XFS_WANT_CORRUPTED_RETURN(mp, sfep->namelen < MAXNAMELEN);
|
||||
|
||||
/*
|
||||
* Check that the variable-length part of the structure is
|
||||
* within the data buffer. The next entry starts after the
|
||||
* name component, so nextentry is an acceptable test.
|
||||
*/
|
||||
next_sfep = dops->sf_nextentry(sfp, sfep);
|
||||
XFS_WANT_CORRUPTED_RETURN(mp, endp >= (char *)next_sfep);
|
||||
|
||||
/* Check that the offsets always increase. */
|
||||
XFS_WANT_CORRUPTED_RETURN(mp,
|
||||
xfs_dir2_sf_get_offset(sfep) >= offset);
|
||||
|
||||
/* Check the inode number. */
|
||||
ino = dops->sf_get_ino(sfp, sfep);
|
||||
i8count += ino > XFS_DIR2_MAX_SHORT_INUM;
|
||||
XFS_WANT_CORRUPTED_RETURN(mp, !xfs_dir_ino_validate(mp, ino));
|
||||
|
||||
/* Check the file type. */
|
||||
filetype = dops->sf_get_ftype(sfep);
|
||||
XFS_WANT_CORRUPTED_RETURN(mp, filetype < XFS_DIR3_FT_MAX);
|
||||
|
||||
offset = xfs_dir2_sf_get_offset(sfep) +
|
||||
dops->data_entsize(sfep->namelen);
|
||||
|
||||
sfep = next_sfep;
|
||||
}
|
||||
XFS_WANT_CORRUPTED_RETURN(mp, i8count == sfp->i8count);
|
||||
XFS_WANT_CORRUPTED_RETURN(mp, (void *)sfep == (void *)endp);
|
||||
|
||||
/* Make sure this whole thing ought to be in local format. */
|
||||
XFS_WANT_CORRUPTED_RETURN(mp, offset +
|
||||
(sfp->count + 2) * (uint)sizeof(xfs_dir2_leaf_entry_t) +
|
||||
(uint)sizeof(xfs_dir2_block_tail_t) <= mp->m_dir_geo->blksize);
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
/*
|
||||
* Create a new (shortform) directory.
|
||||
*/
|
||||
|
@ -33,6 +33,8 @@
|
||||
#include "xfs_trace.h"
|
||||
#include "xfs_attr_sf.h"
|
||||
#include "xfs_da_format.h"
|
||||
#include "xfs_da_btree.h"
|
||||
#include "xfs_dir2_priv.h"
|
||||
|
||||
kmem_zone_t *xfs_ifork_zone;
|
||||
|
||||
@ -320,6 +322,7 @@ xfs_iformat_local(
|
||||
int whichfork,
|
||||
int size)
|
||||
{
|
||||
int error;
|
||||
|
||||
/*
|
||||
* If the size is unreasonable, then something
|
||||
@ -336,6 +339,14 @@ xfs_iformat_local(
|
||||
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);
|
||||
return 0;
|
||||
}
|
||||
@ -856,7 +867,7 @@ xfs_iextents_copy(
|
||||
* In these cases, the format always takes precedence, because the
|
||||
* format indicates the current state of the fork.
|
||||
*/
|
||||
void
|
||||
int
|
||||
xfs_iflush_fork(
|
||||
xfs_inode_t *ip,
|
||||
xfs_dinode_t *dip,
|
||||
@ -866,6 +877,7 @@ xfs_iflush_fork(
|
||||
char *cp;
|
||||
xfs_ifork_t *ifp;
|
||||
xfs_mount_t *mp;
|
||||
int error;
|
||||
static const short brootflag[2] =
|
||||
{ XFS_ILOG_DBROOT, XFS_ILOG_ABROOT };
|
||||
static const short dataflag[2] =
|
||||
@ -874,7 +886,7 @@ xfs_iflush_fork(
|
||||
{ XFS_ILOG_DEXT, XFS_ILOG_AEXT };
|
||||
|
||||
if (!iip)
|
||||
return;
|
||||
return 0;
|
||||
ifp = XFS_IFORK_PTR(ip, whichfork);
|
||||
/*
|
||||
* This can happen if we gave up in iformat in an error path,
|
||||
@ -882,12 +894,19 @@ xfs_iflush_fork(
|
||||
*/
|
||||
if (!ifp) {
|
||||
ASSERT(whichfork == XFS_ATTR_FORK);
|
||||
return;
|
||||
return 0;
|
||||
}
|
||||
cp = XFS_DFORK_PTR(dip, whichfork);
|
||||
mp = ip->i_mount;
|
||||
switch (XFS_IFORK_FORMAT(ip, whichfork)) {
|
||||
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]) &&
|
||||
(ifp->if_bytes > 0)) {
|
||||
ASSERT(ifp->if_u1.if_data != NULL);
|
||||
@ -940,6 +959,7 @@ xfs_iflush_fork(
|
||||
ASSERT(0);
|
||||
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);
|
||||
|
||||
int xfs_iformat_fork(struct xfs_inode *, struct xfs_dinode *);
|
||||
void xfs_iflush_fork(struct xfs_inode *, struct xfs_dinode *,
|
||||
int xfs_iflush_fork(struct xfs_inode *, struct xfs_dinode *,
|
||||
struct xfs_inode_log_item *, int);
|
||||
void xfs_idestroy_fork(struct xfs_inode *, int);
|
||||
void xfs_idata_realloc(struct xfs_inode *, int, int);
|
||||
|
@ -71,22 +71,11 @@ xfs_dir2_sf_getdents(
|
||||
struct xfs_da_geometry *geo = args->geo;
|
||||
|
||||
ASSERT(dp->i_df.if_flags & XFS_IFINLINE);
|
||||
/*
|
||||
* Give up if the directory is way too short.
|
||||
*/
|
||||
if (dp->i_d.di_size < offsetof(xfs_dir2_sf_hdr_t, parent)) {
|
||||
ASSERT(XFS_FORCED_SHUTDOWN(dp->i_mount));
|
||||
return -EIO;
|
||||
}
|
||||
|
||||
ASSERT(dp->i_df.if_bytes == dp->i_d.di_size);
|
||||
ASSERT(dp->i_df.if_u1.if_data != NULL);
|
||||
|
||||
sfp = (xfs_dir2_sf_hdr_t *)dp->i_df.if_u1.if_data;
|
||||
|
||||
if (dp->i_d.di_size < xfs_dir2_sf_hdr_size(sfp->i8count))
|
||||
return -EFSCORRUPTED;
|
||||
|
||||
/*
|
||||
* If the block number in the offset is out of range, we're done.
|
||||
*/
|
||||
|
@ -3475,6 +3475,7 @@ xfs_iflush_int(
|
||||
struct xfs_inode_log_item *iip = ip->i_itemp;
|
||||
struct xfs_dinode *dip;
|
||||
struct xfs_mount *mp = ip->i_mount;
|
||||
int error;
|
||||
|
||||
ASSERT(xfs_isilocked(ip, XFS_ILOCK_EXCL|XFS_ILOCK_SHARED));
|
||||
ASSERT(xfs_isiflocked(ip));
|
||||
@ -3557,9 +3558,14 @@ xfs_iflush_int(
|
||||
if (ip->i_d.di_flushiter == DI_MAX_FLUSH)
|
||||
ip->i_d.di_flushiter = 0;
|
||||
|
||||
xfs_iflush_fork(ip, dip, iip, XFS_DATA_FORK);
|
||||
if (XFS_IFORK_Q(ip))
|
||||
xfs_iflush_fork(ip, dip, iip, XFS_ATTR_FORK);
|
||||
error = xfs_iflush_fork(ip, dip, iip, XFS_DATA_FORK);
|
||||
if (error)
|
||||
return error;
|
||||
if (XFS_IFORK_Q(ip)) {
|
||||
error = xfs_iflush_fork(ip, dip, iip, XFS_ATTR_FORK);
|
||||
if (error)
|
||||
return error;
|
||||
}
|
||||
xfs_inobp_check(mp, bp);
|
||||
|
||||
/*
|
||||
|
Loading…
Reference in New Issue
Block a user