diff --git a/changelog/fragments/1764181634-rpc_fragment_sanitization.yaml b/changelog/fragments/1764181634-rpc_fragment_sanitization.yaml new file mode 100644 index 000000000000..a7658e7ae39b --- /dev/null +++ b/changelog/fragments/1764181634-rpc_fragment_sanitization.yaml @@ -0,0 +1,45 @@ +# REQUIRED +# Kind can be one of: +# - breaking-change: a change to previously-documented behavior +# - deprecation: functionality that is being removed in a later release +# - bug-fix: fixes a problem in a previous version +# - enhancement: extends functionality but does not break or fix existing behavior +# - feature: new functionality +# - known-issue: problems that we are aware of in a given version +# - security: impacts on the security of a product or a user’s deployment. +# - upgrade: important information for someone upgrading from a prior version +# - other: does not fit into any of the other categories +kind: bug-fix + +# REQUIRED for all kinds +# Change summary; a 80ish characters long description of the change. +summary: rpc_fragment_sanitization + +# REQUIRED for breaking-change, deprecation, known-issue +# Long description; in case the summary is not enough to describe the change +# this field accommodate a description without length limits. +# description: + +# REQUIRED for breaking-change, deprecation, known-issue +# impact: + +# REQUIRED for breaking-change, deprecation, known-issue +# action: + +# REQUIRED for all kinds +# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc. +component: packetbeat + +# AUTOMATED +# OPTIONAL to manually add other PR URLs +# PR URL: A link the PR that added the changeset. +# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added. +# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number. +# Please provide it if you are adding a fragment for a different PR. +pr: https://github.com/elastic/beats/pull/47803 + +# AUTOMATED +# OPTIONAL to manually add other issue URLs +# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of). +# If not present is automatically filled by the tooling with the issue linked to the PR number. +# issue: https://github.com/owner/repo/1234 diff --git a/packetbeat/protos/nfs/malformed.go b/packetbeat/protos/nfs/malformed.go new file mode 100644 index 000000000000..939977ffa800 --- /dev/null +++ b/packetbeat/protos/nfs/malformed.go @@ -0,0 +1,28 @@ +// Licensed to Elasticsearch B.V. under one or more contributor +// license agreements. See the NOTICE file distributed with +// this work for additional information regarding copyright +// ownership. Elasticsearch B.V. licenses this file to you under +// the Apache License, Version 2.0 (the "License"); you may +// not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, +// software distributed under the License is distributed on an +// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY +// KIND, either express or implied. See the License for the +// specific language governing permissions and limitations +// under the License. + +package nfs + +import "github.com/elastic/elastic-agent-libs/logp" + +func dropMalformed(context string, err error) bool { + if err != nil { + logp.Warn("nfs: dropping malformed %s: %v", context, err) + return true + } + return false +} diff --git a/packetbeat/protos/nfs/nfs.go b/packetbeat/protos/nfs/nfs.go index 983996a4d789..f082d3be85bb 100644 --- a/packetbeat/protos/nfs/nfs.go +++ b/packetbeat/protos/nfs/nfs.go @@ -30,7 +30,7 @@ type nfs struct { event beat.Event } -func (nfs *nfs) getRequestInfo(xdr *xdr) mapstr.M { +func (nfs *nfs) getRequestInfo(xdr *xdr) (mapstr.M, error) { nfsInfo := mapstr.M{ "version": nfs.vers, } @@ -43,21 +43,35 @@ func (nfs *nfs) getRequestInfo(xdr *xdr) mapstr.M { case 0: nfsInfo["opcode"] = "NULL" case 1: - tag := xdr.getDynamicOpaque() + tag, err := xdr.getDynamicOpaque() + if err != nil { + return nil, err + } nfsInfo["tag"] = string(tag) - nfsInfo["minor_version"] = xdr.getUInt() - nfsInfo["opcode"] = nfs.findV4MainOpcode(xdr) + minor, err := xdr.getUInt() + if err != nil { + return nil, err + } + nfsInfo["minor_version"] = minor + opcode, err := nfs.findV4MainOpcode(xdr) + if err != nil { + return nil, err + } + nfsInfo["opcode"] = opcode } } - return nfsInfo + return nfsInfo, nil } -func (nfs *nfs) getNFSReplyStatus(xdr *xdr) string { +func (nfs *nfs) getNFSReplyStatus(xdr *xdr) (string, error) { switch nfs.proc { case 0: - return nfsStatus[0] + return nfsStatus[0], nil default: - stat := int(xdr.getUInt()) - return nfsStatus[stat] + stat, err := xdr.getUInt() + if err != nil { + return "", err + } + return nfsStatus[int(stat)], nil } } diff --git a/packetbeat/protos/nfs/nfs4.go b/packetbeat/protos/nfs/nfs4.go index 596b73843eed..cdfd641c4ed2 100644 --- a/packetbeat/protos/nfs/nfs4.go +++ b/packetbeat/protos/nfs/nfs4.go @@ -163,46 +163,61 @@ var nfsOpnum4 = map[int]string{ 10044: "ILLEGAL", } -func (nfs *nfs) eatData(op int, xdr *xdr) { +func (nfs *nfs) eatData(op int, xdr *xdr) error { switch op { case opGetattr: - xdr.getUIntVector() + _, err := xdr.getUIntVector() + return err case opGetfh: - // nothing to eat + return nil case opLookup: - xdr.getDynamicOpaque() + _, err := xdr.getDynamicOpaque() + return err case opLookupp: - // nothing to eat + return nil case opNverify: - xdr.getUIntVector() - xdr.getDynamicOpaque() + if _, err := xdr.getUIntVector(); err != nil { + return err + } + _, err := xdr.getDynamicOpaque() + return err case opPutfh: - xdr.getDynamicOpaque() + _, err := xdr.getDynamicOpaque() + return err case opPutpubfh: - // nothing to eat + return nil case opPutrootfh: - // nothing to eat + return nil case opReadlink: - // nothing to eat + return nil case opRenew: - xdr.getUHyper() + _, err := xdr.getUHyper() + return err case opRestorefh: - // nothing to eat + return nil case opSavefh: - // nothing to eat + return nil case opSecinfo: - xdr.getDynamicOpaque() + _, err := xdr.getDynamicOpaque() + return err case opVerify: - xdr.getUIntVector() - xdr.getDynamicOpaque() + if _, err := xdr.getUIntVector(); err != nil { + return err + } + _, err := xdr.getDynamicOpaque() + return err case opSequence: - xdr.getOpaque(16) - xdr.getUInt() - xdr.getUInt() - xdr.getUInt() - xdr.getUInt() - + if _, err := xdr.getOpaque(16); err != nil { + return err + } + for i := 0; i < 4; i++ { + if _, err := xdr.getUInt(); err != nil { + return err + } + } + return nil } + return nil } // findV4MainOpcode finds the main operation in a compound call. If no main operation can be found, the last operation @@ -219,20 +234,28 @@ func (nfs *nfs) eatData(op int, xdr *xdr) { // PUTFH + GETATTR // // GETATTR is the main operation. -func (nfs *nfs) findV4MainOpcode(xdr *xdr) string { +func (nfs *nfs) findV4MainOpcode(xdr *xdr) (string, error) { // did we find a main operation opcode? found := false // default op code currentOpname := "ILLEGAL" - opcount := int(xdr.getUInt()) + opcountVal, err := xdr.getUInt() + if err != nil { + return "", err + } + opcount := int(opcountVal) for i := 0; !found && i < opcount; i++ { - op := int(xdr.getUInt()) + opVal, err := xdr.getUInt() + if err != nil { + return "", err + } + op := int(opVal) opname, ok := nfsOpnum4[op] if !ok { - return "ILLEGAL" + return "ILLEGAL", nil } currentOpname = opname @@ -302,8 +325,10 @@ func (nfs *nfs) findV4MainOpcode(xdr *xdr) string { found = true default: - nfs.eatData(op, xdr) + if err := nfs.eatData(op, xdr); err != nil { + return "", err + } } } - return currentOpname + return currentOpname, nil } diff --git a/packetbeat/protos/nfs/request_handler.go b/packetbeat/protos/nfs/request_handler.go index dd448c472d38..9f67597c17c3 100644 --- a/packetbeat/protos/nfs/request_handler.go +++ b/packetbeat/protos/nfs/request_handler.go @@ -24,6 +24,7 @@ import ( "time" "github.com/elastic/beats/v7/libbeat/common" + "github.com/elastic/elastic-agent-libs/logp" "github.com/elastic/elastic-agent-libs/mapstr" "github.com/elastic/elastic-agent-libs/monitoring" @@ -53,9 +54,13 @@ func (r *rpc) handleExpiredPacket(nfs *nfs) { // called when we process a RPC call func (r *rpc) handleCall(xid string, xdr *xdr, ts time.Time, tcptuple *common.TCPTuple, dir uint8) { - // eat rpc version number - xdr.getUInt() - rpcProg := xdr.getUInt() + if _, err := xdr.getUInt(); dropMalformed("rpc call version", err) { + return + } + rpcProg, err := xdr.getUInt() + if dropMalformed("rpc call program", err) { + return + } if rpcProg != nfsProgramNumber { // not a NFS request return @@ -86,8 +91,14 @@ func (r *rpc) handleCall(xid string, xdr *xdr, ts time.Time, tcptuple *common.TC pbf.Event.Start = ts pbf.Network.Transport = "tcp" - nfsVers := xdr.getUInt() - nfsProc := xdr.getUInt() + nfsVers, err := xdr.getUInt() + if dropMalformed("rpc call nfs version", err) { + return + } + nfsProc, err := xdr.getUInt() + if dropMalformed("rpc call nfs procedure", err) { + return + } switch nfsVers { case 3: @@ -105,8 +116,14 @@ func (r *rpc) handleCall(xid string, xdr *xdr, ts time.Time, tcptuple *common.TC fields := evt.Fields - authFlavor := xdr.getUInt() - authOpaque := xdr.getDynamicOpaque() + authFlavor, err := xdr.getUInt() + if dropMalformed("rpc auth flavor", err) { + return + } + authOpaque, err := xdr.getDynamicOpaque() + if dropMalformed("rpc auth opaque", err) { + return + } switch authFlavor { case 0: rpcInfo["auth_flavor"] = "none" @@ -114,8 +131,15 @@ func (r *rpc) handleCall(xid string, xdr *xdr, ts time.Time, tcptuple *common.TC rpcInfo["auth_flavor"] = "unix" cred := mapstr.M{} credXdr := makeXDR(authOpaque) - cred["stamp"] = credXdr.getUInt() - machine := credXdr.getString() + stamp, err := credXdr.getUInt() + if dropMalformed("rpc unix cred stamp", err) { + return + } + cred["stamp"] = stamp + machine, err := credXdr.getString() + if dropMalformed("rpc unix cred machinename", err) { + return + } if machine == "" { machine = src.IP } else { @@ -124,13 +148,25 @@ func (r *rpc) handleCall(xid string, xdr *xdr, ts time.Time, tcptuple *common.TC cred["machinename"] = machine fields["host.hostname"] = machine - cred["uid"] = credXdr.getUInt() - fields["user.id"] = cred["uid"] + uid, err := credXdr.getUInt() + if dropMalformed("rpc unix cred uid", err) { + return + } + cred["uid"] = uid + fields["user.id"] = uid - cred["gid"] = credXdr.getUInt() - fields["group.id"] = cred["gid"] + gid, err := credXdr.getUInt() + if dropMalformed("rpc unix cred gid", err) { + return + } + cred["gid"] = gid + fields["group.id"] = gid - cred["gids"] = credXdr.getUIntVector() + gids, err := credXdr.getUIntVector() + if dropMalformed("rpc unix cred gids", err) { + return + } + cred["gids"] = gids rpcInfo["cred"] = cred case 6: rpcInfo["auth_flavor"] = "rpcsec_gss" @@ -139,8 +175,12 @@ func (r *rpc) handleCall(xid string, xdr *xdr, ts time.Time, tcptuple *common.TC } // eat auth verifier - xdr.getUInt() - xdr.getDynamicOpaque() + if _, err := xdr.getUInt(); dropMalformed("rpc verifier flavor", err) { + return + } + if _, err := xdr.getDynamicOpaque(); dropMalformed("rpc verifier body", err) { + return + } fields["status"] = common.OK_STATUS // all packages are OK for now fields["type"] = pbf.Event.Dataset @@ -151,7 +191,10 @@ func (r *rpc) handleCall(xid string, xdr *xdr, ts time.Time, tcptuple *common.TC pbf: pbf, event: evt, } - info := nfs.getRequestInfo(xdr) + info, err := nfs.getRequestInfo(xdr) + if dropMalformed("nfs request info", err) { + return + } fields["nfs"] = info if opcode, ok := info["opcode"].(string); ok && opcode != "" { @@ -167,15 +210,22 @@ func (r *rpc) handleCall(xid string, xdr *xdr, ts time.Time, tcptuple *common.TC // called when we process a RPC reply func (r *rpc) handleReply(xid string, xdr *xdr, ts time.Time, tcptuple *common.TCPTuple, dir uint8) { - replyStatus := xdr.getUInt() + replyStatus, err := xdr.getUInt() + if dropMalformed("rpc reply status", err) { + return + } // we are interested only in accepted rpc reply if replyStatus != 0 { return } // eat auth verifier - xdr.getUInt() - xdr.getDynamicOpaque() + if _, err := xdr.getUInt(); dropMalformed("rpc reply verifier flavor", err) { + return + } + if _, err := xdr.getDynamicOpaque(); dropMalformed("rpc reply verifier body", err) { + return + } // xid+src ip is used to uniquely identify request. var reqID string @@ -190,19 +240,46 @@ func (r *rpc) handleReply(xid string, xdr *xdr, ts time.Time, tcptuple *common.T // get cached request v := r.callsSeen.Delete(reqID) if v != nil { - nfs := v.(*nfs) + nfs, ok := v.(*nfs) + if !ok { + logp.Warn("nfs: failed to assert nfs interface") + return + + } nfs.pbf.Event.End = ts nfs.pbf.Destination.Bytes = int64(xdr.size()) fields := nfs.event.Fields - rpcInfo := fields["rpc"].(mapstr.M) - status := int(xdr.getUInt()) + rpcInfo, ok := fields["rpc"].(mapstr.M) + if !ok { + logp.Warn("nfs: failed to assert map[string]") + return + + } + statusVal, err := xdr.getUInt() + if dropMalformed("rpc accept status", err) { + return + } + status := int(statusVal) + if status < 0 || status >= len(acceptStatus) { + logp.Warn("nfs: rpc accept status %d out of range", status) + return + } rpcInfo["status"] = acceptStatus[status] // populate nfs info for successfully executed requests if status == 0 { - nfsInfo := fields["nfs"].(mapstr.M) - nfsInfo["status"] = nfs.getNFSReplyStatus(xdr) + nfsInfo, ok := fields["nfs"].(mapstr.M) + if !ok { + logp.Warn("nfs: failed to assert map[string]") + return + + } + nfsStatus, err := nfs.getNFSReplyStatus(xdr) + if dropMalformed("nfs reply status", err) { + return + } + nfsInfo["status"] = nfsStatus } else { nfs.pbf.Event.Outcome = "failure" } diff --git a/packetbeat/protos/nfs/rpc.go b/packetbeat/protos/nfs/rpc.go index 90c158014309..4933c7232278 100644 --- a/packetbeat/protos/nfs/rpc.go +++ b/packetbeat/protos/nfs/rpc.go @@ -92,7 +92,9 @@ func New( } func (r *rpc) init(results protos.Reporter, config *rpcConfig) error { - r.setFromConfig(config) + if err := r.setFromConfig(config); err != nil { + return err + } r.results = results r.callsSeen = common.NewCacheWithRemovalListener( r.transactionTimeout, @@ -214,7 +216,7 @@ func (r *rpc) handleRPCFragment( break } - marker := uint32(binary.BigEndian.Uint32(st.rawData[0:4])) + marker := binary.BigEndian.Uint32(st.rawData[0:4]) size := int(marker & rpcSizeMask) islast := (marker & rpcLastFrag) != 0 @@ -239,9 +241,22 @@ func (r *rpc) handleRPCFragment( } func (r *rpc) handleRPCPacket(xdr *xdr, ts time.Time, tcptuple *common.TCPTuple, dir uint8) { - xid := fmt.Sprintf("%.8x", xdr.getUInt()) + defer func() { + if rec := recover(); rec != nil { + logp.Warn("nfs: recovered from panic while parsing RPC/NFS: %v", rec) + } + }() - msgType := xdr.getUInt() + xidVal, err := xdr.getUInt() + if dropMalformed("rpc packet xid", err) { + return + } + xid := fmt.Sprintf("%.8x", xidVal) + + msgType, err := xdr.getUInt() + if dropMalformed("rpc packet msgType", err) { + return + } switch msgType { case rpcCall: diff --git a/packetbeat/protos/nfs/xdr.go b/packetbeat/protos/nfs/xdr.go index 168fb274cfda..e17a5fa5fe72 100644 --- a/packetbeat/protos/nfs/xdr.go +++ b/packetbeat/protos/nfs/xdr.go @@ -19,12 +19,18 @@ package nfs import ( "encoding/binary" + "fmt" +) + +const ( + maxOpaque int = 1 << 20 + maxVector int = 1 << 15 ) // XDR maps the External Data Representation type xdr struct { data []byte - offset uint32 + offset int } func newXDR(data []byte) *xdr { @@ -40,39 +46,76 @@ func (r *xdr) size() int { return len(r.data) } -func (r *xdr) getUInt() uint32 { - i := uint32(binary.BigEndian.Uint32(r.data[r.offset : r.offset+4])) +func (r *xdr) getUInt() (uint32, error) { + if r.offset+4 > len(r.data) { + return 0, fmt.Errorf("xdr: truncated uint32") + } + i := binary.BigEndian.Uint32(r.data[r.offset : r.offset+4]) r.offset += 4 - return i + return i, nil } -func (r *xdr) getUHyper() uint64 { - i := uint64(binary.BigEndian.Uint64(r.data[r.offset : r.offset+8])) +func (r *xdr) getUHyper() (uint64, error) { + if r.offset+8 > len(r.data) { + return 0, fmt.Errorf("xdr: truncated uint64") + } + i := binary.BigEndian.Uint64(r.data[r.offset : r.offset+8]) r.offset += 8 - return i + return i, nil } -func (r *xdr) getString() string { - return string(r.getDynamicOpaque()) +func (r *xdr) getString() (string, error) { + b, err := r.getDynamicOpaque() + if err != nil { + return "", err + } + return string(b), nil } -func (r *xdr) getOpaque(length uint32) []byte { +func (r *xdr) getOpaque(length int) ([]byte, error) { + if length < 0 || length > maxOpaque { + return nil, fmt.Errorf("xdr: opaque length %d exceeds limit", length) + } + + start := r.offset + end := start + length + if end > len(r.data) { + return nil, fmt.Errorf("xdr: opaque length %d exceeds buffer", length) + } + padding := (4 - (length & 3)) & 3 - b := r.data[r.offset : r.offset+length] - r.offset += length + padding - return b + if end+padding > len(r.data) { + return nil, fmt.Errorf("xdr: opaque padding exceeds buffer") + } + + b := r.data[start:end] + r.offset = end + padding + return b, nil } -func (r *xdr) getDynamicOpaque() []byte { - l := r.getUInt() - return r.getOpaque(l) +func (r *xdr) getDynamicOpaque() ([]byte, error) { + l, err := r.getUInt() + if err != nil { + return nil, err + } + return r.getOpaque(int(l)) } -func (r *xdr) getUIntVector() []uint32 { - l := r.getUInt() +func (r *xdr) getUIntVector() ([]uint32, error) { + l, err := r.getUInt() + if err != nil { + return nil, err + } + if int(l) > maxVector { + return nil, fmt.Errorf("xdr: vector length %d exceeds limit", l) + } v := make([]uint32, int(l)) for i := 0; i < len(v); i++ { - v[i] = r.getUInt() + vi, err := r.getUInt() + if err != nil { + return nil, err + } + v[i] = vi } - return v + return v, nil } diff --git a/packetbeat/protos/nfs/xdr_test.go b/packetbeat/protos/nfs/xdr_test.go index e349df3e00d9..936b9a80cc1a 100644 --- a/packetbeat/protos/nfs/xdr_test.go +++ b/packetbeat/protos/nfs/xdr_test.go @@ -18,9 +18,11 @@ package nfs import ( + "encoding/binary" "testing" "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" ) var testMsg = []byte{ @@ -33,13 +35,223 @@ var testMsg = []byte{ 0x74, 0x65, 0x73, 0x74, 0x20, 0x73, 0x74, 0x72, 0x69, 0x6e, 0x67, 0x00, } +func BytesToUint32(b []byte) []uint32 { + n := len(b) / 4 + out := make([]uint32, n) + for i := 0; i < n; i++ { + out[i] = binary.BigEndian.Uint32(b[i*4:]) + } + return out +} + func TestXdrDecoding(t *testing.T) { xdr := makeXDR(testMsg) - assert.Equal(t, uint32(0x800000e0), uint32(xdr.getUInt())) - assert.Equal(t, uint32(0xb54921ab), uint32(xdr.getUInt())) - assert.Equal(t, uint64(2), uint64(xdr.getUHyper())) - assert.Equal(t, uint32(4), uint32(xdr.getUInt())) - assert.Equal(t, "test string", xdr.getString()) + v, err := xdr.getUInt() + require.NoError(t, err) + assert.Equal(t, uint32(0x800000e0), v) + + v, err = xdr.getUInt() + require.NoError(t, err) + assert.Equal(t, uint32(0xb54921ab), v) + + hv, err := xdr.getUHyper() + require.NoError(t, err) + assert.Equal(t, uint64(2), hv) + + v, err = xdr.getUInt() + require.NoError(t, err) + assert.Equal(t, uint32(4), v) + + str, err := xdr.getString() + require.NoError(t, err) + assert.Equal(t, "test string", str) assert.Equal(t, len(testMsg), xdr.size()) } + +func TestXdrDecodingFailures(t *testing.T) { + + // test getUInt + xdr := makeXDR([]byte{0x80}) + _, err := xdr.getUInt() + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00}) + _, err = xdr.getUInt() + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00, 0x00}) + _, err = xdr.getUInt() + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00, 0x00, 0x01}) + v, err := xdr.getUInt() + require.NoError(t, err) + assert.Equal(t, uint32(0x80000001), v) + + // test getUHyper + xdr = makeXDR([]byte{0x80}) + _, err = xdr.getUHyper() + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00}) + _, err = xdr.getUHyper() + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00, 0x00}) + _, err = xdr.getUHyper() + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00, 0x00, 0x01}) + _, err = xdr.getUHyper() + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00, 0x00, 0x01, 0x80}) + _, err = xdr.getUHyper() + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00, 0x00, 0x01, 0x80, 0x00}) + _, err = xdr.getUHyper() + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00}) + _, err = xdr.getUHyper() + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00, 0x00, 0x01, 0x80, 0x00, 0x00, 0x01}) + v64, err := xdr.getUHyper() + require.NoError(t, err) + assert.Equal(t, uint64(0x8000000180000001), v64) + + // test getString + xdr = makeXDR([]byte{0x00}) + _, err = xdr.getString() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00}) + _, err = xdr.getString() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00, 0x00}) + _, err = xdr.getString() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00, 0x00, 0x03}) + _, err = xdr.getString() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00, 0x00, 0x03, 0x68}) + _, err = xdr.getString() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00, 0x00, 0x03, 0x68, 0x69}) + _, err = xdr.getString() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00, 0x00, 0x03, 0x68, 0x69, 0x21}) + _, err = xdr.getString() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00, 0x00, 0x03, 0x68, 0x69, 0x21, 0x00}) + msg, err := xdr.getString() + require.NoError(t, err) + assert.Equal(t, "hi!", msg) + + // test getOpaque + b := make([]byte, maxOpaque+1) + xdr = makeXDR(b) + _, err = xdr.getOpaque(maxOpaque + 1) + require.Error(t, err) + + b = make([]byte, maxOpaque+1) + xdr = makeXDR(b) + _, err = xdr.getOpaque(-1) + require.Error(t, err) + + b = make([]byte, maxOpaque-1) + xdr = makeXDR(b) + _, err = xdr.getOpaque(maxOpaque) + require.Error(t, err) + + b = make([]byte, maxOpaque) + xdr = makeXDR(b) + ba, err := xdr.getOpaque(maxOpaque) + require.NoError(t, err) + assert.Equal(t, maxOpaque, len(ba)) + + xdr = makeXDR([]byte{0x80}) + _, err = xdr.getOpaque(1) + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00}) + _, err = xdr.getOpaque(1) + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00, 0x00}) + _, err = xdr.getOpaque(1) + require.Error(t, err) + + xdr = makeXDR([]byte{0x80, 0x00, 0x00, 0x00}) + ba, err = xdr.getOpaque(1) + require.NoError(t, err) + assert.Equal(t, []byte{0x80}, ba) + + // test getDynamicOpaque + xdr = makeXDR([]byte{0x00}) + _, err = xdr.getDynamicOpaque() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00}) + _, err = xdr.getDynamicOpaque() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00, 0x00}) + _, err = xdr.getDynamicOpaque() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00, 0x00, 0x01}) + _, err = xdr.getDynamicOpaque() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00, 0x00, 0x01, 0x08}) + _, err = xdr.getDynamicOpaque() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00, 0x00, 0x01, 0x08, 0x00}) + _, err = xdr.getDynamicOpaque() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00}) + _, err = xdr.getDynamicOpaque() + require.Error(t, err) + + xdr = makeXDR([]byte{0x00, 0x00, 0x00, 0x01, 0x08, 0x00, 0x00, 0x00}) + ba, err = xdr.getDynamicOpaque() + require.NoError(t, err) + assert.Equal(t, []byte{0x08}, ba) + + // test getUintVector + length := []byte{0x00, 0x00, 0x80, 0x00} // maxVector + uv := make([]byte, maxVector*4) + data := append(length, uv...) + xdr = makeXDR(data) + uva, err := xdr.getUIntVector() + require.NoError(t, err) + assert.Equal(t, BytesToUint32(uv), uva) + + // test length longer than buffer + length = []byte{0x00, 0x00, 0x80, 0x00} // maxVector + uv = make([]byte, (maxVector-1)*4) + data = append(length, uv...) + xdr = makeXDR(data) + _, err = xdr.getUIntVector() + require.Error(t, err) + + // test matching length and buffer exceed size limits + length = []byte{0x00, 0x00, 0x80, 0x01} // maxVector + 1 + uv = make([]byte, (maxVector+1)*4) + data = append(length, uv...) + xdr = makeXDR(data) + _, err = xdr.getUIntVector() + require.Error(t, err) +} diff --git a/packetbeat/tests/system/pcaps/rpc_fragment.pcap b/packetbeat/tests/system/pcaps/rpc_fragment.pcap new file mode 100644 index 000000000000..d98f97046691 Binary files /dev/null and b/packetbeat/tests/system/pcaps/rpc_fragment.pcap differ diff --git a/packetbeat/tests/system/test_0061_nfs.py b/packetbeat/tests/system/test_0061_nfs.py index 5ce47469a597..73660ea02a2f 100644 --- a/packetbeat/tests/system/test_0061_nfs.py +++ b/packetbeat/tests/system/test_0061_nfs.py @@ -110,3 +110,12 @@ def test_clone_notsupp_v42(self): assert o["nfs.opcode"] == "CLONE" assert o["nfs.status"] == "NFSERR_NOTSUPP" + + def test_rpc_fragment(self): + """ + Should not crash + """ + self.render_config_template( + nfs_ports=[12049], + ) + self.run_packetbeat(pcap="rpc_fragment.pcap")