• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2017 syzkaller project authors. All rights reserved.
2// Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file.
3
4package compiler
5
6import (
7	"bufio"
8	"bytes"
9	"fmt"
10	"io/ioutil"
11	"path/filepath"
12	"sort"
13	"strconv"
14	"strings"
15
16	"github.com/google/syzkaller/pkg/ast"
17	"github.com/google/syzkaller/prog"
18	"github.com/google/syzkaller/sys/targets"
19)
20
21type ConstInfo struct {
22	Consts   []string
23	Includes []string
24	Incdirs  []string
25	Defines  map[string]string
26}
27
28func ExtractConsts(desc *ast.Description, target *targets.Target, eh ast.ErrorHandler) map[string]*ConstInfo {
29	res := Compile(desc, nil, target, eh)
30	if res == nil {
31		return nil
32	}
33	return res.fileConsts
34}
35
36// extractConsts returns list of literal constants and other info required for const value extraction.
37func (comp *compiler) extractConsts() map[string]*ConstInfo {
38	infos := make(map[string]*constInfo)
39	for _, decl := range comp.desc.Nodes {
40		pos, _, _ := decl.Info()
41		info := getConstInfo(infos, pos)
42		switch n := decl.(type) {
43		case *ast.Include:
44			info.includeArray = append(info.includeArray, n.File.Value)
45		case *ast.Incdir:
46			info.incdirArray = append(info.incdirArray, n.Dir.Value)
47		case *ast.Define:
48			v := fmt.Sprint(n.Value.Value)
49			switch {
50			case n.Value.CExpr != "":
51				v = n.Value.CExpr
52			case n.Value.Ident != "":
53				v = n.Value.Ident
54			}
55			name := n.Name.Name
56			info.defines[name] = v
57			info.consts[name] = true
58		case *ast.Call:
59			if comp.target.SyscallNumbers && !strings.HasPrefix(n.CallName, "syz_") {
60				info.consts[comp.target.SyscallPrefix+n.CallName] = true
61			}
62		}
63	}
64
65	for _, decl := range comp.desc.Nodes {
66		switch decl.(type) {
67		case *ast.Call, *ast.Struct, *ast.Resource, *ast.TypeDef:
68			comp.foreachType(decl, func(t *ast.Type, desc *typeDesc,
69				args []*ast.Type, _ prog.IntTypeCommon) {
70				for i, arg := range args {
71					if desc.Args[i].Type.Kind == kindInt {
72						if arg.Ident != "" {
73							info := getConstInfo(infos, arg.Pos)
74							info.consts[arg.Ident] = true
75						}
76						if arg.Ident2 != "" {
77							info := getConstInfo(infos, arg.Pos2)
78							info.consts[arg.Ident2] = true
79						}
80					}
81				}
82			})
83		}
84	}
85
86	for _, decl := range comp.desc.Nodes {
87		switch n := decl.(type) {
88		case *ast.Struct:
89			for _, attr := range n.Attrs {
90				if attr.Ident == "size" {
91					info := getConstInfo(infos, attr.Pos)
92					info.consts[attr.Args[0].Ident] = true
93				}
94			}
95		}
96	}
97
98	comp.desc.Walk(ast.Recursive(func(n0 ast.Node) {
99		if n, ok := n0.(*ast.Int); ok {
100			info := getConstInfo(infos, n.Pos)
101			info.consts[n.Ident] = true
102		}
103	}))
104
105	return convertConstInfo(infos)
106}
107
108type constInfo struct {
109	consts       map[string]bool
110	defines      map[string]string
111	includeArray []string
112	incdirArray  []string
113}
114
115func getConstInfo(infos map[string]*constInfo, pos ast.Pos) *constInfo {
116	info := infos[pos.File]
117	if info == nil {
118		info = &constInfo{
119			consts:  make(map[string]bool),
120			defines: make(map[string]string),
121		}
122		infos[pos.File] = info
123	}
124	return info
125}
126
127func convertConstInfo(infos map[string]*constInfo) map[string]*ConstInfo {
128	res := make(map[string]*ConstInfo)
129	for file, info := range infos {
130		res[file] = &ConstInfo{
131			Consts:   toArray(info.consts),
132			Includes: info.includeArray,
133			Incdirs:  info.incdirArray,
134			Defines:  info.defines,
135		}
136	}
137	return res
138}
139
140// assignSyscallNumbers assigns syscall numbers, discards unsupported syscalls.
141func (comp *compiler) assignSyscallNumbers(consts map[string]uint64) {
142	for _, decl := range comp.desc.Nodes {
143		c, ok := decl.(*ast.Call)
144		if !ok || strings.HasPrefix(c.CallName, "syz_") {
145			continue
146		}
147		str := comp.target.SyscallPrefix + c.CallName
148		nr, ok := consts[str]
149		if ok {
150			c.NR = nr
151			continue
152		}
153		c.NR = ^uint64(0) // mark as unused to not generate it
154		name := "syscall " + c.CallName
155		if !comp.unsupported[name] {
156			comp.unsupported[name] = true
157			comp.warning(c.Pos, "unsupported syscall: %v due to missing const %v",
158				c.CallName, str)
159		}
160	}
161}
162
163// patchConsts replaces all symbolic consts with their numeric values taken from consts map.
164// Updates desc and returns set of unsupported syscalls and flags.
165func (comp *compiler) patchConsts(consts map[string]uint64) {
166	for _, decl := range comp.desc.Nodes {
167		switch decl.(type) {
168		case *ast.IntFlags:
169			// Unsupported flag values are dropped.
170			n := decl.(*ast.IntFlags)
171			var values []*ast.Int
172			for _, v := range n.Values {
173				if comp.patchIntConst(&v.Value, &v.Ident, consts, nil) {
174					values = append(values, v)
175				}
176			}
177			n.Values = values
178		case *ast.Resource, *ast.Struct, *ast.Call, *ast.TypeDef:
179			// Walk whole tree and replace consts in Type's and Int's.
180			missing := ""
181			comp.foreachType(decl, func(_ *ast.Type, desc *typeDesc,
182				args []*ast.Type, _ prog.IntTypeCommon) {
183				for i, arg := range args {
184					if desc.Args[i].Type.Kind == kindInt {
185						comp.patchIntConst(&arg.Value, &arg.Ident, consts, &missing)
186						if arg.HasColon {
187							comp.patchIntConst(&arg.Value2,
188								&arg.Ident2, consts, &missing)
189						}
190					}
191				}
192			})
193			if n, ok := decl.(*ast.Resource); ok {
194				for _, v := range n.Values {
195					comp.patchIntConst(&v.Value, &v.Ident, consts, &missing)
196				}
197			}
198			if n, ok := decl.(*ast.Struct); ok {
199				for _, attr := range n.Attrs {
200					if attr.Ident == "size" {
201						sz := attr.Args[0]
202						comp.patchIntConst(&sz.Value, &sz.Ident, consts, &missing)
203					}
204				}
205			}
206			if missing == "" {
207				continue
208			}
209			// Produce a warning about unsupported syscall/resource/struct.
210			// TODO(dvyukov): we should transitively remove everything that
211			// depends on unsupported things.
212			// Potentially we still can get, say, a bad int range error
213			// due to the 0 const value.
214			pos, typ, name := decl.Info()
215			if id := typ + " " + name; !comp.unsupported[id] {
216				comp.unsupported[id] = true
217				comp.warning(pos, "unsupported %v: %v due to missing const %v",
218					typ, name, missing)
219			}
220			if c, ok := decl.(*ast.Call); ok {
221				c.NR = ^uint64(0) // mark as unused to not generate it
222			}
223		}
224	}
225}
226
227func (comp *compiler) patchIntConst(val *uint64, id *string, consts map[string]uint64, missing *string) bool {
228	if *id == "" {
229		return true
230	}
231	v, ok := consts[*id]
232	if !ok {
233		if missing != nil && *missing == "" {
234			*missing = *id
235		}
236	}
237	*val = v
238	return ok
239}
240
241func SerializeConsts(consts map[string]uint64, undeclared map[string]bool) []byte {
242	type nameValuePair struct {
243		declared bool
244		name     string
245		val      uint64
246	}
247	var nv []nameValuePair
248	for k, v := range consts {
249		nv = append(nv, nameValuePair{true, k, v})
250	}
251	for k := range undeclared {
252		nv = append(nv, nameValuePair{false, k, 0})
253	}
254	sort.Slice(nv, func(i, j int) bool {
255		return nv[i].name < nv[j].name
256	})
257
258	buf := new(bytes.Buffer)
259	fmt.Fprintf(buf, "# AUTOGENERATED FILE\n")
260	for _, x := range nv {
261		if x.declared {
262			fmt.Fprintf(buf, "%v = %v\n", x.name, x.val)
263		} else {
264			fmt.Fprintf(buf, "# %v is not set\n", x.name)
265		}
266	}
267	return buf.Bytes()
268}
269
270func DeserializeConsts(data []byte, file string, eh ast.ErrorHandler) map[string]uint64 {
271	consts := make(map[string]uint64)
272	pos := ast.Pos{
273		File: file,
274		Line: 1,
275	}
276	ok := true
277	s := bufio.NewScanner(bytes.NewReader(data))
278	for ; s.Scan(); pos.Line++ {
279		line := s.Text()
280		if line == "" || line[0] == '#' {
281			continue
282		}
283		eq := strings.IndexByte(line, '=')
284		if eq == -1 {
285			eh(pos, "expect '='")
286			ok = false
287			continue
288		}
289		name := strings.TrimSpace(line[:eq])
290		val, err := strconv.ParseUint(strings.TrimSpace(line[eq+1:]), 0, 64)
291		if err != nil {
292			eh(pos, fmt.Sprintf("failed to parse int: %v", err))
293			ok = false
294			continue
295		}
296		if _, dup := consts[name]; dup {
297			eh(pos, fmt.Sprintf("duplicate const %q", name))
298			ok = false
299			continue
300		}
301		consts[name] = val
302	}
303	if err := s.Err(); err != nil {
304		eh(pos, fmt.Sprintf("failed to parse: %v", err))
305		ok = false
306	}
307	if !ok {
308		return nil
309	}
310	return consts
311}
312
313func DeserializeConstsGlob(glob string, eh ast.ErrorHandler) map[string]uint64 {
314	if eh == nil {
315		eh = ast.LoggingHandler
316	}
317	files, err := filepath.Glob(glob)
318	if err != nil {
319		eh(ast.Pos{}, fmt.Sprintf("failed to find const files: %v", err))
320		return nil
321	}
322	if len(files) == 0 {
323		eh(ast.Pos{}, fmt.Sprintf("no const files matched by glob %q", glob))
324		return nil
325	}
326	consts := make(map[string]uint64)
327	for _, f := range files {
328		data, err := ioutil.ReadFile(f)
329		if err != nil {
330			eh(ast.Pos{}, fmt.Sprintf("failed to read const file: %v", err))
331			return nil
332		}
333		consts1 := DeserializeConsts(data, filepath.Base(f), eh)
334		if consts1 == nil {
335			consts = nil
336		}
337		if consts != nil {
338			for n, v := range consts1 {
339				if old, ok := consts[n]; ok && old != v {
340					eh(ast.Pos{}, fmt.Sprintf(
341						"different values for const %q: %v vs %v", n, v, old))
342					return nil
343				}
344				consts[n] = v
345			}
346		}
347	}
348	return consts
349}
350