From c67887f9abbfb726d20d1fa67dca0041a97398bc Mon Sep 17 00:00:00 2001 From: Paul Buetow Date: Sun, 8 Mar 2026 08:47:21 +0200 Subject: task(ior): harden malformed raw-event decoding (task ed7a7a3f) --- internal/eventloop.go | 88 ++++++++++++++++++++++++++----- internal/eventloop_error_handling_test.go | 31 +++++++++++ internal/types/fastdecode.go | 27 ++++++++++ internal/types/fastdecode_test.go | 25 +++++++++ 4 files changed, 159 insertions(+), 12 deletions(-) diff --git a/internal/eventloop.go b/internal/eventloop.go index 26eaafc..2def4c0 100644 --- a/internal/eventloop.go +++ b/internal/eventloop.go @@ -408,46 +408,106 @@ func (e *eventLoop) initRawHandlers() { } e.rawHandlers[types.ENTER_OPEN_EVENT] = func(raw []byte, _ chan<- *event.Pair) { - if ev, ok := e.filter.openEvent(types.NewOpenEventFast(raw)); ok { + openEv := types.NewOpenEventFast(raw) + if openEv == nil { + e.dropMalformedRawEvent(types.ENTER_OPEN_EVENT, raw) + return + } + if ev, ok := e.filter.openEvent(openEv); ok { e.tracepointEntered(ev) } } e.rawHandlers[types.EXIT_OPEN_EVENT] = func(raw []byte, ch chan<- *event.Pair) { - e.tracepointExited(types.NewRetEventFast(raw), ch) + retEv := types.NewRetEventFast(raw) + if retEv == nil { + e.dropMalformedRawEvent(types.EXIT_OPEN_EVENT, raw) + return + } + e.tracepointExited(retEv, ch) } e.rawHandlers[types.ENTER_FD_EVENT] = func(raw []byte, _ chan<- *event.Pair) { - e.tracepointEntered(types.NewFdEventFast(raw)) + fdEv := types.NewFdEventFast(raw) + if fdEv == nil { + e.dropMalformedRawEvent(types.ENTER_FD_EVENT, raw) + return + } + e.tracepointEntered(fdEv) } e.rawHandlers[types.EXIT_FD_EVENT] = func(raw []byte, ch chan<- *event.Pair) { - e.tracepointExited(types.NewFdEventFast(raw), ch) + fdEv := types.NewFdEventFast(raw) + if fdEv == nil { + e.dropMalformedRawEvent(types.EXIT_FD_EVENT, raw) + return + } + e.tracepointExited(fdEv, ch) } e.rawHandlers[types.ENTER_NULL_EVENT] = func(raw []byte, _ chan<- *event.Pair) { - e.tracepointEntered(types.NewNullEventFast(raw)) + nullEv := types.NewNullEventFast(raw) + if nullEv == nil { + e.dropMalformedRawEvent(types.ENTER_NULL_EVENT, raw) + return + } + e.tracepointEntered(nullEv) } e.rawHandlers[types.EXIT_NULL_EVENT] = func(raw []byte, ch chan<- *event.Pair) { - e.tracepointExited(types.NewNullEventFast(raw), ch) + nullEv := types.NewNullEventFast(raw) + if nullEv == nil { + e.dropMalformedRawEvent(types.EXIT_NULL_EVENT, raw) + return + } + e.tracepointExited(nullEv, ch) } e.rawHandlers[types.EXIT_RET_EVENT] = func(raw []byte, ch chan<- *event.Pair) { - e.tracepointExited(types.NewRetEventFast(raw), ch) + retEv := types.NewRetEventFast(raw) + if retEv == nil { + e.dropMalformedRawEvent(types.EXIT_RET_EVENT, raw) + return + } + e.tracepointExited(retEv, ch) } e.rawHandlers[types.ENTER_NAME_EVENT] = func(raw []byte, _ chan<- *event.Pair) { - if ev, ok := e.filter.nameEvent(types.NewNameEventFast(raw)); ok { + nameEv := types.NewNameEventFast(raw) + if nameEv == nil { + e.dropMalformedRawEvent(types.ENTER_NAME_EVENT, raw) + return + } + if ev, ok := e.filter.nameEvent(nameEv); ok { e.tracepointEntered(ev) } } e.rawHandlers[types.ENTER_PATH_EVENT] = func(raw []byte, _ chan<- *event.Pair) { - if ev, ok := e.filter.pathEvent(types.NewPathEventFast(raw)); ok { + pathEv := types.NewPathEventFast(raw) + if pathEv == nil { + e.dropMalformedRawEvent(types.ENTER_PATH_EVENT, raw) + return + } + if ev, ok := e.filter.pathEvent(pathEv); ok { e.tracepointEntered(ev) } } e.rawHandlers[types.ENTER_FCNTL_EVENT] = func(raw []byte, _ chan<- *event.Pair) { - e.tracepointEntered(types.NewFcntlEventFast(raw)) + fcntlEv := types.NewFcntlEventFast(raw) + if fcntlEv == nil { + e.dropMalformedRawEvent(types.ENTER_FCNTL_EVENT, raw) + return + } + e.tracepointEntered(fcntlEv) } e.rawHandlers[types.ENTER_OPEN_BY_HANDLE_AT_EVENT] = func(raw []byte, _ chan<- *event.Pair) { - e.tracepointEntered(types.NewOpenByHandleAtEventFast(raw)) + openByHandleEv := types.NewOpenByHandleAtEventFast(raw) + if openByHandleEv == nil { + e.dropMalformedRawEvent(types.ENTER_OPEN_BY_HANDLE_AT_EVENT, raw) + return + } + e.tracepointEntered(openByHandleEv) } e.rawHandlers[types.ENTER_DUP3_EVENT] = func(raw []byte, _ chan<- *event.Pair) { - e.tracepointEntered(types.NewDup3EventFast(raw)) + dup3Ev := types.NewDup3EventFast(raw) + if dup3Ev == nil { + e.dropMalformedRawEvent(types.ENTER_DUP3_EVENT, raw) + return + } + e.tracepointEntered(dup3Ev) } } @@ -849,6 +909,10 @@ func (e *eventLoop) notifyWarning(message string) { e.warningCb(message) } +func (e *eventLoop) dropMalformedRawEvent(evType types.EventType, raw []byte) { + e.notifyWarning(fmt.Sprintf("Dropped malformed raw event type %d (len=%d)", evType, len(raw))) +} + func (e *eventLoop) resolveFdFile(fd int32, pid uint32) file.File { if fdFile, ok := e.fdState().get(fd); ok { return fdFile diff --git a/internal/eventloop_error_handling_test.go b/internal/eventloop_error_handling_test.go index 8361dea..7f2c572 100644 --- a/internal/eventloop_error_handling_test.go +++ b/internal/eventloop_error_handling_test.go @@ -113,3 +113,34 @@ func TestProcessRawEventUnknownTypeDoesNotPanicAndNotifies(t *testing.T) { t.Fatalf("expected warning notification") } } + +func TestProcessRawEventMalformedKnownTypeDoesNotPanicAndNotifies(t *testing.T) { + el := mustNewEventLoop(t, eventLoopConfig{}) + warnings := make(chan string, 1) + el.warningCb = func(message string) { warnings <- message } + + pairCh := make(chan *event.Pair, 1) + + defer func() { + if r := recover(); r != nil { + t.Fatalf("processRawEvent panicked: %v", r) + } + }() + + el.processRawEvent([]byte{byte(types.ENTER_OPEN_EVENT)}, pairCh) + + select { + case ep := <-pairCh: + t.Fatalf("unexpected event produced: %v", ep) + default: + } + + select { + case msg := <-warnings: + if msg == "" { + t.Fatalf("expected non-empty warning message") + } + default: + t.Fatalf("expected warning notification") + } +} diff --git a/internal/types/fastdecode.go b/internal/types/fastdecode.go index 92b57c6..a0ce5f1 100644 --- a/internal/types/fastdecode.go +++ b/internal/types/fastdecode.go @@ -15,6 +15,9 @@ const ( ) func NewOpenEventFast(raw []byte) *OpenEvent { + if len(raw) < openEventSize { + return nil + } if len(raw) != openEventSize { return NewOpenEvent(raw) } @@ -31,6 +34,9 @@ func NewOpenEventFast(raw []byte) *OpenEvent { } func NewNullEventFast(raw []byte) *NullEvent { + if len(raw) < nullEventSize { + return nil + } if len(raw) != nullEventSize { return NewNullEvent(raw) } @@ -44,6 +50,9 @@ func NewNullEventFast(raw []byte) *NullEvent { } func NewFdEventFast(raw []byte) *FdEvent { + if len(raw) < fdEventSize { + return nil + } if len(raw) != fdEventSize { return NewFdEvent(raw) } @@ -58,6 +67,9 @@ func NewFdEventFast(raw []byte) *FdEvent { } func NewRetEventFast(raw []byte) *RetEvent { + if len(raw) < retEventSize { + return nil + } if len(raw) != retEventSize { return NewRetEvent(raw) } @@ -73,6 +85,9 @@ func NewRetEventFast(raw []byte) *RetEvent { } func NewNameEventFast(raw []byte) *NameEvent { + if len(raw) < nameEventSize { + return nil + } if len(raw) != nameEventSize { return NewNameEvent(raw) } @@ -88,6 +103,9 @@ func NewNameEventFast(raw []byte) *NameEvent { } func NewPathEventFast(raw []byte) *PathEvent { + if len(raw) < pathEventSize { + return nil + } if len(raw) != pathEventSize { return NewPathEvent(raw) } @@ -102,6 +120,9 @@ func NewPathEventFast(raw []byte) *PathEvent { } func NewFcntlEventFast(raw []byte) *FcntlEvent { + if len(raw) < fcntlEventSize { + return nil + } if len(raw) != fcntlEventSize { return NewFcntlEvent(raw) } @@ -118,6 +139,9 @@ func NewFcntlEventFast(raw []byte) *FcntlEvent { } func NewDup3EventFast(raw []byte) *Dup3Event { + if len(raw) < dup3EventSize { + return nil + } if len(raw) != dup3EventSize { return NewDup3Event(raw) } @@ -133,6 +157,9 @@ func NewDup3EventFast(raw []byte) *Dup3Event { } func NewOpenByHandleAtEventFast(raw []byte) *OpenByHandleAtEvent { + if len(raw) < openByHandleAtEventSize { + return nil + } if len(raw) != openByHandleAtEventSize { return NewOpenByHandleAtEvent(raw) } diff --git a/internal/types/fastdecode_test.go b/internal/types/fastdecode_test.go index 3ce5592..f40faeb 100644 --- a/internal/types/fastdecode_test.go +++ b/internal/types/fastdecode_test.go @@ -125,3 +125,28 @@ func TestFastDecodersMatchGeneratedDecoders(t *testing.T) { } }) } + +func TestFastDecodersReturnNilOnShortPayload(t *testing.T) { + cases := []struct { + name string + decode func([]byte) bool + }{ + {name: "OpenEvent", decode: func(raw []byte) bool { return NewOpenEventFast(raw) == nil }}, + {name: "NullEvent", decode: func(raw []byte) bool { return NewNullEventFast(raw) == nil }}, + {name: "FdEvent", decode: func(raw []byte) bool { return NewFdEventFast(raw) == nil }}, + {name: "RetEvent", decode: func(raw []byte) bool { return NewRetEventFast(raw) == nil }}, + {name: "NameEvent", decode: func(raw []byte) bool { return NewNameEventFast(raw) == nil }}, + {name: "PathEvent", decode: func(raw []byte) bool { return NewPathEventFast(raw) == nil }}, + {name: "FcntlEvent", decode: func(raw []byte) bool { return NewFcntlEventFast(raw) == nil }}, + {name: "Dup3Event", decode: func(raw []byte) bool { return NewDup3EventFast(raw) == nil }}, + {name: "OpenByHandleAtEvent", decode: func(raw []byte) bool { return NewOpenByHandleAtEventFast(raw) == nil }}, + } + + for _, tc := range cases { + t.Run(tc.name, func(t *testing.T) { + if !tc.decode([]byte{1}) { + t.Fatalf("expected nil for short payload") + } + }) + } +} -- cgit v1.2.3