104 lines
3.4 KiB
Go
104 lines
3.4 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|||
|
|
// SPDX-License-Identifier: MIT
|
||
|
|
|
||
|
|
package nfs4
|
||
|
|
|
||
|
|
import (
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"sourcedock.dev/petrbalvin/nfs/internal/xdr"
|
||
|
|
)
|
||
|
|
|
||
|
|
// FuzzDecodeCompound feeds arbitrary compound payloads through the
|
||
|
|
// argument and result walkers: no input may panic, and every malformed
|
||
|
|
// body must arrive as an error, never as a wrong split.
|
||
|
|
func FuzzDecodeCompound(f *testing.F) {
|
||
|
|
args := AppendCompoundArgs(nil, "seed", MinorVersion, [][]byte{
|
||
|
|
AppendSequenceArgs(nil, SessionID{1}, 1, 0, 0, true),
|
||
|
|
AppendPutRootfh(nil),
|
||
|
|
AppendLookup(nil, "a"),
|
||
|
|
})
|
||
|
|
f.Add(args)
|
||
|
|
res := AppendCompoundRes(nil, ErrOK, "seed", [][]byte{
|
||
|
|
append(AppendOpHeader(nil, OpSequence, ErrOK), AppendSequenceRes(nil, SessionID{1}, 1, 0, 0, 0)...),
|
||
|
|
AppendOpHeader(nil, OpPutRootfh, ErrOK),
|
||
|
|
})
|
||
|
|
f.Add(res)
|
||
|
|
f.Add([]byte{0, 0, 0, 0, 0, 0, 0, 2, 0, 0, 0, 3, 0, 0, 0, 75, 0, 0, 0, 0})
|
||
|
|
f.Add([]byte{0, 0, 0, 9, 0, 0, 0, 1, 0, 0, 0, 1})
|
||
|
|
f.Fuzz(func(t *testing.T, data []byte) {
|
||
|
|
// The property under test is that none of this panics; a
|
||
|
|
// malformed body is an ordinary error and a well formed one
|
||
|
|
// decodes with bodies that stay inside the payload.
|
||
|
|
_, d, err := DecodeCompoundArgs(data)
|
||
|
|
if err == nil {
|
||
|
|
for {
|
||
|
|
op, oerr := d.Uint32()
|
||
|
|
if oerr != nil || walkArgs(op, d) {
|
||
|
|
break
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|
||
|
|
_, _, _ = DecodeCompoundResBodies(data)
|
||
|
|
_, _, _ = DecodeCompoundResBodiesCB(data)
|
||
|
|
})
|
||
|
|
}
|
||
|
|
|
||
|
|
// walkArgs walks one operation's arguments the way the dispatcher does,
|
||
|
|
// so the fuzzed stream exercises the real decoders. The boolean reports
|
||
|
|
// that the stream ended or was refused.
|
||
|
|
func walkArgs(op uint32, d *xdr.Decoder) bool {
|
||
|
|
switch op {
|
||
|
|
case OpExchangeID:
|
||
|
|
_, err := DecodeExchangeIDArgs(d)
|
||
|
|
return err != nil
|
||
|
|
case OpCreateSession:
|
||
|
|
_, err := DecodeCreateSessionArgs(d)
|
||
|
|
return err != nil
|
||
|
|
case OpSequence:
|
||
|
|
_, err := DecodeSequenceArgs(d)
|
||
|
|
return err != nil
|
||
|
|
case OpSecinfoNoName:
|
||
|
|
_, err := DecodeSecinfoNoNameArgs(d)
|
||
|
|
return err != nil
|
||
|
|
}
|
||
|
|
_, err := d.Uint32()
|
||
|
|
return err != nil
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestRegistryValues pins the registry numbers this package speaks
|
||
|
|
// against the standards: an accidental renumbering of any of these
|
||
|
|
// breaks interoperation with every conformant peer, so the values
|
||
|
|
// themselves are the contract.
|
||
|
|
func TestRegistryValues(t *testing.T) {
|
||
|
|
pins := []struct {
|
||
|
|
name string
|
||
|
|
got uint32
|
||
|
|
want uint32
|
||
|
|
rfc string
|
||
|
|
}{
|
||
|
|
{"session id size", uint32(len(SessionID{})), 16, "RFC 7863"},
|
||
|
|
{"FATTR4_TIME_ACCESS_SET", AttrTimeAccessSet, 48, "RFC 7863"},
|
||
|
|
{"FATTR4_TIME_MODIFY_SET", AttrTimeModifySet, 54, "RFC 7863"},
|
||
|
|
{"LAYOUT4_FLEX_FILES", LayoutTypeFlexfiles, 4, "RFC 8435"},
|
||
|
|
{"LAYOUT4_NFSV4_1_FILES", LayoutTypeFiles, 1, "RFC 7863"},
|
||
|
|
{"LAYOUT4_OSD2_OBJECTS", LayoutTypeObjects, 2, "RFC 7863"},
|
||
|
|
{"LAYOUT4_BLOCK_VOLUME", LayoutTypeBlock, 3, "RFC 7863"},
|
||
|
|
{"layoutiomode4 RW", IoModeRW, 2, "RFC 7863"},
|
||
|
|
{"layoutiomode4 ANY", IoModeAny, 3, "RFC 7863"},
|
||
|
|
{"SP4_MACH_CRED", StateProtectMachCred, 1, "RFC 8881"},
|
||
|
|
{"SP4_SSV", StateProtectSSV, 2, "RFC 8881"},
|
||
|
|
{"SECINFO_STYLE4_CURRENT_FH", StyleCurrentFH, 0, "RFC 8881"},
|
||
|
|
{"SECINFO_STYLE4_PARENT", StyleParent, 1, "RFC 8881"},
|
||
|
|
{"NFS4ERR_NXIO", ErrNXIO, 6, "RFC 8881"},
|
||
|
|
{"NFS4ERR_BADOWNER", ErrBadOwner, 10093, "RFC 8881"},
|
||
|
|
{"NFS4ERR_NOXATTR", ErrNoXattr, 10095, "RFC 8276"},
|
||
|
|
{"NFS4ERR_XATTR2BIG", ErrXattr2Big, 10096, "RFC 8276"},
|
||
|
|
}
|
||
|
|
for _, p := range pins {
|
||
|
|
if p.got != p.want {
|
||
|
|
t.Errorf("%s: got %d, want %d per %s", p.name, p.got, p.want, p.rfc)
|
||
|
|
}
|
||
|
|
}
|
||
|
|
}
|