aboutsummaryrefslogtreecommitdiffstats
path: root/prog
diff options
context:
space:
mode:
authorDmitry Vyukov <dvyukov@google.com>2024-05-06 08:33:51 +0200
committerDmitry Vyukov <dvyukov@google.com>2024-05-06 11:24:51 +0000
commit441f4fcc08ae33f36e5a15a9dd5abde3f0797921 (patch)
treeb85c0f6661d52d537e6e39b2791f0d7022390fec /prog
parent69f2eab004cdc5bce339d5359dcf234698153dc7 (diff)
prog: fix validation of DataMmapProg
Allow to serialize/deserialize DataMmapProg and fix validation in debug mode. Fixes #4750
Diffstat (limited to 'prog')
-rw-r--r--prog/clone.go8
-rw-r--r--prog/encoding.go49
-rw-r--r--prog/encoding_test.go18
-rw-r--r--prog/mutation.go3
-rw-r--r--prog/prog.go3
-rw-r--r--prog/rand.go5
-rw-r--r--prog/validation.go32
7 files changed, 68 insertions, 50 deletions
diff --git a/prog/clone.go b/prog/clone.go
index 6ec28e39e..d41f3c13c 100644
--- a/prog/clone.go
+++ b/prog/clone.go
@@ -12,6 +12,14 @@ func (p *Prog) Clone() *Prog {
}
func (p *Prog) cloneWithMap(newargs map[*ResultArg]*ResultArg) *Prog {
+ if p.isUnsafe {
+ // We could clone it, but since we prohibit mutation
+ // of unsafe programs, it's unclear why we would clone it.
+ // Note: this also covers cloning of corpus programs
+ // during mutation, so if this is removed, we may need
+ // additional checks during mutation.
+ panic("cloning of unsafe programs is not supposed to be done")
+ }
p1 := &Prog{
Target: p.Target,
Calls: cloneCalls(p.Calls, newargs),
diff --git a/prog/encoding.go b/prog/encoding.go
index 06099993f..af0bccfc0 100644
--- a/prog/encoding.go
+++ b/prog/encoding.go
@@ -256,7 +256,8 @@ func (target *Target) Deserialize(data []byte, mode DeserializeMode) (*Prog, err
}
}()
strict := mode == Strict || mode == StrictUnsafe
- p := newParser(target, data, strict)
+ unsafe := mode == StrictUnsafe || mode == NonStrictUnsafe
+ p := newParser(target, data, strict, unsafe)
prog, err := p.parseProg()
if err := p.Err(); err != nil {
return nil, err
@@ -270,7 +271,6 @@ func (target *Target) Deserialize(data []byte, mode DeserializeMode) (*Prog, err
if err := prog.validateWithOpts(validationOptions{
// Don't validate auto-set conditional fields. We'll patch them later.
ignoreTransient: true,
- allowUnsafe: mode == StrictUnsafe || mode == NonStrictUnsafe,
}); err != nil {
return nil, err
}
@@ -278,7 +278,7 @@ func (target *Target) Deserialize(data []byte, mode DeserializeMode) (*Prog, err
if p.autos != nil {
p.fixupAutos(prog)
}
- if mode != StrictUnsafe {
+ if !unsafe {
if err := prog.sanitize(!strict); err != nil {
return nil, err
}
@@ -288,7 +288,8 @@ func (target *Target) Deserialize(data []byte, mode DeserializeMode) (*Prog, err
func (p *parser) parseProg() (*Prog, error) {
prog := &Prog{
- Target: p.target,
+ Target: p.target,
+ isUnsafe: p.unsafe,
}
for p.Scan() {
if p.EOF() {
@@ -860,31 +861,8 @@ func (p *parser) parseAddr() (uint64, uint64, error) {
if err != nil {
return 0, 0, fmt.Errorf("failed to parse addr: %q", pstr)
}
- if addr < encodingAddrBase {
- return 0, 0, fmt.Errorf("address without base offset: %q", pstr)
- }
addr -= encodingAddrBase
- // This is not used anymore, but left here to parse old programs.
- if p.Char() == '+' || p.Char() == '-' {
- minus := false
- if p.Char() == '-' {
- minus = true
- p.Parse('-')
- } else {
- p.Parse('+')
- }
- ostr := p.Ident()
- off, err := strconv.ParseUint(ostr, 0, 64)
- if err != nil {
- return 0, 0, fmt.Errorf("failed to parse addr offset: %q", ostr)
- }
- if minus {
- off = -off
- }
- addr += off
- }
target := p.target
- maxMem := target.NumPages * target.PageSize
var vmaSize uint64
if p.Char() == '/' {
p.Parse('/')
@@ -898,11 +876,14 @@ func (p *parser) parseAddr() (uint64, uint64, error) {
if vmaSize == 0 {
vmaSize = target.PageSize
}
- if vmaSize > maxMem {
- vmaSize = maxMem
- }
- if addr > maxMem-vmaSize {
- addr = maxMem - vmaSize
+ if !p.unsafe {
+ maxMem := target.NumPages * target.PageSize
+ if vmaSize > maxMem {
+ vmaSize = maxMem
+ }
+ if addr > maxMem-vmaSize {
+ addr = maxMem - vmaSize
+ }
}
}
p.Parse(')')
@@ -1119,6 +1100,7 @@ func fromHexChar(v byte) (byte, bool) {
type parser struct {
target *Target
strict bool
+ unsafe bool
vars map[string]*ResultArg
autos map[Arg]bool
comment string
@@ -1130,10 +1112,11 @@ type parser struct {
e error
}
-func newParser(target *Target, data []byte, strict bool) *parser {
+func newParser(target *Target, data []byte, strict, unsafe bool) *parser {
p := &parser{
target: target,
strict: strict,
+ unsafe: unsafe,
vars: make(map[string]*ResultArg),
data: data,
}
diff --git a/prog/encoding_test.go b/prog/encoding_test.go
index 608f94371..64e10ef0f 100644
--- a/prog/encoding_test.go
+++ b/prog/encoding_test.go
@@ -10,6 +10,8 @@ import (
"reflect"
"sort"
"testing"
+
+ "github.com/stretchr/testify/assert"
)
func setToArray(s map[string]struct{}) []string {
@@ -32,7 +34,7 @@ func TestSerializeData(t *testing.T) {
}
buf := new(bytes.Buffer)
serializeData(buf, data, readable)
- p := newParser(nil, buf.Bytes(), true)
+ p := newParser(nil, buf.Bytes(), true, false)
if !p.Scan() {
t.Fatalf("parser does not scan")
}
@@ -386,6 +388,18 @@ func TestSerializeDeserialize(t *testing.T) {
})
}
+func TestDeserializeDataMmapProg(t *testing.T) {
+ testEachTarget(t, func(t *testing.T, target *Target) {
+ p := target.DataMmapProg()
+ data := p.Serialize()
+ p2, err := target.Deserialize(data, StrictUnsafe)
+ assert.NoError(t, err)
+ data2 := p2.Serialize()
+ assert.Equal(t, string(data), string(data2))
+ p.SerializeForExec()
+ })
+}
+
func TestSerializeDeserializeRandom(t *testing.T) {
testEachTargetRandom(t, func(t *testing.T, target *Target, rs rand.Source, iters int) {
ct := target.DefaultChoiceTable()
@@ -545,7 +559,7 @@ func TestHasNext(t *testing.T) {
{"abc", true},
}
for _, testCase := range testCases {
- p := newParser(nil, []byte(testCase.input), true)
+ p := newParser(nil, []byte(testCase.input), true, false)
if !p.Scan() {
t.Fatalf("parser does not scan")
}
diff --git a/prog/mutation.go b/prog/mutation.go
index 8547be3cc..c6cd3c7cf 100644
--- a/prog/mutation.go
+++ b/prog/mutation.go
@@ -55,6 +55,9 @@ func (o MutateOpts) weight() int {
func (p *Prog) MutateWithOpts(rs rand.Source, ncalls int, ct *ChoiceTable, noMutate map[int]bool,
corpus []*Prog, opts MutateOpts) {
+ if p.isUnsafe {
+ panic("mutation of unsafe programs is not supposed to be done")
+ }
totalWeight := opts.weight()
r := newRand(p.Target, rs)
if ncalls < len(p.Calls) {
diff --git a/prog/prog.go b/prog/prog.go
index 880e02eeb..29667d217 100644
--- a/prog/prog.go
+++ b/prog/prog.go
@@ -12,6 +12,9 @@ type Prog struct {
Target *Target
Calls []*Call
Comments []string
+
+ // Was deserialized using Unsafe mode, so can do unsafe things.
+ isUnsafe bool
}
func (p *Prog) CallName(call int) string {
diff --git a/prog/rand.go b/prog/rand.go
index 6c58f7649..c55d43a3f 100644
--- a/prog/rand.go
+++ b/prog/rand.go
@@ -663,8 +663,9 @@ func (target *Target) GenSampleProg(meta *Syscall, rs rand.Source) *Prog {
// Also used for testing as the simplest program.
func (target *Target) DataMmapProg() *Prog {
return &Prog{
- Target: target,
- Calls: target.MakeDataMmap(),
+ Target: target,
+ Calls: target.MakeDataMmap(),
+ isUnsafe: true,
}
}
diff --git a/prog/validation.go b/prog/validation.go
index b2a358706..38cc3873e 100644
--- a/prog/validation.go
+++ b/prog/validation.go
@@ -26,23 +26,24 @@ func (p *Prog) validate() error {
}
type validCtx struct {
- target *Target
- opts validationOptions
- args map[Arg]bool
- uses map[Arg]Arg
+ target *Target
+ isUnsafe bool
+ opts validationOptions
+ args map[Arg]bool
+ uses map[Arg]Arg
}
type validationOptions struct {
ignoreTransient bool
- allowUnsafe bool // allow global file names, etc
}
func (p *Prog) validateWithOpts(opts validationOptions) error {
ctx := &validCtx{
- target: p.Target,
- opts: opts,
- args: make(map[Arg]bool),
- uses: make(map[Arg]Arg),
+ target: p.Target,
+ isUnsafe: p.isUnsafe,
+ opts: opts,
+ args: make(map[Arg]bool),
+ uses: make(map[Arg]Arg),
}
for i, c := range p.Calls {
if c.Meta == nil {
@@ -61,7 +62,7 @@ func (p *Prog) validateWithOpts(opts validationOptions) error {
}
func (ctx *validCtx) validateCall(c *Call) error {
- if !ctx.opts.allowUnsafe && c.Meta.Attrs.Disabled {
+ if !ctx.isUnsafe && c.Meta.Attrs.Disabled {
return fmt.Errorf("use of a disabled call")
}
if c.Props.Rerun > 0 && c.Props.FailNth > 0 {
@@ -211,7 +212,7 @@ func (arg *DataArg) validate(ctx *validCtx, dir Dir) error {
typ.Name(), arg.Size(), typ.TypeSize)
}
case BufferFilename:
- if !ctx.opts.allowUnsafe && escapingFilename(string(arg.data)) {
+ if !ctx.isUnsafe && escapingFilename(string(arg.data)) {
return fmt.Errorf("escaping filename %q", arg.data)
}
}
@@ -286,11 +287,16 @@ func (arg *PointerArg) validate(ctx *validCtx, dir Dir) error {
}
} else {
maxMem := ctx.target.NumPages * ctx.target.PageSize
- size := arg.VmaSize
+ addr, size := arg.Address, arg.VmaSize
if size == 0 && arg.Res != nil {
size = arg.Res.Size()
}
- if arg.Address >= maxMem || arg.Address+size > maxMem {
+ if ctx.isUnsafe {
+ // Allow mapping 2 surrounding pages for DataMmapProg.
+ addr += ctx.target.PageSize
+ maxMem += 2 * ctx.target.PageSize
+ }
+ if addr >= maxMem || addr+size > maxMem {
return fmt.Errorf("ptr %v has bad address %v/%v/%v",
arg.Type().Name(), arg.Address, arg.VmaSize, size)
}