Skip to content

Commit afbccd1

Browse files
[Packetbeat] rpc fragment bounds checking (#47803)
* nfs xdr sanitization * Add integration test This test caused a crash prior to this PR pcap captured when running the following: ```bash nc -l 12049 >/dev/null ``` and in a different shell ```python import socket, struct, time dest = ("127.0.0.1", 12049) frag_header = struct.pack("!I", 0x80000001) payload = b"\x00" with socket.create_connection(dest, timeout=5) as sock: sock.sendall(frag_header + payload) time.sleep(0.2) ``` * Add changelog fragment * Update changelog/fragments/1764181634-rpc_fragment_sanitization.yaml Co-authored-by: Mykola Kmet <[email protected]> * review suggestions - add return for consistency - add failure case unit tests * Appease the linter --------- Co-authored-by: Mykola Kmet <[email protected]>
1 parent fdd2b21 commit afbccd1

File tree

10 files changed

+560
-92
lines changed

10 files changed

+560
-92
lines changed
Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
# REQUIRED
2+
# Kind can be one of:
3+
# - breaking-change: a change to previously-documented behavior
4+
# - deprecation: functionality that is being removed in a later release
5+
# - bug-fix: fixes a problem in a previous version
6+
# - enhancement: extends functionality but does not break or fix existing behavior
7+
# - feature: new functionality
8+
# - known-issue: problems that we are aware of in a given version
9+
# - security: impacts on the security of a product or a user’s deployment.
10+
# - upgrade: important information for someone upgrading from a prior version
11+
# - other: does not fit into any of the other categories
12+
kind: bug-fix
13+
14+
# REQUIRED for all kinds
15+
# Change summary; a 80ish characters long description of the change.
16+
summary: rpc_fragment_sanitization
17+
18+
# REQUIRED for breaking-change, deprecation, known-issue
19+
# Long description; in case the summary is not enough to describe the change
20+
# this field accommodate a description without length limits.
21+
# description:
22+
23+
# REQUIRED for breaking-change, deprecation, known-issue
24+
# impact:
25+
26+
# REQUIRED for breaking-change, deprecation, known-issue
27+
# action:
28+
29+
# REQUIRED for all kinds
30+
# Affected component; usually one of "elastic-agent", "fleet-server", "filebeat", "metricbeat", "auditbeat", "all", etc.
31+
component: packetbeat
32+
33+
# AUTOMATED
34+
# OPTIONAL to manually add other PR URLs
35+
# PR URL: A link the PR that added the changeset.
36+
# If not present is automatically filled by the tooling finding the PR where this changelog fragment has been added.
37+
# NOTE: the tooling supports backports, so it's able to fill the original PR number instead of the backport PR number.
38+
# Please provide it if you are adding a fragment for a different PR.
39+
pr: https://github.com/elastic/beats/pull/47803
40+
41+
# AUTOMATED
42+
# OPTIONAL to manually add other issue URLs
43+
# Issue URL; optional; the GitHub issue related to this changeset (either closes or is part of).
44+
# If not present is automatically filled by the tooling with the issue linked to the PR number.
45+
# issue: https://github.com/owner/repo/1234

packetbeat/protos/nfs/malformed.go

Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
// Licensed to Elasticsearch B.V. under one or more contributor
2+
// license agreements. See the NOTICE file distributed with
3+
// this work for additional information regarding copyright
4+
// ownership. Elasticsearch B.V. licenses this file to you under
5+
// the Apache License, Version 2.0 (the "License"); you may
6+
// not use this file except in compliance with the License.
7+
// You may obtain a copy of the License at
8+
//
9+
// http://www.apache.org/licenses/LICENSE-2.0
10+
//
11+
// Unless required by applicable law or agreed to in writing,
12+
// software distributed under the License is distributed on an
13+
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
14+
// KIND, either express or implied. See the License for the
15+
// specific language governing permissions and limitations
16+
// under the License.
17+
18+
package nfs
19+
20+
import "github.com/elastic/elastic-agent-libs/logp"
21+
22+
func dropMalformed(context string, err error) bool {
23+
if err != nil {
24+
logp.Warn("nfs: dropping malformed %s: %v", context, err)
25+
return true
26+
}
27+
return false
28+
}

packetbeat/protos/nfs/nfs.go

Lines changed: 23 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -30,7 +30,7 @@ type nfs struct {
3030
event beat.Event
3131
}
3232

33-
func (nfs *nfs) getRequestInfo(xdr *xdr) mapstr.M {
33+
func (nfs *nfs) getRequestInfo(xdr *xdr) (mapstr.M, error) {
3434
nfsInfo := mapstr.M{
3535
"version": nfs.vers,
3636
}
@@ -43,21 +43,35 @@ func (nfs *nfs) getRequestInfo(xdr *xdr) mapstr.M {
4343
case 0:
4444
nfsInfo["opcode"] = "NULL"
4545
case 1:
46-
tag := xdr.getDynamicOpaque()
46+
tag, err := xdr.getDynamicOpaque()
47+
if err != nil {
48+
return nil, err
49+
}
4750
nfsInfo["tag"] = string(tag)
48-
nfsInfo["minor_version"] = xdr.getUInt()
49-
nfsInfo["opcode"] = nfs.findV4MainOpcode(xdr)
51+
minor, err := xdr.getUInt()
52+
if err != nil {
53+
return nil, err
54+
}
55+
nfsInfo["minor_version"] = minor
56+
opcode, err := nfs.findV4MainOpcode(xdr)
57+
if err != nil {
58+
return nil, err
59+
}
60+
nfsInfo["opcode"] = opcode
5061
}
5162
}
52-
return nfsInfo
63+
return nfsInfo, nil
5364
}
5465

55-
func (nfs *nfs) getNFSReplyStatus(xdr *xdr) string {
66+
func (nfs *nfs) getNFSReplyStatus(xdr *xdr) (string, error) {
5667
switch nfs.proc {
5768
case 0:
58-
return nfsStatus[0]
69+
return nfsStatus[0], nil
5970
default:
60-
stat := int(xdr.getUInt())
61-
return nfsStatus[stat]
71+
stat, err := xdr.getUInt()
72+
if err != nil {
73+
return "", err
74+
}
75+
return nfsStatus[int(stat)], nil
6276
}
6377
}

packetbeat/protos/nfs/nfs4.go

Lines changed: 54 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -163,46 +163,61 @@ var nfsOpnum4 = map[int]string{
163163
10044: "ILLEGAL",
164164
}
165165

166-
func (nfs *nfs) eatData(op int, xdr *xdr) {
166+
func (nfs *nfs) eatData(op int, xdr *xdr) error {
167167
switch op {
168168
case opGetattr:
169-
xdr.getUIntVector()
169+
_, err := xdr.getUIntVector()
170+
return err
170171
case opGetfh:
171-
// nothing to eat
172+
return nil
172173
case opLookup:
173-
xdr.getDynamicOpaque()
174+
_, err := xdr.getDynamicOpaque()
175+
return err
174176
case opLookupp:
175-
// nothing to eat
177+
return nil
176178
case opNverify:
177-
xdr.getUIntVector()
178-
xdr.getDynamicOpaque()
179+
if _, err := xdr.getUIntVector(); err != nil {
180+
return err
181+
}
182+
_, err := xdr.getDynamicOpaque()
183+
return err
179184
case opPutfh:
180-
xdr.getDynamicOpaque()
185+
_, err := xdr.getDynamicOpaque()
186+
return err
181187
case opPutpubfh:
182-
// nothing to eat
188+
return nil
183189
case opPutrootfh:
184-
// nothing to eat
190+
return nil
185191
case opReadlink:
186-
// nothing to eat
192+
return nil
187193
case opRenew:
188-
xdr.getUHyper()
194+
_, err := xdr.getUHyper()
195+
return err
189196
case opRestorefh:
190-
// nothing to eat
197+
return nil
191198
case opSavefh:
192-
// nothing to eat
199+
return nil
193200
case opSecinfo:
194-
xdr.getDynamicOpaque()
201+
_, err := xdr.getDynamicOpaque()
202+
return err
195203
case opVerify:
196-
xdr.getUIntVector()
197-
xdr.getDynamicOpaque()
204+
if _, err := xdr.getUIntVector(); err != nil {
205+
return err
206+
}
207+
_, err := xdr.getDynamicOpaque()
208+
return err
198209
case opSequence:
199-
xdr.getOpaque(16)
200-
xdr.getUInt()
201-
xdr.getUInt()
202-
xdr.getUInt()
203-
xdr.getUInt()
204-
210+
if _, err := xdr.getOpaque(16); err != nil {
211+
return err
212+
}
213+
for i := 0; i < 4; i++ {
214+
if _, err := xdr.getUInt(); err != nil {
215+
return err
216+
}
217+
}
218+
return nil
205219
}
220+
return nil
206221
}
207222

208223
// 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) {
219234
// PUTFH + GETATTR
220235
//
221236
// GETATTR is the main operation.
222-
func (nfs *nfs) findV4MainOpcode(xdr *xdr) string {
237+
func (nfs *nfs) findV4MainOpcode(xdr *xdr) (string, error) {
223238
// did we find a main operation opcode?
224239
found := false
225240

226241
// default op code
227242
currentOpname := "ILLEGAL"
228243

229-
opcount := int(xdr.getUInt())
244+
opcountVal, err := xdr.getUInt()
245+
if err != nil {
246+
return "", err
247+
}
248+
opcount := int(opcountVal)
230249
for i := 0; !found && i < opcount; i++ {
231-
op := int(xdr.getUInt())
250+
opVal, err := xdr.getUInt()
251+
if err != nil {
252+
return "", err
253+
}
254+
op := int(opVal)
232255
opname, ok := nfsOpnum4[op]
233256

234257
if !ok {
235-
return "ILLEGAL"
258+
return "ILLEGAL", nil
236259
}
237260
currentOpname = opname
238261

@@ -302,8 +325,10 @@ func (nfs *nfs) findV4MainOpcode(xdr *xdr) string {
302325

303326
found = true
304327
default:
305-
nfs.eatData(op, xdr)
328+
if err := nfs.eatData(op, xdr); err != nil {
329+
return "", err
330+
}
306331
}
307332
}
308-
return currentOpname
333+
return currentOpname, nil
309334
}

0 commit comments

Comments
 (0)