FFS extattr: fix handling of the tail

There are three issues with change that stopped truncating ea area before
write, and resulted in possible zero tail in the ea area:
- Truncate to zero checked i_ea_len after the reference was dropped,
  making the last drop effectively truncate to zero length always.
- Loop to fill uio for zeroing specified too large length, that triggered
  assert in normal situation.
- Integrity check could trip over the tail, instead we must allow
  partial header or header with zero length, and clamp ea image in
  memory at it.

Reported by:	arichardson
Tested by:	arichardson, pho
Sponsored by:	The FreeBSD Foundation
MFC after:	3 days
Fixup:	5e198e7646
Differential Revision:	https://reviews.freebsd.org/D28999
This commit is contained in:
Konstantin Belousov 2021-03-01 17:24:11 +02:00
parent a59e2982fe
commit 8742817ba6

View File

@ -1346,13 +1346,20 @@ ffs_rdextattr(u_char **p, struct vnode *vp, struct thread *td)
/* Validate disk xattrfile contents. */
for (eap = (void *)eae, eaend = (void *)(eae + easize); eap < eaend;
eap = eapnext) {
/* Detect zeroed out tail */
if (eap->ea_length < sizeof(*eap) || eap->ea_length == 0) {
easize = (const u_char *)eap - eae;
break;
}
eapnext = EXTATTR_NEXT(eap);
/* Bogusly short entry or bogusly long entry. */
if (eap->ea_length < sizeof(*eap) || eapnext > eaend) {
/* Bogusly long entry. */
if (eapnext > eaend) {
free(eae, M_TEMP);
return (EINTEGRITY);
}
}
ip->i_ea_len = easize;
*p = eae;
return (0);
}
@ -1407,7 +1414,6 @@ ffs_open_ea(struct vnode *vp, struct ucred *cred, struct thread *td)
ffs_unlock_ea(vp);
return (error);
}
ip->i_ea_len = dp->di_extsize;
ip->i_ea_error = 0;
ip->i_ea_refs++;
ffs_unlock_ea(vp);
@ -1426,6 +1432,7 @@ ffs_close_ea(struct vnode *vp, int commit, struct ucred *cred, struct thread *td
struct ufs2_dinode *dp;
size_t ea_len, tlen;
int error, i, lcnt;
bool truncate;
ip = VTOI(vp);
@ -1436,6 +1443,7 @@ ffs_close_ea(struct vnode *vp, int commit, struct ucred *cred, struct thread *td
}
dp = ip->i_din2;
error = ip->i_ea_error;
truncate = false;
if (commit && error == 0) {
ASSERT_VOP_ELOCKED(vp, "ffs_close_ea commit");
if (cred == NOCRED)
@ -1452,12 +1460,12 @@ ffs_close_ea(struct vnode *vp, int commit, struct ucred *cred, struct thread *td
liovec[0].iov_base = ip->i_ea_area;
liovec[0].iov_len = ip->i_ea_len;
for (i = 1, tlen = ea_len; i < lcnt; i++) {
for (i = 1, tlen = ea_len - ip->i_ea_len; i < lcnt; i++) {
liovec[i].iov_base = __DECONST(void *, zero_region);
liovec[i].iov_len = MIN(ZERO_REGION_SIZE, tlen);
tlen -= liovec[i].iov_len;
}
MPASS(tlen == ip->i_ea_len);
MPASS(tlen == 0);
luio.uio_iov = liovec;
luio.uio_offset = 0;
@ -1466,6 +1474,8 @@ ffs_close_ea(struct vnode *vp, int commit, struct ucred *cred, struct thread *td
luio.uio_rw = UIO_WRITE;
luio.uio_td = td;
error = ffs_extwrite(vp, &luio, IO_EXT | IO_SYNC, cred);
if (error == 0 && ip->i_ea_len == 0)
truncate = true;
}
if (--ip->i_ea_refs == 0) {
free(ip->i_ea_area, M_TEMP);
@ -1475,7 +1485,7 @@ ffs_close_ea(struct vnode *vp, int commit, struct ucred *cred, struct thread *td
}
ffs_unlock_ea(vp);
if (commit && error == 0 && ip->i_ea_len == 0)
if (truncate)
ffs_truncate(vp, 0, IO_EXT, cred);
return (error);
}