Hi Jerome, Thanks for the suggestion. Yes I think your fix is more robust. Do you want me to give another patch here?
Best, Murtaza On Thu, Jul 23, 2026 at 8:10 AM Jerome Forissier <[email protected]> wrote: > Hi Murtaza, > > On 22/07/2026 22:47, Murtaza Munaim wrote: > > nfs_read_reply() reads the NFS READ reply count into a signed int rlen > > via ntohl() and then bounds-checks it before passing it to store_block() > > as an unsigned length: > > > > if (((uchar *)&rpc_pkt.u.reply.data[0] - (uchar *)&rpc_pkt + rlen) > > len) > > return -9999; > > if (store_block(data_ptr, nfs_offset, rlen)) > > return -9999; > > > > The left-hand side of the check is a pointer difference (ptrdiff_t) plus > > the signed rlen, compared against the unsigned int len. A malicious NFS > > server can set the READ count field to a value with the top bit set, so > > that rlen becomes negative. > > > > On ILP32 targets the pointer difference is a 32-bit signed value and the > > comparison against unsigned len wraps the negative sum to a large > > unsigned quantity, so the check triggers and the reply is rejected. On > > LP64 targets the pointer difference is a 64-bit ptrdiff_t and len > > promotes to signed long; the sum stays negative, the check passes, and > > the negative rlen is then converted to a ~2-4 GB size_t inside > > store_block(), where memcpy() copies far past the packet buffer and the > > image load address. The commit that added this check only sanitised the > > length on ILP32. > > > > Reject negative rlen explicitly before the existing check so it is sound > > on all targets. > > > > Fixes: aa207cf3a6d6 ("CVE-2019-14194/CVE-2019-14198: nfs: fix unbounded > memcpy with a failed length check at nfs_read_reply") > > Signed-off-by: Murtaza Munaim <[email protected]> > > --- > > net/nfs-common.c | 11 +++++++++++ > > 1 file changed, 11 insertions(+) > > > > diff --git a/net/nfs-common.c b/net/nfs-common.c > > index 637fcfd9bb8..111b95459c6 100644 > > --- a/net/nfs-common.c > > +++ b/net/nfs-common.c > > @@ -744,6 +744,17 @@ static int nfs_read_reply(uchar *pkt, unsigned int > len) > > &rpc_pkt.u.reply.data[4 + nfsv3_data_offset]; > > } > > > > + /* > > + * rlen is read from the wire and used below as an unsigned length > > + * for store_block()/memcpy(). The length check that follows > compares > > + * a pointer difference (ptrdiff_t) plus rlen against len; on LP64 > a > > + * negative rlen keeps the left-hand side negative, so the check > > + * passes and rlen wraps to a huge size_t in store_block(). Reject > > + * negative values explicitly so the check is sound on all targets. > > + */ > > + if (rlen < 0) > > + return -9999; > > + > > if (((uchar *)&rpc_pkt.u.reply.data[0] - (uchar *)&rpc_pkt + rlen) > > len) > > return -9999; > > Is this correct? Shouldn't data_ptr be used instead? How about: > > diff --git a/net/nfs-common.c b/net/nfs-common.c > index 72d8fd823e33..34b276517987 100644 > --- a/net/nfs-common.c > +++ b/net/nfs-common.c > @@ -694,8 +694,9 @@ static int nfs_readlink_reply(uchar *pkt, unsigned int > len) > static int nfs_read_reply(uchar *pkt, unsigned int len) > { > struct rpc_t rpc_pkt; > - int rlen; > + unsigned int rlen; > uchar *data_ptr; > + size_t data_offset; > > memcpy(&rpc_pkt.u.data[0], pkt, sizeof(rpc_pkt.u.reply)); > > @@ -738,7 +739,8 @@ static int nfs_read_reply(uchar *pkt, unsigned int len) > &rpc_pkt.u.reply.data[4 + nfsv3_data_offset]; > } > > - if (((uchar *)&rpc_pkt.u.reply.data[0] - (uchar *)&rpc_pkt + rlen) > > len) > + data_offset = data_ptr - (uchar *)&rpc_pkt; > + if (data_offset > len || rlen > len - data_offset) > return -9999; > > if (store_block(data_ptr, nfs_offset, rlen)) > > > Thanks, > -- > Jerome > >
