diff --git a/TBInit.py b/TBInit.py index bec015d..e14098f 100644 --- a/TBInit.py +++ b/TBInit.py @@ -1,7 +1,7 @@ from utils import Colors, init_interpret from OpCodes import WASM_OP_Code -from section_structs import Code_Section, Func_Body from execute import * +from section_structs import Code_Section, Func_Body, External_Kind, Global_Kind # handles the debug option --memdump. dumps the contents of linear memories. @@ -183,13 +183,40 @@ def __init__(self, module): self.module = module def TypeSection(self): - pass + section = self.module.type_section + if section is None: + return(True) + for func_sig in section.func_types: + if func_sig.form != -0x20 or func_sig.return_cnt > 1: + return(False) + return True def ImportSection(self): - pass + section = self.module.import_section + if section is None: + return(True) + for entry in section.import_entry: + if entry.kind == External_Kind.FUNCTION: + pass + if entry.kind == External_Kind.TABLE: + pass + if entry.kind == External_Kind.MEMORY: + pass + if entry.kind == External_Kind.GLOBAL: + if entry.mutability != Global_Kind.IMMUTABLE: + return(False) + return(True) def FunctionSection(self): - pass + section = self.module.function_section + if section is None: + return(True) + csection = self.module.code_section + if csection is None: + return(False) + if len(section.type_section_index) != len(csection.func_bodies): + return(False) + return(True) def TableSection(self): pass @@ -198,13 +225,69 @@ def MemorySection(self): pass def GlobalSection(self): - pass + section = self.module.global_section + if section is None: + return(True) + for entry in section.global_variables: + desc_type = entry.global_type.content_type + init_expr = entry.init_expr + #get_global + if init_expr[0] == 0x23: + index = init_expr[1] + glob = self.module.global_index_space[index] + if desc_type != glob.content_type: + return(False) + #const + elif init_expr[0] == 0x41: + if desc_type != 0x7f: + return(False) + elif init_expr[0] == 0x42: + if desc_type != 0x7e: + return(False) + elif init_expr[0] == 0x43: + if desc_type != 0x7d: + return(False) + elif init_expr[0] == 0x44: + if desc_type != 0x7c: + return(False) + return(True) - def ExportSection(self): - pass + def ExportSection(self, machinestate): + section = self.module.export_section + if section is None: + return(True) + name_list = {} + for entry in section.export_entries: + name = ''.join(chr(c) for c in entry.field_str) + if name in name_list: + return(False) + name_list[name]=None + + index = entry.index + if entry.kind == External_Kind.FUNCTION: + if index >= len(machinestate.Index_Space_Function): + return(False) + if entry.kind == External_Kind.TABLE: + if index >= len(machinestate.Index_Space_Table): + return(False) + if entry.kind == External_Kind.MEMORY: + if index >= len(machinestate.Index_Space_Linear): + return(False) + if entry.kind == External_Kind.GLOBAL: + if index >= len(machinestate.Index_Space_Global): + return(False) + return(True) def StartSection(self): - pass + section = self.module.start_section + if section is None: + return(True) + index = section.function_section_index[0] + csection = self.module.CodeSection + if index >= len(csection.func_bodies): + return(False) + #func = self.module.function_index_space[index] + return(True) def ElementSection(self): pass @@ -219,14 +302,24 @@ def TBCustom(self): pass def ValidateAll(self): - self.TypeSection() - self.ImportSection() - self.FunctionSection() + machinestate = TBMachine() + init = TBInit(self.module, machinestate) + init.run() + machinestate = init.getInits() + if not self.TypeSection(): + return(False) + if not self.ImportSection(): + return(False) + if not self.FunctionSection(): + return(False) self.TableSection() self.MemorySection() - self.GlobalSection() - self.ExportSection() - self.StartSection() + if not self.GlobalSection(): + return(False) + if not self.ExportSection(machinestate): + return(False) + if not self.StartSection(): + return(False) self.ElementSection() self.CodeSection() self.DataSection() diff --git a/argparser.py b/argparser.py index a07bbb2..9de576a 100644 --- a/argparser.py +++ b/argparser.py @@ -605,6 +605,7 @@ def ReadCodeSection(self): if whatever[0] == 10: code_section = whatever.copy() section_exists = True + break if not section_exists: return None @@ -677,6 +678,7 @@ def ReadDataSection(self): if whatever[0] == 11: data_section = whatever.copy() section_exists = True + break if not section_exists: return None @@ -751,11 +753,11 @@ def ReadImportSection(self): temp_import_entry.kind = kind # function type - if kind == 0: + if kind == External_Kind.FUNCTION: import_type, offset, dummy = Read(import_section[6], offset, 'varuint32') temp_import_entry.type = import_type # table type - elif kind == 1: + elif kind == External_Kind.TABLE: table_type = Table_Type() table_type.elemet_type, offset, dummy = Read(import_section[6], offset, 'varint7') rsz_limits = Resizable_Limits() @@ -765,7 +767,7 @@ def ReadImportSection(self): rsz_limits.maximum, offset, dummy = Read(import_section[6], offset, 'varuint32') table_type.limit = rsz_limits temp_import_entry.type = table_type - elif kind == 2: + elif kind == External_Kind.MEMORY: memory_type = Memory_Type() rsz_limits = Resizable_Limits() rsz_limits.flags, offset, dummy = Read(import_section[6], offset, 'varuint1') @@ -774,7 +776,7 @@ def ReadImportSection(self): rsz_limits.maximum, offset, dummy = Read(import_section[6], offset, 'varuint32') memory_type.limits = rsz_limits temp_import_entry.type = memory_type - elif kind == 3: + elif kind == External_Kind.GLOBAL: global_type = Global_Type() global_type.content_type, offset, dummy = Read(import_section[6], offset, 'uint8') global_type.mutability, offset, dummy = Read(import_section[6], offset, 'varuint1') @@ -798,6 +800,7 @@ def ReadExportSection(self): if whatever[0] == 7: export_section = whatever.copy() section_exists = True + break if not section_exists: return None @@ -811,7 +814,7 @@ def ReadExportSection(self): for i in range(0, field_length): field_name.append(export_section[6][offset + i]) - temp_export_entry.fiels_str = field_name + temp_export_entry.field_str = field_name offset += field_length kind, offset, dummy = Read(export_section[6], offset, 'uint8') @@ -838,6 +841,7 @@ def ReadTypeSection(self): if whatever[0] == 1: type_section = whatever.copy() section_exists = True + break if not section_exists: return None @@ -883,6 +887,7 @@ def ReadFunctionSection(self): if whatever[0] == 3: function_section = whatever.copy() section_exists = True + break if not section_exists: return None @@ -911,6 +916,7 @@ def ReadElementSection(self): if whatever[0] == 9: element_section = whatever.copy() section_exists = True + break if not section_exists: return None @@ -958,6 +964,7 @@ def ReadMemorySection(self): if whatever[0] == 5: memory_section = whatever.copy() section_exists = True + break if not section_exists: return None @@ -1033,6 +1040,7 @@ def ReadGlobalSection(self): if whatever[0] == 6: global_section = whatever.copy() section_exists = True + break if not section_exists: return None @@ -1058,7 +1066,6 @@ def ReadGlobalSection(self): temp_gl_var.global_type = temp_gl_type GS.global_variables.append(deepcopy(temp_gl_var)) - count -= 1 loop = True init_expr = [] @@ -1074,6 +1081,7 @@ def ReadStartSection(self): if whatever[0] == 8: start_section = whatever.copy() section_exists = True + break if not section_exists: return None @@ -1288,7 +1296,7 @@ def dump_sections(self, module): # palceholder for the validation tests def runValidations(self): - modulevalidation = ModuleValidation(self.modules[0]) + modulevalidation = ModuleValidation(self.modules[-1]) return(modulevalidation.ValidateAll()) diff --git a/section_structs.py b/section_structs.py index ef251c3..f48b60f 100644 --- a/section_structs.py +++ b/section_structs.py @@ -28,11 +28,15 @@ def __init__(self): class External_Kind(): - def __init__(self): - self.Function = 0 - self.Table = 1 - self.Memory = 2 - self.Global = 3 + FUNCTION = 0 + TABLE = 1 + MEMORY = 2 + GLOBAL = 3 + + +class Global_Kind(): + IMMUTABLE = 0 + MUTABLE = 1 class Memory_Type(): @@ -224,6 +228,11 @@ class Module(): def __init__(self, type_section, import_section, function_section, table_section, memory_section, global_section, export_section, start_section, element_section, code_section, data_section): + self.function_index_space = [] + self.global_index_space = [] + self.memory_index_space = [] + self.table_index_space = [] + self.type_section = type_section self.import_section = import_section self.function_section = function_section @@ -235,3 +244,30 @@ def __init__(self, type_section, import_section, function_section, self.element_section = element_section self.code_section = code_section self.data_section = data_section + + if self.import_section is not None: + for entry in self.import_section.import_entry: + if entry.kind == External_Kind.FUNCTION: + self.function_index_space.append(entry) + if entry.kind == External_Kind.TABLE: + self.table_index_space.append(entry) + if entry.kind == External_Kind.MEMORY: + self.memory_index_space.append(entry) + if entry.kind == External_Kind.GLOBAL: + self.global_index_space.append(entry) + + if self.function_section is not None: + for entry in self.function_section.type_section_index: + self.function_index_space.append(entry) + + if self.table_section is not None: + for entry in self.table_section.table_types: + self.table_index_space.append(entry) + + if self.memory_section is not None: + for entry in self.memory_section.memory_types: + self.memory_index_space.append(entry) + + if self.global_section is not None: + for entry in self.global_section.global_variables: + self.global_index_space.append(entry) diff --git a/test/sectionvalidation.py b/test/sectionvalidation.py new file mode 100644 index 0000000..a1c87b5 --- /dev/null +++ b/test/sectionvalidation.py @@ -0,0 +1,132 @@ +import sys +import os +sys.path.append('../') +from utils import Colors +from argparser import * +from TBInit import * + +success = Colors.green + "SUCCESS: " + Colors.ENDC +fail = Colors.red + "FAIL: " + Colors.ENDC + +def ObjectList(directory): + obj_list = [] + cwd = os.getcwd() + + for file in os.listdir(cwd + directory): + if file.endswith(".wasm"): + obj_list.append(cwd + directory + "/" + file) + + return(obj_list) + +def section_validation_fail(directory): + return_list = [] + obj_list = ObjectList(directory) + for testfile in obj_list: + pid = os.fork() + # I dont have a bellybutton + if pid == 0: + # @DEVI-FIXME-pipe stdout and stderr to a file instead of the + # bitbucket + sys.stdout = open('/dev/null', 'w') + sys.stderr = open('/dev/null', 'w') + + interpreter = PythonInterpreter() + module = interpreter.parse(testfile) + interpreter.appendmodule(module) + interpreter.dump_sections(module) + modulevalidation = ModuleValidation(module) + if "type" in directory: + if not modulevalidation.TypeSection(): + sys.exit(1) + if "import" in directory: + if not modulevalidation.ImportSection(): + sys.exit(1) + if "function" in directory: + if not modulevalidation.FunctionSection(): + sys.exit(1) + if "table" in directory: + if not modulevalidation.TableSection(): + sys.exit(1) + if "memory" in directory: + if not modulevalidation.MemorySection(): + sys.exit(1) + if "global" in directory: + if not modulevalidation.GlobalSection(): + sys.exit(1) + if "export" in directory: + if not modulevalidation.ExportSection(): + sys.exit(1) + if "start" in directory: + if not modulevalidation.StartSection(): + sys.exit(1) + if "element" in directory: + if not modulevalidation.ElementSection(): + sys.exit(1) + if "code" in directory: + if not modulevalidation.CodeSection(): + sys.exit(1) + if "data" in directory: + if not modulevalidation.DataSection(): + sys.exit(1) + sys.exit() + # the parent process + elif pid > 0: + # @DEVI-FIXME-we are intentionally blocking. later i will fix this + # so we can use multicores to run our reg tests faster. + cpid, status = os.waitpid(pid, 0) + return_list.append(status) + if status != 0: + print(success + testfile) + else: + print(fail + testfile) + else: + # basically we couldnt fork a child + print(fail + 'return code:' + pid) + raise Exception("could not fork child") + + +def section_validation(): + return_list = [] + obj_list = ObjectList("/testsuite") + for testfile in obj_list: + pid = os.fork() + # I dont have a bellybutton + if pid == 0: + # @DEVI-FIXME-pipe stdout and stderr to a file instead of the + # bitbucket + sys.stdout = open('/dev/null', 'w') + sys.stderr = open('/dev/null', 'w') + + interpreter = PythonInterpreter() + module = interpreter.parse(testfile) + interpreter.appendmodule(module) + interpreter.dump_sections(module) + if not interpreter.runValidations(): + sys.exit(1) + vm = VM(interpreter.getmodules()) + ms = vm.getState() + # interpreter.dump_sections(module) + DumpIndexSpaces(ms) + DumpLinearMems(ms.Linear_Memory, 1000) + sys.exit() + # the parent process + elif pid > 0: + # @DEVI-FIXME-we are intentionally blocking. later i will fix this + # so we can use multicores to run our reg tests faster. + cpid, status = os.waitpid(pid, 0) + return_list.append(status) + if status == 0: + print(success + testfile) + else: + print(fail + testfile) + else: + # basically we couldnt fork a child + print(fail + 'return code:' + pid) + raise Exception("could not fork child") + +def main(): + sectionvalidation(False) + sectionvalidation(True) + +if __name__ == '__main__': + main() diff --git a/test/test.py b/test/test.py index 1a5546f..efbc836 100755 --- a/test/test.py +++ b/test/test.py @@ -10,6 +10,7 @@ from test_LEB128 import test_unsigned_LEB128 from leb128s import leb128sencodedecodeexhaustive from leb128s import leb128uencodedecodeexhaustive +from sectionvalidation import * from abc import ABCMeta, abstractmethod sys.path.append('../') from utils import Colors @@ -61,16 +62,6 @@ def Spwn(self): print(fail + 'return code:' + pid) raise Exception("could not fork child") - -def ObjectList(): - obj_list = [] - cwd = os.getcwd() - for file in os.listdir(cwd + "/testsuite"): - if file.endswith(".wasm"): - obj_list.append(cwd + "/testsuite/" + file) - - return(obj_list) - ################################################################################ class LEB128EncodeTest(Void_Spwner): def Legacy(self): @@ -88,52 +79,37 @@ def Legacy(self): def GetName(self): return('leb128exhaustive') +class SectionValidationTest(Void_Spwner): + def Legacy(self): + fail_dir = "/testsuite_fail/" + section_validation() + section_validation_fail(fail_dir + "type") + section_validation_fail(fail_dir + "import") + section_validation_fail(fail_dir + "function") + section_validation_fail(fail_dir + "table") + section_validation_fail(fail_dir + "memory") + section_validation_fail(fail_dir + "global") + section_validation_fail(fail_dir + "export") + section_validation_fail(fail_dir + "start") + section_validation_fail(fail_dir + "element") + section_validation_fail(fail_dir + "code") + section_validation_fail(fail_dir + "data") + + def GetName(self): + return('sectionvalidationtest') + ################################################################################ def main(): - return_list = [] # LEB128 tests leb128encodetest = LEB128EncodeTest() leb128encodetest.Spwn() # leb128s exhaustive leb128sex = LEB128Exhaustive() leb128sex.Spwn() - # parser test on the WASM testsuite - obj_list = ObjectList() - for testfile in obj_list: - pid = os.fork() - # I dont have a bellybutton - if pid == 0: - # @DEVI-FIXME-pipe stdout and stderr to a file instead of the - # bitbucket - sys.stdout = open('/dev/null', 'w') - sys.stderr = open('/dev/null', 'w') - - interpreter = PythonInterpreter() - module = interpreter.parse(testfile) - interpreter.appendmodule(module) - interpreter.dump_sections(module) - interpreter.runValidations() - vm = VM(interpreter.getmodules()) - ms = vm.getState() - # interpreter.dump_sections(module) - DumpIndexSpaces(ms) - DumpLinearMems(ms.Linear_Memory, 1000) - sys.exit() - # the parent process - elif pid > 0: - # @DEVI-FIXME-we are intentionally blocking. later i will fix this - # so we can use multicores to run our reg tests faster. - cpid, status = os.waitpid(pid, 0) - return_list.append(status) - if status == 0: - print(success + testfile) - else: - print(fail + testfile) - else: - # basically we couldnt fork a child - print(fail + 'return code:' + pid) - raise Exception("could not fork child") + # parser test on the WASM testsuite + sectionvalidation = SectionValidationTest() + sectionvalidation.Spwn() if __name__ == '__main__': main() diff --git a/test/testsuite_fail/README.md b/test/testsuite_fail/README.md new file mode 100644 index 0000000..3bf4fd5 --- /dev/null +++ b/test/testsuite_fail/README.md @@ -0,0 +1 @@ +This directory will contain invalid wasm files aimed at testing section validation and that they fail in a correct manner. diff --git a/test/testsuite_fail/global/incompatible_init_desc_type.wasm b/test/testsuite_fail/global/incompatible_init_desc_type.wasm new file mode 100644 index 0000000..f37b912 Binary files /dev/null and b/test/testsuite_fail/global/incompatible_init_desc_type.wasm differ diff --git a/test/testsuite_fail/type/invalid_form.wasm b/test/testsuite_fail/type/invalid_form.wasm new file mode 100644 index 0000000..56ebe0d Binary files /dev/null and b/test/testsuite_fail/type/invalid_form.wasm differ diff --git a/test/testsuite_fail/type/invalid_retcount.wasm b/test/testsuite_fail/type/invalid_retcount.wasm new file mode 100644 index 0000000..c31bbac Binary files /dev/null and b/test/testsuite_fail/type/invalid_retcount.wasm differ diff --git a/utils.py b/utils.py index cbbde25..254a978 100644 --- a/utils.py +++ b/utils.py @@ -153,8 +153,8 @@ def Read(section_byte, offset, kind): # we have read the lasy byte of the operand break - return_list = LEB128SignedDecode(operand) - operand = [] + return_list = LEB128SignedDecode(operand) + operand = [] return return_list, offset, read_bytes