# Copyright 2017 syzkaller project authors. All rights reserved. # Use of this source code is governed by Apache 2 LICENSE that can be found in the LICENSE file. ''' This module contains container classes for holding struct, struct fields, and a global namespace for struct objects obtained from multiple header files. ''' import logging from headerlib.struct_walker import StructWalker class StructRepr(object): ''' This class is a container for a single struct type. `fr_list` is a list of all items inside the struct, along with type information. ''' def __init__(self, struct_name, fr_list, loglvl=logging.INFO): self.struct_name = struct_name self.fr_list = fr_list self.global_hierarchy = {} self._setuplogging(loglvl) def __str__(self): return self._output_syzkaller_fmt() def _setuplogging(self, loglvl): self.logger = logging.getLogger(self.__class__.__name__) formatter = logging.Formatter('DEBUG:%(name)s:%(message)s') sh = logging.StreamHandler() sh.setFormatter(formatter) sh.setLevel(loglvl) self.logger.addHandler(sh) self.logger.setLevel(loglvl) def _output_syzkaller_fmt(self): header = '%s {' % (self.struct_name) body = self.get_syzkaller_field_body()[:-1] footer = '}' return '\n'.join([header, body, footer]) def get_syzkaller_field_body(self): ''' Returns the metadata description for a struct field in syzkaller format. eg: "len intptr". In cases where more than one syzkaller type maps to a native type, return a string with possible syzkaller types seperated by '|'. ''' def _get_syzkaller_type(native_type): syzkaller_types = { 'size_t' : 'len|fileoff|intN', 'ssize_t' : 'len|intN', 'unsigned int' : 'len|fileoff|int32', 'int' : 'len|fileoff|flags|int32', 'long' : 'len|fileoff|flags|intN', 'unsigned long' : 'len|fileoff|flags|intN', 'unsigned long long': 'len|fileoff|intN', 'char*' : 'ptr[in|out, string]|ptr[in, filename]', 'char**' : 'ptr[in, [ptr[in|out, string]]]', 'void*' : 'ptr[in|out, string]|ptr[in|out, array]', 'void (*)()' : 'vma', 'uint64_t' : 'len|int64', 'int64_t' : 'len|int64', 'uint32_t' : 'len|int32', 'int32_t' : 'len|int32', 'uint16_t' : 'len|int16', 'int16_t' : 'len|int16', 'uint8_t' : 'len|int8', 'int8_t' : 'len|int8', } if '[' in native_type and ']' in native_type: return 'array' # If we have a pointer to a struct object elif 'struct ' in native_type: if '*' in native_type: return 'ptr|buffer|array' else: return native_type.split(' ')[-1] elif 'enum ' in native_type: return native_type.split(' ')[-1] # typedef types return syzkaller_types.get(native_type, native_type) body = '' rows = [] for field in self.fr_list: rows.append((field.field_identifier, _get_syzkaller_type(field.field_type), field.field_type)) maxcolwidth = lambda rows, x: max([len(row[x])+5 for row in rows]) col1_width = maxcolwidth(rows, 0) col2_width = maxcolwidth(rows, 1) for row in rows: body += ' '*10 + '%s%s#(%s)\n' % (row[0].ljust(col1_width), row[1].ljust(col2_width), row[2]) return body def get_fields(self): ''' Get a list of all fields in this struct. ''' return self.fr_list def set_global_hierarchy(self, global_hierarchy): ''' Set a reference to the global heirarchy of structs. This is useful when unrolling structs. ''' self.global_hierarchy = global_hierarchy class FieldRepr(object): ''' This class is a container for a single item in a struct. field_type refers to the type of the item. field_identifier refers to the name/label of the item. field_extra is any item specific metadata. In cases where the field_type refers to another struct (whose items we are aware of), field_extra points to its StructRepr instance. This is used for struct unrolling in cases where an instance of "struct B" is an item inside "struct A". ''' def __init__(self, field_type, field_identifier): self._field_type = field_type self._field_identifier = field_identifier self._field_extra = None @property def field_type(self): '''Retrieve the field type.''' return self._field_type @field_type.setter def field_type(self, field_type): self._field_type = field_type @property def field_identifier(self): '''Retrieve the field identifier.''' return self._field_identifier @field_identifier.setter def field_identifier(self, field_identifier): self._field_identifier = field_identifier @property def field_extra(self): '''Retrieve any field specific metadata object.''' return self._field_extra @field_extra.setter def field_extra(self, field_extra): self._field_extra = field_extra class GlobalHierarchy(dict): ''' This class is a global container for structs and their items across a list of header files. Each struct is stored key'd by the struct name, and represented by an instance of `StructRepr`. ''' def __init__(self, filenames, loglvl=logging.INFO, include_lines='', output_fmt=''): super(GlobalHierarchy, self).__init__() self.filenames = filenames self.include_lines = include_lines self.loglvl = loglvl self._setuplogging() if self.filenames: self.load_header_files() def __str__(self): return self._output_syzkaller_fmt() def _setuplogging(self): self.logger = logging.getLogger(self.__class__.__name__) formatter = logging.Formatter('DEBUG:%(name)s:%(message)s') sh = logging.StreamHandler() sh.setFormatter(formatter) sh.setLevel(self.loglvl) self.logger.addHandler(sh) self.logger.setLevel(self.loglvl) @staticmethod def _get_struct_name(struct_type): return struct_type.split()[-1] def _output_syzkaller_fmt(self): return '' def add_header_file(self, filename): '''Add a header file to the list of headers we are about to parse.''' self.filenames.append(filename) def load_header_files(self): ''' Parse the list of header files and generate StructRepr instances to represent each struct object. Maintain a global view of all structs. ''' self.logger.debug('load_header_files : %s', str(self.filenames)) struct_walker = StructWalker(filenames=self.filenames, include_lines=self.include_lines, loglvl=self.loglvl) local_hierarchy = struct_walker.generate_local_hierarchy() for struct_name in local_hierarchy: fr_list = [FieldRepr(i[0], i[1]) for i in local_hierarchy[struct_name]] sr = StructRepr(struct_name, fr_list, loglvl=self.loglvl) sr.set_global_hierarchy(self) self["struct %s" % (struct_name)] = sr for struct_name in self.keys(): sr = self[struct_name] for field in sr.get_fields(): # If the item is a struct object, we link it against an # instance of its corresponding `sr` if field.field_type in self: field.field_extra = self[field.field_type] def get_metadata_structs(self): ''' Generate metadata structs for all structs that this global namespace knows about. ''' metadata_structs = "" for struct_name in sorted(self.keys()): sr = self[struct_name] metadata_structs += str(sr) + "\n" return metadata_structs.strip()