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 7 days ago
parent 1636a06bfa
commit 67b5fb7800

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

@ -344,15 +344,16 @@ impl NFSState {
let mut data = r.prog_data; let mut data = r.prog_data;
if let RpcRequestCreds::GssApi(ref creds) = r.creds { 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 { if creds.procedure == 0 && creds.service == 2 {
SCLogDebug!("GSS INTEGRITY: {:?}", creds); SCLogDebug!("GSS INTEGRITY: {:?}", creds);
match parse_rpc_gssapi_integrity(r.prog_data) { match parse_rpc_gssapi_integrity(r.prog_data) {
Ok((_rem, rec)) => { Ok((_rem, rec)) => {
SCLogDebug!("GSS INTEGRITY wrapper: {:?}", rec); SCLogDebug!("GSS INTEGRITY wrapper: {:?}", rec);
data = rec.data; data = rec.data;
// store proc and serv for the reply
xidmap.gssapi_proc = creds.procedure;
xidmap.gssapi_service = creds.service;
} }
Err(Err::Incomplete(_n)) => { Err(Err::Incomplete(_n)) => {
SCLogDebug!("NFSPROC4_COMPOUND/GSS INTEGRITY: 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 claimed envelope length fails the bounds check: definitive malformed
/// (the full record path rejects it the same way). /// (the full record path rejects it the same way).
EnvelopeMalformed, 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 /// `gss` is the request's RPCSEC_GSS credential combination (header on the
/// (header on the request side, xidmap on the response). Only the (0, 2) combination /// request side, xidmap on the response; None: non-GSS). Only (0, 2) is
/// the full record path unwraps is unwrapped here, so both always see the same compound. /// unwrapped, (0, 3) is opaque; another GSS combination is definitive
pub fn gss_compound_scan_data( /// malformed, never Incomplete
gss_proc: u32, gss_service: u32, prog_data: &[u8], pub fn gss_compound_scan_data(gss: Option<(u32, u32)>, prog_data: &[u8]) -> GssCompoundScan<'_> {
) -> GssCompoundScan<'_> { match gss {
if gss_proc == 0 && gss_service == 2 { None => GssCompoundScan::Compound(prog_data),
match parse_rpc_gssapi_integrity(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), Ok((_rem, ref rec)) => GssCompoundScan::Compound(rec.data),
Err(Err::Incomplete(_)) => GssCompoundScan::EnvelopeIncomplete, Err(Err::Incomplete(_)) => GssCompoundScan::EnvelopeIncomplete,
Err(_) => GssCompoundScan::EnvelopeMalformed, Err(_) => GssCompoundScan::EnvelopeMalformed,
} },
} else { // rpc_gss_svc_privacy: the payload is encrypted, in both directions
GssCompoundScan::Compound(prog_data) 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(&((compound.len()) as u32).to_be_bytes()); // len
envelope.extend_from_slice(&1u32.to_be_bytes()); // seqnum envelope.extend_from_slice(&1u32.to_be_bytes()); // seqnum
envelope.extend_from_slice(compound); // data 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 { match r {
GssCompoundScan::Compound(d) => assert_eq!(d, compound), GssCompoundScan::Compound(d) => assert_eq!(d, compound),
_ => panic!("unexpected {:?}", r), _ => 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] #[test]
fn test_gss_compound_scan_data_no_wrapper() { fn test_gss_compound_scan_data_no_wrapper() {
// non-GSS requests: prog_data is scanned as-is // non-GSS requests: prog_data is scanned as-is
let prog: &[u8] = &[0x00, 0x00, 0x00, 0x02]; let prog: &[u8] = &[0x00, 0x00, 0x00, 0x02];
assert_eq!( assert_eq!(
gss_compound_scan_data(0, 0, prog), gss_compound_scan_data(None, prog),
GssCompoundScan::Compound(prog) GssCompoundScan::Compound(prog)
); );
// other GSS combinations are not unwrapped (the full record path // a GSS combination the full record path does not unwrap: definitive
// does not unwrap them either) // malformed (the full path rejects it the same way), never Incomplete
assert_eq!( assert_eq!(
gss_compound_scan_data(1, 0, prog), gss_compound_scan_data(Some((1, 0)), prog),
GssCompoundScan::Compound(prog) GssCompoundScan::EnvelopeMalformed
); );
} }
@ -535,7 +562,7 @@ mod tests {
envelope.extend_from_slice(&100u32.to_be_bytes()); envelope.extend_from_slice(&100u32.to_be_bytes());
envelope.extend_from_slice(&1u32.to_be_bytes()); envelope.extend_from_slice(&1u32.to_be_bytes());
assert_eq!( assert_eq!(
gss_compound_scan_data(0, 2, &envelope[..]), gss_compound_scan_data(Some((0, 2)), &envelope[..]),
GssCompoundScan::EnvelopeIncomplete GssCompoundScan::EnvelopeIncomplete
); );
} }
@ -549,7 +576,7 @@ mod tests {
envelope.extend_from_slice(&1u32.to_be_bytes()); envelope.extend_from_slice(&1u32.to_be_bytes());
envelope.extend_from_slice(&[0u8; 16]); envelope.extend_from_slice(&[0u8; 16]);
assert_eq!( assert_eq!(
gss_compound_scan_data(0, 2, &envelope[..]), gss_compound_scan_data(Some((0, 2)), &envelope[..]),
GssCompoundScan::EnvelopeMalformed GssCompoundScan::EnvelopeMalformed
); );
} }

Loading…
Cancel
Save