mirror of
https://github.com/Buriburizaem0n/nezha_domains.git
synced 2026-09-19 09:40:12 +00:00
fix(agentcompat): decode inotify events safely
Co-authored-by: naiba/CloudCode <hi+cloudcode@nai.ba>
This commit is contained in:
@@ -5,11 +5,11 @@ package process
|
|||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
"sync"
|
"sync"
|
||||||
"unsafe"
|
|
||||||
|
|
||||||
"golang.org/x/sys/unix"
|
"golang.org/x/sys/unix"
|
||||||
)
|
)
|
||||||
@@ -35,6 +35,12 @@ type SQLiteJournalWatch struct {
|
|||||||
deleted bool
|
deleted bool
|
||||||
}
|
}
|
||||||
|
|
||||||
|
type inotifyEvent struct {
|
||||||
|
watchDescriptor int32
|
||||||
|
mask uint32
|
||||||
|
name []byte
|
||||||
|
}
|
||||||
|
|
||||||
func OpenSQLiteJournalWatch(path string) (*SQLiteJournalWatch, error) {
|
func OpenSQLiteJournalWatch(path string) (*SQLiteJournalWatch, error) {
|
||||||
journalFD, err := unix.Open(path, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
journalFD, err := unix.Open(path, unix.O_PATH|unix.O_NOFOLLOW|unix.O_CLOEXEC, 0)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
@@ -140,22 +146,35 @@ func (watch *SQLiteJournalWatch) readEvents() error {
|
|||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
for offset := 0; offset+unix.SizeofInotifyEvent <= count; {
|
for offset := 0; offset < count; {
|
||||||
event := (*unix.InotifyEvent)(unsafe.Pointer(&buffer[offset]))
|
event, consumed, err := decodeInotifyEvent(buffer[offset:count])
|
||||||
next := offset + unix.SizeofInotifyEvent + int(event.Len)
|
if err != nil {
|
||||||
if next > count {
|
|
||||||
return &SQLiteJournalLifecycleError{}
|
return &SQLiteJournalLifecycleError{}
|
||||||
}
|
}
|
||||||
nameStart := offset + unix.SizeofInotifyEvent
|
if err := watch.observeEvent(event.watchDescriptor, event.mask, event.name); err != nil {
|
||||||
name := bytes.TrimRight(buffer[nameStart:next], "\x00")
|
|
||||||
if err := watch.observeEvent(event.Wd, event.Mask, name); err != nil {
|
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
offset = next
|
offset += consumed
|
||||||
}
|
}
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func decodeInotifyEvent(buffer []byte) (inotifyEvent, int, error) {
|
||||||
|
if len(buffer) < unix.SizeofInotifyEvent {
|
||||||
|
return inotifyEvent{}, 0, errors.New("truncated inotify header")
|
||||||
|
}
|
||||||
|
nameLength := binary.NativeEndian.Uint32(buffer[12:])
|
||||||
|
if uint64(nameLength) > uint64(len(buffer)-unix.SizeofInotifyEvent) {
|
||||||
|
return inotifyEvent{}, 0, errors.New("truncated inotify name")
|
||||||
|
}
|
||||||
|
consumed64 := uint64(unix.SizeofInotifyEvent) + uint64(nameLength)
|
||||||
|
if consumed64 > uint64(^uint(0)>>1) {
|
||||||
|
return inotifyEvent{}, 0, errors.New("inotify event exceeds int range")
|
||||||
|
}
|
||||||
|
consumed := int(consumed64)
|
||||||
|
return inotifyEvent{watchDescriptor: int32(binary.NativeEndian.Uint32(buffer)), mask: binary.NativeEndian.Uint32(buffer[4:]), name: bytes.TrimRight(buffer[unix.SizeofInotifyEvent:consumed], "\x00")}, consumed, nil
|
||||||
|
}
|
||||||
|
|
||||||
func (watch *SQLiteJournalWatch) observeEvent(watchDescriptor int32, mask uint32, name []byte) error {
|
func (watch *SQLiteJournalWatch) observeEvent(watchDescriptor int32, mask uint32, name []byte) error {
|
||||||
if int(watchDescriptor) == watch.directoryWD && mask&unix.IN_DELETE != 0 && bytes.Equal(name, watch.journalName) {
|
if int(watchDescriptor) == watch.directoryWD && mask&unix.IN_DELETE != 0 && bytes.Equal(name, watch.journalName) {
|
||||||
return watch.observe(unix.IN_DELETE_SELF)
|
return watch.observe(unix.IN_DELETE_SELF)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@ package process
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
|
"encoding/binary"
|
||||||
"errors"
|
"errors"
|
||||||
"os"
|
"os"
|
||||||
"path/filepath"
|
"path/filepath"
|
||||||
@@ -132,6 +133,65 @@ func TestSQLiteJournalWatch_RejectsDuplicateTerminalEvent(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestSQLiteJournalWatch_ReadEventsRejectsTruncatedName(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
pipe := make([]int, 2)
|
||||||
|
requireNoError(t, unix.Pipe(pipe))
|
||||||
|
readFD, writeFD := pipe[0], pipe[1]
|
||||||
|
t.Cleanup(func() { requireNoError(t, unix.Close(readFD)) })
|
||||||
|
t.Cleanup(func() { requireNoError(t, unix.Close(writeFD)) })
|
||||||
|
watch := &SQLiteJournalWatch{inotifyFD: readFD}
|
||||||
|
buffer := make([]byte, unix.SizeofInotifyEvent)
|
||||||
|
binary.NativeEndian.PutUint32(buffer[12:], 1)
|
||||||
|
_, err := unix.Write(writeFD, buffer)
|
||||||
|
requireNoError(t, err)
|
||||||
|
|
||||||
|
// When
|
||||||
|
err = watch.readEvents()
|
||||||
|
|
||||||
|
// Then
|
||||||
|
if !errors.Is(err, ErrSQLiteJournalLifecycle) {
|
||||||
|
t.Fatalf("truncated event error=%v, want lifecycle error", err)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeInotifyEvent_DecodesUnalignedNativeEndianEvent(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
name := []byte("journal\x00\x00")
|
||||||
|
buffer := append([]byte{0xff}, make([]byte, unix.SizeofInotifyEvent+len(name))...)
|
||||||
|
eventBytes := buffer[1:]
|
||||||
|
binary.NativeEndian.PutUint32(eventBytes, 17)
|
||||||
|
binary.NativeEndian.PutUint32(eventBytes[4:], unix.IN_DELETE)
|
||||||
|
binary.NativeEndian.PutUint32(eventBytes[12:], uint32(len(name)))
|
||||||
|
copy(eventBytes[unix.SizeofInotifyEvent:], name)
|
||||||
|
|
||||||
|
// When
|
||||||
|
event, consumed, err := decodeInotifyEvent(eventBytes)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
requireNoError(t, err)
|
||||||
|
if event.watchDescriptor != 17 || event.mask != unix.IN_DELETE || string(event.name) != "journal" {
|
||||||
|
t.Fatalf("event=%+v", event)
|
||||||
|
}
|
||||||
|
if consumed != len(eventBytes) {
|
||||||
|
t.Fatalf("consumed=%d, want %d", consumed, len(eventBytes))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestDecodeInotifyEvent_RejectsTruncatedName(t *testing.T) {
|
||||||
|
// Given
|
||||||
|
buffer := make([]byte, unix.SizeofInotifyEvent)
|
||||||
|
binary.NativeEndian.PutUint32(buffer[12:], 1)
|
||||||
|
|
||||||
|
// When
|
||||||
|
_, _, err := decodeInotifyEvent(buffer)
|
||||||
|
|
||||||
|
// Then
|
||||||
|
if err == nil {
|
||||||
|
t.Fatal("truncated inotify name was accepted")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func TestSQLiteJournalWatch_WaitsForCloseThenDeleteAndCancellation(t *testing.T) {
|
func TestSQLiteJournalWatch_WaitsForCloseThenDeleteAndCancellation(t *testing.T) {
|
||||||
// Given
|
// Given
|
||||||
path := writeJournal(t, "dashboard.sqlite-journal")
|
path := writeJournal(t, "dashboard.sqlite-journal")
|
||||||
|
|||||||
Reference in New Issue
Block a user