nfs: scan a GSS record by the combination of its call

The record scanners unwrapped the (0, 2) combination and took every other
one as a bare compound. A control procedure or a privacy compound was
then scanned as if it were a COMPOUND: the envelope length was read as
the first tag, so the scan either stayed incomplete and buffered toward
the claim, or rejected a record that has nothing to reject.

Scan by the combination the call recorded: hand the credential pair to
gss_compound_scan_data, which leaves (0, 1) raw because rpc_gss_svc_none
wraps nothing, unwraps (0, 2), and reports a privacy (0, 3) compound as
opaque. There is nothing to scan in an encrypted compound, so the record
is skipped to its boundary and neither parsed nor scored: the request
path keeps the call in the request map for its reply, the reply path
drops it. Any other combination is one the full record path rejects as
well, so it is malformed, boundedly and without buffering. The v4
request path records the combination whatever it is, not only when it
unwraps.

Ticket: #9263.
pull/16514/head
Victor Julien 6 days ago
parent 1636a06bfa
commit 67b5fb7800

@ -2433,22 +2433,34 @@ impl NFSState {
match parse_rpc(cur_i, false) {
Ok((_rem, ref hdr)) => {
// RPCSEC_GSS integrity compounds arrive wrapped (length, seqnum, data):
// unwrap like the full record path, or the envelope length is read as the
// compound tag and the scan stays incomplete (buffering toward the claim).
let (gss_proc, gss_service) = match &hdr.creds {
RpcRequestCreds::GssApi(ref g) => (g.procedure, g.service),
_ => (0, 0),
// unwrap like the full record path before scanning - the raw
// envelope's length would be read as the compound tag, buffering toward the claim
let gss = match &hdr.creds {
RpcRequestCreds::GssApi(ref g) => Some((g.procedure, g.service)),
_ => None,
};
let scanres = match gss_compound_scan_data(
gss_proc,
gss_service,
hdr.prog_data,
) {
let scanres = match gss_compound_scan_data(gss, hdr.prog_data) {
GssCompoundScan::Compound(data) => {
scan_nfs4_request_compound_write_len(data, max_write)
}
GssCompoundScan::EnvelopeIncomplete => Nfs4CompoundScan::Incomplete,
GssCompoundScan::EnvelopeMalformed => Nfs4CompoundScan::Malformed,
// privacy: encrypted; keep the call for its
// reply and skip the record, no tx
GssCompoundScan::Opaque => {
let mut xidmap =
NFSRequestXidMap::new(hdr.progver, hdr.procedure, 0);
if let Some((procedure, service)) = gss {
xidmap.gssapi_proc = procedure;
xidmap.gssapi_service = service;
}
self.requestmap.put(hdr.hdr.xid, xidmap);
return self.skip_partial_record(
Direction::ToServer,
cur_i,
rec_size,
);
}
};
match scanres {
Nfs4CompoundScan::Oversized(_write_len) => {
@ -2737,29 +2749,33 @@ impl NFSState {
let max_read = unsafe { NFS_CFG_MAX_READ_SIZE };
// a zero limit disables the check
if max_read != 0 {
// the request recorded its RPCSEC_GSS credential combination in the
// xidmap; integrity-wrapped compounds (proc 0, service 2) are unwrapped first.
let gss_integrity = self
.requestmap
.get(&phdr.xid)
.map(|xm| xm.gssapi_proc == 0 && xm.gssapi_service == 2)
.unwrap_or(false);
// (0, 0) is the non-GSS default; GSS requests record
// their combination (only (0, 2) is unwrapped)
let gss = self.requestmap.get(&phdr.xid).and_then(|xm| {
if xm.gssapi_proc == 0 && xm.gssapi_service == 0 {
None
} else {
Some((xm.gssapi_proc, xm.gssapi_service))
}
});
match parse_rpc_reply(cur_i, false) {
Ok((_rem, ref hdr)) => {
let scanres = if gss_integrity {
match gss_compound_scan_data(0, 2, hdr.prog_data) {
GssCompoundScan::Compound(data) => {
scan_nfs4_response_compound_read_len(data, max_read)
}
GssCompoundScan::EnvelopeIncomplete => {
Nfs4CompoundScan::Incomplete
}
GssCompoundScan::EnvelopeMalformed => {
Nfs4CompoundScan::Malformed
}
let scanres = match gss_compound_scan_data(gss, hdr.prog_data) {
GssCompoundScan::Compound(data) => {
scan_nfs4_response_compound_read_len(data, max_read)
}
GssCompoundScan::EnvelopeIncomplete => Nfs4CompoundScan::Incomplete,
GssCompoundScan::EnvelopeMalformed => Nfs4CompoundScan::Malformed,
// privacy: encrypted reply; the call is done,
// pop its xid and skip the record
GssCompoundScan::Opaque => {
self.requestmap.pop(&hdr.hdr.xid);
return self.skip_partial_record(
Direction::ToClient,
cur_i,
rec_size,
);
}
} else {
scan_nfs4_response_compound_read_len(hdr.prog_data, max_read)
};
match scanres {
Nfs4CompoundScan::Oversized(read_len) => {

@ -344,15 +344,16 @@ impl NFSState {
let mut data = r.prog_data;
if let RpcRequestCreds::GssApi(ref creds) = r.creds {
// store proc and serv for the reply; only (0, 2) is
// unwrapped below
xidmap.gssapi_proc = creds.procedure;
xidmap.gssapi_service = creds.service;
if creds.procedure == 0 && creds.service == 2 {
SCLogDebug!("GSS INTEGRITY: {:?}", creds);
match parse_rpc_gssapi_integrity(r.prog_data) {
Ok((_rem, rec)) => {
SCLogDebug!("GSS INTEGRITY wrapper: {:?}", rec);
data = rec.data;
// store proc and serv for the reply
xidmap.gssapi_proc = creds.procedure;
xidmap.gssapi_service = creds.service;
}
Err(Err::Incomplete(_n)) => {
SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: INCOMPLETE {:?}", _n);

@ -129,22 +129,28 @@ pub enum GssCompoundScan<'a> {
/// The claimed envelope length fails the bounds check: definitive malformed
/// (the full record path rejects it the same way).
EnvelopeMalformed,
/// RPCSEC_GSS privacy (0, 3): the compound is encrypted, so there is
/// nothing to scan either way; it is opaque, not malformed.
Opaque,
}
/// `gss_proc`/`gss_service` carry the request's RPCSEC_GSS credential combination
/// (header on the request side, xidmap on the response). Only the (0, 2) combination
/// the full record path unwraps is unwrapped here, so both always see the same compound.
pub fn gss_compound_scan_data(
gss_proc: u32, gss_service: u32, prog_data: &[u8],
) -> GssCompoundScan<'_> {
if gss_proc == 0 && gss_service == 2 {
match parse_rpc_gssapi_integrity(prog_data) {
/// `gss` is the request's RPCSEC_GSS credential combination (header on the
/// request side, xidmap on the response; None: non-GSS). Only (0, 2) is
/// unwrapped, (0, 3) is opaque; another GSS combination is definitive
/// malformed, never Incomplete
pub fn gss_compound_scan_data(gss: Option<(u32, u32)>, prog_data: &[u8]) -> GssCompoundScan<'_> {
match gss {
None => GssCompoundScan::Compound(prog_data),
// rpc_gss_svc_none (RFC 2203): no integrity/privacy wrap
Some((0, 1)) => GssCompoundScan::Compound(prog_data),
Some((0, 2)) => match parse_rpc_gssapi_integrity(prog_data) {
Ok((_rem, ref rec)) => GssCompoundScan::Compound(rec.data),
Err(Err::Incomplete(_)) => GssCompoundScan::EnvelopeIncomplete,
Err(_) => GssCompoundScan::EnvelopeMalformed,
}
} else {
GssCompoundScan::Compound(prog_data)
},
// rpc_gss_svc_privacy: the payload is encrypted, in both directions
Some((0, 3)) => GssCompoundScan::Opaque,
Some(_) => GssCompoundScan::EnvelopeMalformed,
}
}
@ -504,26 +510,47 @@ mod tests {
envelope.extend_from_slice(&((compound.len()) as u32).to_be_bytes()); // len
envelope.extend_from_slice(&1u32.to_be_bytes()); // seqnum
envelope.extend_from_slice(compound); // data
let r = gss_compound_scan_data(0, 2, &envelope[..]);
let r = gss_compound_scan_data(Some((0, 2)), &envelope[..]);
match r {
GssCompoundScan::Compound(d) => assert_eq!(d, compound),
_ => panic!("unexpected {:?}", r),
}
}
#[test]
fn test_gss_compound_scan_data_service_none() {
// (0, 1): rpc_gss_svc_none (RFC 2203) -- the data is not
// wrapped, the raw compound is scanned as-is
let prog: &[u8] = &[0x00, 0x00, 0x00, 0x01, 0x00, 0x00, 0x00, 0x02];
assert_eq!(
gss_compound_scan_data(Some((0, 1)), prog),
GssCompoundScan::Compound(prog)
);
}
#[test]
fn test_gss_compound_scan_data_privacy() {
// rpc_gss_svc_privacy: encrypted, opaque to the scan
let prog: &[u8] = &[0x00, 0x00, 0x00, 0x08, 0xde, 0xad, 0xbe, 0xef];
assert_eq!(
gss_compound_scan_data(Some((0, 3)), prog),
GssCompoundScan::Opaque
);
}
#[test]
fn test_gss_compound_scan_data_no_wrapper() {
// non-GSS requests: prog_data is scanned as-is
let prog: &[u8] = &[0x00, 0x00, 0x00, 0x02];
assert_eq!(
gss_compound_scan_data(0, 0, prog),
gss_compound_scan_data(None, prog),
GssCompoundScan::Compound(prog)
);
// other GSS combinations are not unwrapped (the full record path
// does not unwrap them either)
// a GSS combination the full record path does not unwrap: definitive
// malformed (the full path rejects it the same way), never Incomplete
assert_eq!(
gss_compound_scan_data(1, 0, prog),
GssCompoundScan::Compound(prog)
gss_compound_scan_data(Some((1, 0)), prog),
GssCompoundScan::EnvelopeMalformed
);
}
@ -535,7 +562,7 @@ mod tests {
envelope.extend_from_slice(&100u32.to_be_bytes());
envelope.extend_from_slice(&1u32.to_be_bytes());
assert_eq!(
gss_compound_scan_data(0, 2, &envelope[..]),
gss_compound_scan_data(Some((0, 2)), &envelope[..]),
GssCompoundScan::EnvelopeIncomplete
);
}
@ -549,7 +576,7 @@ mod tests {
envelope.extend_from_slice(&1u32.to_be_bytes());
envelope.extend_from_slice(&[0u8; 16]);
assert_eq!(
gss_compound_scan_data(0, 2, &envelope[..]),
gss_compound_scan_data(Some((0, 2)), &envelope[..]),
GssCompoundScan::EnvelopeMalformed
);
}

Loading…
Cancel
Save