From 8ef00507063baf3fa681bb53113cb33adda5e4d7 Mon Sep 17 00:00:00 2001 From: Dmitry Vyukov Date: Wed, 13 Dec 2017 20:12:13 +0100 Subject: prog: don't serialize output data args Fixes #188 We now will write just ""/1000 to denote a 1000-byte output buffer. Also we now don't store 1000-byte buffer in memory just to denote size. Old format is still parsed. --- prog/analysis.go | 6 +++--- prog/clone.go | 2 +- prog/encoding.go | 40 ++++++++++++++++++++++++++++++---------- prog/encoding_test.go | 8 ++++++-- prog/encodingexec.go | 12 +++++++----- prog/hints.go | 20 +++++++++----------- prog/hints_test.go | 36 ++++++++++++++++++++---------------- prog/mutation.go | 26 ++++++++++++++------------ prog/prog.go | 40 ++++++++++++++++++++++++++++++++++------ prog/rand.go | 28 +++++++++++++++++----------- prog/validation.go | 16 +++++++--------- 11 files changed, 148 insertions(+), 86 deletions(-) (limited to 'prog') diff --git a/prog/analysis.go b/prog/analysis.go index 80a127108..fcb00350b 100644 --- a/prog/analysis.go +++ b/prog/analysis.go @@ -58,12 +58,12 @@ func (s *state) analyze(c *Call) { } case *BufferType: a := arg.(*DataArg) - if typ.Dir() != DirOut && len(a.Data) != 0 { + if typ.Dir() != DirOut && len(a.Data()) != 0 { switch typ.Kind { case BufferString: - s.strings[string(a.Data)] = true + s.strings[string(a.Data())] = true case BufferFilename: - s.files[string(a.Data)] = true + s.files[string(a.Data())] = true } } } diff --git a/prog/clone.go b/prog/clone.go index 1ff42f60f..bdb9b5074 100644 --- a/prog/clone.go +++ b/prog/clone.go @@ -44,7 +44,7 @@ func clone(arg Arg, newargs map[Arg]Arg) Arg { case *DataArg: a1 := new(DataArg) *a1 = *a - a1.Data = append([]byte{}, a.Data...) + a1.data = append([]byte{}, a.data...) arg1 = a1 case *GroupArg: a1 := new(GroupArg) diff --git a/prog/encoding.go b/prog/encoding.go index 4d8150959..2b7999efc 100644 --- a/prog/encoding.go +++ b/prog/encoding.go @@ -74,15 +74,19 @@ func serialize(arg Arg, buf *bytes.Buffer, vars map[Arg]int, varSeq *int) { fmt.Fprintf(buf, "&%v=", serializeAddr(arg)) serialize(a.Res, buf, vars, varSeq) case *DataArg: - data := a.Data - if !arg.Type().Varlen() { - // Statically typed data will be padded with 0s during - // deserialization, so we can strip them here for readability. - for len(data) >= 2 && data[len(data)-1] == 0 && data[len(data)-2] == 0 { - data = data[:len(data)-1] + if a.Type().Dir() == DirOut { + fmt.Fprintf(buf, "\"\"/%v", a.Size()) + } else { + data := a.Data() + if !arg.Type().Varlen() { + // Statically typed data will be padded with 0s during + // deserialization, so we can strip them here for readability. + for len(data) >= 2 && data[len(data)-1] == 0 && data[len(data)-2] == 0 { + data = data[:len(data)-1] + } } + serializeData(buf, data) } - serializeData(buf, data) case *GroupArg: var delims []byte switch arg.Type().(type) { @@ -287,13 +291,29 @@ func (target *Target) parseArg(typ Type, p *parser, vars map[string]Arg) (Arg, e if err != nil { return nil, err } + size := ^uint64(0) + if p.Char() == '/' { + p.Parse('/') + sizeStr := p.Ident() + size, err = strconv.ParseUint(sizeStr, 0, 64) + if err != nil { + return nil, fmt.Errorf("failed to parse buffer size: %q", sizeStr) + } + } if !typ.Varlen() { - if diff := int(typ.Size()) - len(data); diff > 0 { + size = typ.Size() + } else if size == ^uint64(0) { + size = uint64(len(data)) + } + if typ.Dir() == DirOut { + arg = MakeOutDataArg(typ, size) + } else { + if diff := int(size) - len(data); diff > 0 { data = append(data, make([]byte, diff)...) } - data = data[:typ.Size()] + data = data[:size] + arg = MakeDataArg(typ, data) } - arg = MakeDataArg(typ, data) case '{': t1, ok := typ.(*StructType) if !ok { diff --git a/prog/encoding_test.go b/prog/encoding_test.go index 39063a055..5fce30142 100644 --- a/prog/encoding_test.go +++ b/prog/encoding_test.go @@ -168,8 +168,12 @@ func TestSerializeDeserialize(t *testing.T) { target := initTargetTest(t, "test", "64") tests := [][2]string{ { - `serialize(&(0x7f0000408000)={"6861736800000000000000000000", "4849000000"})`, - `serialize(&(0x7f0000408000)={'hash\x00', 'HI\x00'})`, + `serialize0(&(0x7f0000408000)={"6861736800000000000000000000", "4849000000"})`, + `serialize0(&(0x7f0000408000)={'hash\x00', 'HI\x00'})`, + }, + { + `serialize1(&(0x7f0000000000)="0000000000000000", 0x8)`, + `serialize1(&(0x7f0000000000)=""/8, 0x8)`, }, } for _, test := range tests { diff --git a/prog/encodingexec.go b/prog/encodingexec.go index e45ba1192..937f3c322 100644 --- a/prog/encodingexec.go +++ b/prog/encodingexec.go @@ -104,7 +104,8 @@ func (p *Prog) SerializeForExec(buffer []byte, pid int) (int, error) { if _, ok := arg1.(*UnionArg); ok { return } - if a1, ok := arg1.(*DataArg); ok && len(a1.Data) == 0 { + if a1, ok := arg1.(*DataArg); ok && + (a1.Type().Dir() == DirOut || len(a1.Data()) == 0) { return } if !IsPad(arg1.Type()) && arg1.Type().Dir() != DirOut { @@ -270,16 +271,17 @@ func (w *execContext) writeArg(arg Arg, pid int, csumMap map[Arg]CsumInfo) { w.write(0) // bit field offset w.write(0) // bit field length case *DataArg: + data := a.Data() w.write(ExecArgData) - w.write(uint64(len(a.Data))) - padded := len(a.Data) - if pad := 8 - len(a.Data)%8; pad != 8 { + w.write(uint64(len(data))) + padded := len(data) + if pad := 8 - len(data)%8; pad != 8 { padded += pad } if len(w.buf) < padded { w.eof = true } else { - copy(w.buf, a.Data) + copy(w.buf, data) w.buf = w.buf[padded:] } default: diff --git a/prog/hints.go b/prog/hints.go index e9f6f8a62..fb3100c3f 100644 --- a/prog/hints.go +++ b/prog/hints.go @@ -120,16 +120,21 @@ func checkConstArg(arg *ConstArg, compMap CompMap, exec func()) { func checkDataArg(arg *DataArg, compMap CompMap, exec func()) { bytes := make([]byte, 8) - for i := 0; i < min(len(arg.Data), maxDataLength); i++ { + data := arg.Data() + size := len(data) + if size > maxDataLength { + size = maxDataLength + } + for i := 0; i < size; i++ { original := make([]byte, 8) - copy(original, arg.Data[i:]) + copy(original, data[i:]) val := binary.LittleEndian.Uint64(original) for replacer := range shrinkExpand(val, compMap) { binary.LittleEndian.PutUint64(bytes, replacer) - copy(arg.Data[i:], bytes) + copy(data[i:], bytes) exec() } - copy(arg.Data[i:], original) + copy(data[i:], original) } } @@ -205,10 +210,3 @@ func init() { specialIntsSet[v] = true } } - -func min(a, b int) int { - if a <= b { - return a - } - return b -} diff --git a/prog/hints_test.go b/prog/hints_test.go index 904d36500..eef2758b6 100644 --- a/prog/hints_test.go +++ b/prog/hints_test.go @@ -161,10 +161,10 @@ func TestHintsCheckDataArg(t *testing.T) { res := make(map[string]bool) // Whatever type here. It's just needed to pass the // dataArg.Type().Dir() == DirIn check. - typ := ArrayType{TypeCommon{"", "", 0, DirIn, false}, nil, 0, 0, 0} - dataArg := &DataArg{ArgCommon{&typ}, []byte(test.in)} + typ := &ArrayType{TypeCommon{"", "", 0, DirIn, false}, nil, 0, 0, 0} + dataArg := MakeDataArg(typ, []byte(test.in)).(*DataArg) checkDataArg(dataArg, test.comps, func() { - res[string(dataArg.Data)] = true + res[string(dataArg.Data())] = true }) if !reflect.DeepEqual(res, test.res) { s := "\ngot: [" @@ -365,26 +365,30 @@ func TestHintsRandom(t *testing.T) { func extractValues(c *Call) map[uint64]bool { vals := make(map[uint64]bool) foreachArg(c, func(arg, _ Arg, _ *[]Arg) { + if arg.Type().Dir() == DirOut { + return + } switch a := arg.(type) { case *ConstArg: vals[a.Val] = true case *DataArg: - for i := range a.Data { - vals[uint64(a.Data[i])] = true - if i < len(a.Data)-1 { - v := uint64(a.Data[i]) | uint64(a.Data[i+1])<<8 + data := a.Data() + for i := range data { + vals[uint64(data[i])] = true + if i < len(data)-1 { + v := uint64(data[i]) | uint64(data[i+1])<<8 vals[v] = true } - if i < len(a.Data)-3 { - v := uint64(a.Data[i]) | uint64(a.Data[i+1])<<8 | - uint64(a.Data[i+2])<<16 | uint64(a.Data[i+3])<<24 + if i < len(data)-3 { + v := uint64(data[i]) | uint64(data[i+1])<<8 | + uint64(data[i+2])<<16 | uint64(data[i+3])<<24 vals[v] = true } - if i < len(a.Data)-7 { - v := uint64(a.Data[i]) | uint64(a.Data[i+1])<<8 | - uint64(a.Data[i+2])<<16 | uint64(a.Data[i+3])<<24 | - uint64(a.Data[i+4])<<32 | uint64(a.Data[i+5])<<40 | - uint64(a.Data[i+6])<<48 | uint64(a.Data[i+7])<<56 + if i < len(data)-7 { + v := uint64(data[i]) | uint64(data[i+1])<<8 | + uint64(data[i+2])<<16 | uint64(data[i+3])<<24 | + uint64(data[i+4])<<32 | uint64(data[i+5])<<40 | + uint64(data[i+6])<<48 | uint64(data[i+7])<<56 vals[v] = true } } @@ -428,7 +432,7 @@ func TestHintsData(t *testing.T) { var got []string p.MutateWithHints(0, test.comps, func(newP *Prog) { got = append(got, hex.EncodeToString( - newP.Calls[0].Args[0].(*PointerArg).Res.(*DataArg).Data)) + newP.Calls[0].Args[0].(*PointerArg).Res.(*DataArg).Data())) }) sort.Strings(test.out) sort.Strings(got) diff --git a/prog/mutation.go b/prog/mutation.go index 63c58ad7f..1aeef4fa4 100644 --- a/prog/mutation.go +++ b/prog/mutation.go @@ -101,27 +101,28 @@ func (p *Prog) Mutate(rs rand.Source, ncalls int, ct *ChoiceTable, corpus []*Pro a := arg.(*DataArg) switch t.Kind { case BufferBlobRand, BufferBlobRange: - var data []byte - data = append([]byte{}, a.Data...) + data := append([]byte{}, a.Data()...) minLen, maxLen := uint64(0), maxBlobLen if t.Kind == BufferBlobRange { minLen, maxLen = t.RangeBegin, t.RangeEnd } - a.Data = mutateData(r, data, minLen, maxLen) + a.data = mutateData(r, data, minLen, maxLen) case BufferString: + data := append([]byte{}, a.Data()...) if r.bin() { minLen, maxLen := uint64(0), maxBlobLen if t.TypeSize != 0 { minLen, maxLen = t.TypeSize, t.TypeSize } - a.Data = mutateData(r, append([]byte{}, a.Data...), minLen, maxLen) + a.data = mutateData(r, data, minLen, maxLen) } else { - a.Data = r.randString(s, t.Values, t.Dir()) + a.data = r.randString(s, t.Values, t.Dir()) } case BufferFilename: - a.Data = []byte(r.filename(s)) + a.data = []byte(r.filename(s)) case BufferText: - a.Data = r.mutateText(t.Text, a.Data) + data := append([]byte{}, a.Data()...) + a.data = r.mutateText(t.Text, data) default: panic("unknown buffer kind") } @@ -430,19 +431,20 @@ func Minimize(p0 *Prog, callIndex0 int, pred0 func(*Prog, int) bool, crash bool) return false } triedPaths[path] = true - if typ.Kind != BufferBlobRand && typ.Kind != BufferBlobRange { + if typ.Kind != BufferBlobRand && typ.Kind != BufferBlobRange || + typ.Dir() == DirOut { return false } a := arg.(*DataArg) minLen := int(typ.RangeBegin) - for step := len(a.Data) - minLen; len(a.Data) > minLen && step > 0; { - if len(a.Data)-step >= minLen { - a.Data = a.Data[:len(a.Data)-step] + for step := len(a.Data()) - minLen; len(a.Data()) > minLen && step > 0; { + if len(a.Data())-step >= minLen { + a.data = a.Data()[:len(a.Data())-step] p.Target.assignSizesCall(call) if pred(p, callIndex0) { continue } - a.Data = a.Data[:len(a.Data)+step] + a.data = a.Data()[:len(a.Data())+step] p.Target.assignSizesCall(call) } step /= 2 diff --git a/prog/prog.go b/prog/prog.go index 0fcf6b66e..66de938b3 100644 --- a/prog/prog.go +++ b/prog/prog.go @@ -86,11 +86,36 @@ func (arg *PointerArg) Size() uint64 { // Used for BufferType. type DataArg struct { ArgCommon - Data []byte + data []byte // for in/inout args + size uint64 // for out Args +} + +func MakeDataArg(t Type, data []byte) Arg { + if t.Dir() == DirOut { + panic("non-empty output data arg") + } + return &DataArg{ArgCommon: ArgCommon{typ: t}, data: append([]byte{}, data...)} +} + +func MakeOutDataArg(t Type, size uint64) Arg { + if t.Dir() != DirOut { + panic("empty input data arg") + } + return &DataArg{ArgCommon: ArgCommon{typ: t}, size: size} } func (arg *DataArg) Size() uint64 { - return uint64(len(arg.Data)) + if len(arg.data) != 0 { + return uint64(len(arg.data)) + } + return arg.size +} + +func (arg *DataArg) Data() []byte { + if arg.Type().Dir() == DirOut { + panic("getting data of output data arg") + } + return arg.data } // Used for StructType and ArrayType. @@ -240,10 +265,6 @@ func MakeResultArg(t Type, r Arg, v uint64) Arg { return arg } -func MakeDataArg(t Type, data []byte) Arg { - return &DataArg{ArgCommon: ArgCommon{typ: t}, Data: append([]byte{}, data...)} -} - func MakePointerArg(t Type, page uint64, off int, npages uint64, obj Arg) Arg { return &PointerArg{ArgCommon: ArgCommon{typ: t}, PageIndex: page, PageOffset: off, PagesNum: npages, Res: obj} } @@ -267,6 +288,13 @@ func defaultArg(t Type) Arg { case *ResourceType: return MakeResultArg(t, nil, typ.Desc.Type.Default()) case *BufferType: + if t.Dir() == DirOut { + var sz uint64 + if !typ.Varlen() { + sz = typ.Size() + } + return MakeOutDataArg(t, sz) + } var data []byte if !typ.Varlen() { data = make([]byte, typ.Size()) diff --git a/prog/rand.go b/prog/rand.go index b99399c9d..173248599 100644 --- a/prog/rand.go +++ b/prog/rand.go @@ -593,32 +593,38 @@ func (r *randGen) generateArg(s *state, typ Type) (arg Arg, calls []*Call) { if a.Kind == BufferBlobRange { sz = r.randRange(a.RangeBegin, a.RangeEnd) } + if a.Dir() == DirOut { + return MakeOutDataArg(a, sz), nil + } data := make([]byte, sz) - if a.Dir() != DirOut { - for i := range data { - data[i] = byte(r.Intn(256)) - } + for i := range data { + data[i] = byte(r.Intn(256)) } return MakeDataArg(a, data), nil case BufferString: data := r.randString(s, a.Values, a.Dir()) + if a.Dir() == DirOut { + return MakeOutDataArg(a, uint64(len(data))), nil + } return MakeDataArg(a, data), nil case BufferFilename: - var data []byte if a.Dir() == DirOut { + sz := 0 switch { case r.nOutOf(1, 3): - data = make([]byte, r.Intn(100)) + sz = r.Intn(100) case r.nOutOf(1, 2): - data = make([]byte, 108) // UNIX_PATH_MAX + sz = 108 // UNIX_PATH_MAX default: - data = make([]byte, 4096) // PATH_MAX + sz = 4096 // PATH_MAX } - } else { - data = []byte(r.filename(s)) + return MakeOutDataArg(a, uint64(sz)), nil } - return MakeDataArg(a, data), nil + return MakeDataArg(a, []byte(r.filename(s))), nil case BufferText: + if a.Dir() == DirOut { + return MakeOutDataArg(a, uint64(r.Intn(100))), nil + } return MakeDataArg(a, r.generateText(a.Text)), nil default: panic("unknown buffer kind") diff --git a/prog/validation.go b/prog/validation.go index e1f083b2a..d0f6d0bd2 100644 --- a/prog/validation.go +++ b/prog/validation.go @@ -68,10 +68,9 @@ func (c *Call) validate(ctx *validCtx) error { return fmt.Errorf("syscall %v: output arg '%v'/'%v' has non default value '%+v'", c.Meta.Name, a.Type().FieldName(), a.Type().Name(), a) } case *DataArg: - for _, v := range a.Data { - if v != 0 { - return fmt.Errorf("syscall %v: output arg '%v' has data", c.Meta.Name, a.Type().Name()) - } + if len(a.data) != 0 { + return fmt.Errorf("syscall %v: output arg '%v' has data", + c.Meta.Name, a.Type().Name()) } } } @@ -123,9 +122,9 @@ func (c *Call) validate(ctx *validCtx) error { case *DataArg: switch typ1.Kind { case BufferString: - if typ1.TypeSize != 0 && uint64(len(a.Data)) != typ1.TypeSize { + if typ1.TypeSize != 0 && a.Size() != typ1.TypeSize { return fmt.Errorf("syscall %v: string arg '%v' has size %v, which should be %v", - c.Meta.Name, a.Type().Name(), len(a.Data), typ1.TypeSize) + c.Meta.Name, a.Type().Name(), a.Size(), typ1.TypeSize) } } default: @@ -178,10 +177,9 @@ func (c *Call) validate(ctx *validCtx) error { } case *DataArg: typ1 := a.Type() - if !typ1.Varlen() && typ1.Size() != uint64(len(a.Data)) { + if !typ1.Varlen() && typ1.Size() != a.Size() { return fmt.Errorf("syscall %v: data arg %v has wrong size %v, want %v", - c.Meta.Name, arg.Type().Name(), - len(a.Data), typ1.Size()) + c.Meta.Name, arg.Type().Name(), a.Size(), typ1.Size()) } switch typ1 := a.Type().(type) { case *ArrayType: -- cgit mrf-deployment