nfs: mirror the OPEN claim types in the v4 request scanner

The claim the scanner skips as a word plus a name was NULL(0) and UNIQUE,
a value no version defines under that name. It is CLAIM_DELEGATE_PREV(3)
whose claim is a component4 name, the same one the parser reads after the
claim word, while 2 is CLAIM_DELEGATE_CUR: its stateid is what the name
length is then read from. So the scanner walked off the end of a compound
that parses fine, and rejected a claim whose record it could have
followed to the op behind it.

Accept 0 and 3, where the claim word is followed by the name the parser
consumes, and reject the others as a malformed compound within the
record's bounds, never as incomplete and never buffering toward the
claim. The test that pins the rejected set loses 3 and gains 2, and a
DELEGATE_PREV claim ahead of another op is now reached.

Ticket: #9263.
pull/16514/head
Victor Julien 6 days ago
parent a7a6e24cc5
commit 5df82c2a6b

@ -733,10 +733,7 @@ fn skip_request_lead_op(i: &[u8], cmd: u32) -> IResult<&[u8], ()> {
NFSPROC4_ACCESS => skip_fixed(i, 4),
// open4args as nfs4_req_open parses it: seq_id (4) + share_access +
// share_deny + client_id (8) + owner (len + bytes, no XDR pad) +
// open_type (+ open_data for type 1) + claim_type + name. The parser
// reads no claim payload: payload-bearing claims desync it (their
// stateid/fh is read as the name length), as do the other
// open_types: not skippable.
// open_type (+ open_data for type 1) + claim_type + name.
NFSPROC4_OPEN => {
let (i, ()) = skip_fixed(i, 20)?;
let (i, owner_len) = be_u32(i)?;
@ -766,15 +763,16 @@ fn skip_request_lead_op(i: &[u8], cmd: u32) -> IResult<&[u8], ()> {
} else {
return Err(Err::Error(make_error(i, ErrorKind::Switch)));
};
// claim4: the parser reads no claim payload (void claims
// only): payload-bearing claims (FH, DELEGATE, FH_ONLY and the
// v4.2 claims) desync it (the payload is read as the name
// length). Only the claims void in every version (NULL=0,
// UNIQUE=2) are skippable; the rest are a bounded rejection,
// never Incomplete (buffering toward the record claim).
// open_claim4 (RFC 7530 16.16.2): the parser reads no claim
// payload; only NULL(0) and DELEGATE_PREV(3) carry a
// component4 name, byte-identical to the name read the parser
// does after the claim word. PREVIOUS(1)/DELEGATE_CUR(2) carry
// delegate_type/stateid payloads and unknown values desync the
// parser: bounded rejection, never Incomplete (buffering toward
// the record claim).
let (i, claim_type) = be_u32(i)?;
match claim_type {
0 | 2 => {}
0 | 3 => {}
_ => return Err(Err::Error(make_error(i, ErrorKind::Switch))),
}
skip_len_bytes(i)
@ -2980,14 +2978,14 @@ mod tests {
#[test]
fn test_scan_request_compound_open_payload_claim_rejected() {
// a payload-bearing claim (FH=1, DELEGATE=3, FH_ONLY=4, the v4.2
// claims 5+) desyncs the parser (the stateid/fh is read as the name
// length): the scanner must reject the compound boundedly
// a non-name claim (PREVIOUS=1, DELEGATE_CUR=2) or an unknown value
// (4+) desyncs the parser (the delegate_type/stateid payload is read
// as the name length): the scanner must reject the compound boundedly
// (Malformed), not buffer (Incomplete) and not report the oversized
// WRITE behind it.
let mut stateid = be32(1).to_vec();
stateid.extend(std::iter::repeat_n(0u8, 12));
for claim_type in [1u32, 3, 4, 8, 9] {
stateid.extend(std::iter::repeat(0u8).take(12));
for claim_type in [1u32, 2, 4, 8, 9] {
let mut buf = compound_head(2);
buf.extend_from_slice(&open_req_op(0, 0, claim_type, &stateid));
buf.extend_from_slice(&write_op(10 * 1024 * 1024));
@ -3000,6 +2998,20 @@ mod tests {
}
}
#[test]
fn test_scan_request_compound_open_delegate_prev_claim_skipped() {
// CLAIM_DELEGATE_PREV (3) carries a component4 name, the same layout
// the parser reads after the claim word (the op's trailing name):
// the scanner skips it and reaches the trailing op (no rejection).
let mut buf = compound_head(2);
buf.extend_from_slice(&open_req_op(0, 0, 3, &[]));
buf.extend_from_slice(&getfh_req_op());
assert_eq!(
scan_nfs4_request_compound_write_len(&buf, 1024),
Nfs4CompoundScan::Clean
);
}
#[test]
fn test_scan_request_compound_open_hides_oversized_write() {
// the OPEN is skipped so a following oversized WRITE is reached (not

Loading…
Cancel
Save