1#!/usr/bin/env python3 2# 3# Copyright 2024 The Chromium Authors 4# Use of this source code is governed by a BSD-style license that can be 5# found in the LICENSE file. 6"""IDL to Fuzzilli profile generator. 7 8A Fuzzilli profile [1] describes how to fuzz a particular target. For example, 9it describes which builtins are available to JS running in that target, and all 10interesting functions and types that Fuzzilli would benefit from knowing. 11This helps the fuzzing engine generate interesting JS programs without having 12to discover how to e.g. create a DOM node through coverage-guided trial and 13error. 14 15This tool outputs a Swift file defining a profile for JS running in Chrome. The 16core of this is defining `ILType` and `ObjectGroup` variables describing all 17known types. See Fuzzilli's `TypeSystem.swift` [2] for more details. 18 19This output file is automatically derived from the contents of all WebIDL files 20known to Chrome. Those files describe all JS interfaces exposed to websites. 21The meat of this tool is converting from IDL types to Fuzzilli IL types. 22 23[1] https://github.com/googleprojectzero/fuzzilli/blob/main/Sources/FuzzilliCli\ 24/Profiles/Profile.swift 25[2] https://github.com/googleprojectzero/fuzzilli/blob/main/Sources/Fuzzilli/\ 26FuzzIL/TypeSystem.swift. 27""" 28 29from __future__ import annotations 30 31import argparse 32import functools 33import os 34import sys 35from typing import List, Optional, Dict, Tuple, Union, Sequence 36 37import dataclasses # Built-in, but pylint treats it as a third party module. 38 39 40def _GetDirAbove(dirname: str): 41 """Returns the directory "above" this file containing |dirname| (which must 42 also be "above" this file).""" 43 path = os.path.abspath(__file__) 44 while True: 45 path, tail = os.path.split(path) 46 if not tail: 47 return None 48 if tail == dirname: 49 return path 50 51 52SOURCE_DIR = _GetDirAbove('testing') 53 54sys.path.insert(1, os.path.join(SOURCE_DIR, 'third_party')) 55sys.path.append(os.path.join(SOURCE_DIR, 'build')) 56sys.path.append( 57 os.path.join(SOURCE_DIR, 'third_party/blink/renderer/bindings/scripts/')) 58 59import jinja2 60import web_idl 61 62 63class SwiftExpression: 64 """Generic type for representing a Swift type.""" 65 66 def fuzzilli_repr(self) -> str: 67 """Returns the Fuzzilli representation of this expression. 68 69 Returns: 70 the string representation of this expression. 71 """ 72 raise Exception('Not implemented.') 73 74 75class SwiftNil(SwiftExpression): 76 """Swift nil value.""" 77 78 def fuzzilli_repr(self) -> str: 79 return 'nil' 80 81 82@dataclasses.dataclass 83class StringLiteral(SwiftExpression): 84 """Represents a Swift string literal.""" 85 value: str 86 87 def fuzzilli_repr(self) -> str: 88 return f'"{self.value}"' 89 90 91@dataclasses.dataclass 92class LiteralList(SwiftExpression): 93 """Represents a literal Swift list. """ 94 values: List[SwiftExpression] 95 96 def fuzzilli_repr(self) -> str: 97 values = ', '.join([v.fuzzilli_repr() for v in self.values]) 98 return f'[{values}]' 99 100 101@dataclasses.dataclass 102class Add(SwiftExpression): 103 lhs: SwiftExpression 104 rhs: SwiftExpression 105 106 def fuzzilli_repr(self): 107 return f'{self.lhs.fuzzilli_repr()} + {self.rhs.fuzzilli_repr()}' 108 109 110@dataclasses.dataclass 111class Or(SwiftExpression): 112 lhs: SwiftExpression 113 rhs: SwiftExpression 114 115 def fuzzilli_repr(self): 116 return f'{self.lhs.fuzzilli_repr()} | {self.rhs.fuzzilli_repr()}' 117 118 119@dataclasses.dataclass 120class ILType(SwiftExpression): 121 """Represents the Fuzzilli 'ILType' Swift type.""" 122 property: str 123 args: Optional[List[SwiftExpression]] = None 124 kwargs: Optional[Dict[str, SwiftExpression]] = None 125 126 def fuzzilli_repr(self) -> str: 127 if self.args is None and self.kwargs is None: 128 return f'ILType.{self.property}' 129 arg_s = '' 130 if self.args: 131 arg_s += ', '.join([a.fuzzilli_repr() for a in self.args]) 132 if self.kwargs: 133 arg_s += ', '.join( 134 [f'{k}: {v.fuzzilli_repr()}' for k, v in self.kwargs.items()]) 135 return f'ILType.{self.property}({arg_s})' 136 137 @staticmethod 138 def nothing() -> ILType: 139 return ILType(property='nothing') 140 141 @staticmethod 142 def anything() -> ILType: 143 return ILType(property='anything') 144 145 @staticmethod 146 def undefined() -> ILType: 147 return ILType(property='undefined') 148 149 @staticmethod 150 def integer() -> ILType: 151 return ILType(property='integer') 152 153 @staticmethod 154 def float() -> ILType: 155 return ILType(property='float') 156 157 @staticmethod 158 def bigint() -> ILType: 159 return ILType(property='bigint') 160 161 @staticmethod 162 def boolean() -> ILType: 163 return ILType(property='boolean') 164 165 @staticmethod 166 def iterable() -> ILType: 167 return ILType(property='iterable') 168 169 @staticmethod 170 def string() -> ILType: 171 return ILType(property='string') 172 173 @staticmethod 174 def jsString() -> ILType: 175 return ILType(property='jsString') 176 177 @staticmethod 178 def jsPromise() -> ILType: 179 return ILType(property='jsPromise') 180 181 @staticmethod 182 def jsArrayBuffer() -> ILType: 183 return ILType(property='jsArrayBuffer') 184 185 @staticmethod 186 def jsSharedArrayBuffer() -> ILType: 187 return ILType(property='jsSharedArrayBuffer') 188 189 @staticmethod 190 def jsDataView() -> ILType: 191 return ILType(property='jsDataView') 192 193 @staticmethod 194 def jsMap() -> ILType: 195 return ILType(property='jsMap') 196 197 @staticmethod 198 def refType(name: str) -> ILType: 199 return ILType(property=name) 200 201 @staticmethod 202 def jsTypedArray(variant: str) -> ILType: 203 return ILType(property='jsTypedArray', 204 args=[StringLiteral(value=variant)], 205 kwargs=None) 206 207 @staticmethod 208 def function(signature: SignatureType) -> ILType: 209 return ILType(property='function', args=[signature], kwargs=None) 210 211 @staticmethod 212 def object(group: Optional[str] = None, 213 props: Optional[List[str]] = None, 214 methods: Optional[List[str]] = None) -> ILType: 215 if not group and not props and not methods: 216 return ILType(property='object', args=[]) 217 218 props_literals = [StringLiteral(value=prop) for prop in props] 219 methods_literals = [StringLiteral(value=method) for method in methods] 220 props_list = LiteralList(values=props_literals) 221 methods_list = LiteralList(values=methods_literals) 222 group_val = StringLiteral(value=group) if group else SwiftNil() 223 kwargs = { 224 'ofGroup': group_val, 225 'withProperties': props_list, 226 'withMethods': methods_list, 227 } 228 return ILType(property='object', kwargs=kwargs) 229 230 @staticmethod 231 def constructor(signature: SignatureType) -> ILType: 232 return ILType(property='constructor', args=[signature]) 233 234 def __add__(self, other: ILType): 235 return Add(self, other) 236 237 def __or__(self, other: ILType): 238 return Or(self, other) 239 240 241SIMPLE_TYPE_TO_ILTYPE = { 242 'void': ILType.undefined(), 243 'object': ILType.object(), 244 'undefined': ILType.undefined(), 245 'any': ILType.anything(), 246 'byte': ILType.integer(), 247 'octet': ILType.integer(), 248 'short': ILType.integer(), 249 'unsigned short': ILType.integer(), 250 'long': ILType.integer(), 251 'unsigned long': ILType.integer(), 252 'long long': ILType.integer(), 253 'unsigned long long': ILType.integer(), 254 'integer': ILType.integer(), 255 'float': ILType.float(), 256 'double': ILType.float(), 257 'unrestricted float': ILType.float(), 258 'unrestricted double': ILType.float(), 259 'bigint': ILType.bigint(), 260 'boolean': ILType.boolean(), 261 'DOMString': ILType.string(), 262 'ByteString': ILType.string(), 263 'USVString': ILType.string(), 264 'ArrayBuffer': ILType.jsArrayBuffer(), 265 'ArrayBufferView': ILType.jsDataView(), 266 'SharedArray': ILType.jsSharedArrayBuffer(), 267 'Int8Array': ILType.jsTypedArray('Int8Array'), 268 'Int16Array': ILType.jsTypedArray('Int16Array'), 269 'Int32Array': ILType.jsTypedArray('Int32Array'), 270 'Uint8Array': ILType.jsTypedArray('Uint8Array'), 271 'Uint16Array': ILType.jsTypedArray('Uint16Array'), 272 'Uint32Array': ILType.jsTypedArray('Uint32Array'), 273 'Uint8ClampedArray': ILType.jsTypedArray('Uint8ClampedArray'), 274 'BigInt64Array': ILType.jsTypedArray('BigInt64Array'), 275 'BigUint64Array': ILType.jsTypedArray('BigUint64Array'), 276 'Float16Array': ILType.jsTypedArray('Float16Array'), 277 'Float32Array': ILType.jsTypedArray('Float32Array'), 278 'Float64Array': ILType.jsTypedArray('Float64Array'), 279 'DataView': ILType.jsDataView(), 280} 281 282 283@dataclasses.dataclass 284class ParameterType(SwiftExpression): 285 """Represents the Fuzzilli 'Parameter' Swift type.""" 286 property: str 287 arg: ILType 288 289 def fuzzilli_repr(self) -> str: 290 return f'Parameter.{self.property}({self.arg.fuzzilli_repr()})' 291 292 @staticmethod 293 def opt(inner_type: ILType): 294 return ParameterType(property='opt', arg=inner_type) 295 296 @staticmethod 297 def plain(inner_type: ILType): 298 return ParameterType(property='plain', arg=inner_type) 299 300 @staticmethod 301 def rest(inner_type: ILType): 302 return ParameterType(property='rest', arg=inner_type) 303 304 305@dataclasses.dataclass 306class SignatureType(SwiftExpression): 307 """Represents the Fuzzilli 'Signature' Swift type.""" 308 args: List[ILType] 309 ret: ILType 310 311 def fuzzilli_repr(self): 312 args = self.args if self.args else [] 313 args_repr = LiteralList(values=args).fuzzilli_repr() 314 ret_repr = self.ret.fuzzilli_repr() 315 return f'Signature(expects: {args_repr}, returns: {ret_repr})' 316 317 318@dataclasses.dataclass() 319class ObjectGroup(SwiftExpression): 320 """Represents the Fuzzilli ObjectGroup swift object.""" 321 name: str 322 instanceType: ILType 323 properties: Dict[str, ILType] 324 methods: Dict[str, ILType] 325 326 327def idl_type_to_iltype(idl_type: web_idl.idl_type.IdlType) -> ILType: 328 """Converts a WebIDL type to a Fuzzilli ILType. 329 330 Args: 331 idl_type: the idl type to parse. 332 333 Raises: 334 whether a type was not handled, used for debugging purposes. 335 336 Returns: 337 the equivalent Fuzzilli ILType that was parsed. 338 """ 339 if isinstance(idl_type, web_idl.idl_type.SimpleType): 340 return SIMPLE_TYPE_TO_ILTYPE[idl_type.keyword_typename] 341 if isinstance(idl_type, web_idl.idl_type.ReferenceType): 342 return ILType.refType(f'js{idl_type.identifier}') 343 if isinstance(idl_type, web_idl.idl_type.UnionType): 344 members = [idl_type_to_iltype(t) for t in idl_type.member_types] 345 return functools.reduce(ILType.__or__, members) 346 if isinstance(idl_type, web_idl.idl_type.NullableType): 347 return idl_type_to_iltype(idl_type.inner_type) 348 if web_idl.idl_type.IsArrayLike(idl_type): 349 return ILType.iterable() 350 if isinstance(idl_type, web_idl.idl_type.PromiseType): 351 return ILType.jsPromise() 352 if isinstance(idl_type, web_idl.idl_type.RecordType): 353 return ILType.jsMap() 354 355 raise TypeError(f'Unhandled IdlType {repr(idl_type)}') 356 357 358def parse_args( 359 args: Sequence[web_idl.argument.Argument]) -> List[ParameterType]: 360 """Parse the list of arguments and returns a list of parameter types. 361 362 Args: 363 op: the operation 364 365 Returns: 366 the list of parameter types 367 """ 368 # In IDL constructor definitions, it is possible to have optional arguments 369 # before plain arguments, which doesn't really make sense in JS. 370 rev_args = [] 371 has_seen_plain = False 372 for arg in reversed(args): 373 if arg.is_optional: 374 if has_seen_plain: 375 rev_args.append(ParameterType.plain(idl_type_to_iltype(arg.idl_type))) 376 else: 377 rev_args.append(ParameterType.opt(idl_type_to_iltype(arg.idl_type))) 378 elif arg.is_variadic: 379 rev_args.append( 380 ParameterType.rest(idl_type_to_iltype(arg.idl_type.element_type))) 381 else: 382 has_seen_plain = True 383 rev_args.append(ParameterType.plain(idl_type_to_iltype(arg.idl_type))) 384 rev_args.reverse() 385 return rev_args 386 387 388def parse_operation( 389 op: Union[web_idl.operation.Operation, 390 web_idl.callback_function.CallbackFunction] 391) -> SignatureType: 392 """Parses an IDL 'operation', which is the method equivalent of a Javascript 393 object. 394 395 Args: 396 op: the operation to parse 397 398 Returns: 399 the signature of the operation 400 """ 401 ret = idl_type_to_iltype(op.return_type) 402 return SignatureType(args=parse_args(op.arguments), ret=ret) 403 404 405def parse_interface( 406 interface: Union[web_idl.interface.Interface, 407 web_idl.callback_interface.CallbackInterface] 408) -> Tuple[ILType, ObjectGroup]: 409 """Parses an IDL 'interface', which is a Javascript object. 410 411 Args: 412 interface: the interface to parse 413 414 Returns: 415 a tuple containing the ILType variable declaration and the associated 416 object group that defines the object properties and methods. 417 """ 418 attributes = { 419 a.identifier: idl_type_to_iltype(a.idl_type) 420 for a in interface.attributes if not a.is_static 421 } 422 methods = { 423 o.identifier: parse_operation(o) 424 for o in interface.operations if not o.is_static 425 } 426 427 obj = ILType.object(group=interface.identifier, 428 props=list(attributes.keys()), 429 methods=list(methods.keys())) 430 group = ObjectGroup(name=interface.identifier, 431 instanceType=ILType.refType(f'js{interface.identifier}'), 432 properties=attributes, 433 methods=methods) 434 return obj, group 435 436 437def parse_constructors( 438 interface: Union[web_idl.interface.Interface, 439 web_idl.callback_interface.CallbackInterface] 440) -> Tuple[Optional[ILType], Optional[ObjectGroup]]: 441 """Parses an IDL 'interface' static properties, methods and constructors. 442 This must be differentiated with `parse_interface`, because those are 443 actually two different objects in Javascript. 444 445 Args: 446 interface: the interface to parse. 447 448 Returns: 449 A var declaration and an object group, if any. 450 """ 451 attributes = { 452 a.identifier: idl_type_to_iltype(a.idl_type) 453 for a in interface.attributes if a.is_static 454 } 455 methods = { 456 o.identifier: parse_operation(o) 457 for o in interface.operations if o.is_static 458 } 459 460 typedecl = None 461 has_object = False 462 if attributes or methods: 463 has_object = True 464 typedecl = ILType.object(group=f'{interface.identifier}Constructor', 465 props=attributes.keys(), 466 methods=methods.keys()) 467 468 if interface.constructors: 469 # As of now, Fuzzilli cannot handle multiple constructors, because it 470 # doesn't make sense in Javascript. 471 c = interface.constructors[0] 472 args = parse_args(c.arguments) 473 ctor = ILType.constructor( 474 SignatureType(args=args, 475 ret=ILType.refType(f'js{interface.identifier}'))) 476 typedecl = ctor + typedecl if typedecl else ctor 477 478 if not typedecl: 479 return None, None 480 481 group = None 482 if has_object: 483 var_name = f'{interface.identifier}Constructor' 484 group = ObjectGroup(name=var_name, 485 instanceType=ILType.refType(f'js{var_name}'), 486 properties=attributes, 487 methods=methods) 488 return typedecl, group 489 490 491def parse_dictionary( 492 dictionary: web_idl.dictionary.Dictionary 493) -> Tuple[Optional[ILType], Optional[ObjectGroup]]: 494 """Parses an IDL dictionary. 495 496 Args: 497 dictionary: the IDL dictionary 498 499 Returns: 500 A tuple consisting of its ILType definition and its ObjectGroup 501 definition. 502 """ 503 props = { 504 m.identifier: idl_type_to_iltype(m.idl_type) 505 for m in dictionary.members 506 } 507 obj = ILType.object(group=f'{dictionary.identifier}', 508 props=list(props.keys()), 509 methods=[]) 510 group = ObjectGroup(name=f'{dictionary.identifier}', 511 instanceType=ILType.refType(f'js{dictionary.identifier}'), 512 properties=props, 513 methods={}) 514 return obj, group 515 516 517def main(): 518 parser = argparse.ArgumentParser( 519 description= 520 'Generates a Chrome Profile for Fuzzilli that describes WebIDLs.') 521 parser.add_argument('-p', 522 '--path', 523 required=True, 524 help='Path to the web_idl_database.') 525 parser.add_argument('-o', 526 '--outfile', 527 required=True, 528 help='Path to the output profile.') 529 530 args = parser.parse_args() 531 database = web_idl.Database.read_from_file(args.path) 532 533 template_dir = os.path.dirname(os.path.abspath(__file__)) 534 environment = jinja2.Environment(loader=jinja2.FileSystemLoader(template_dir)) 535 environment.filters['parse_interface'] = parse_interface 536 environment.filters['parse_constructors'] = parse_constructors 537 environment.filters['parse_operation'] = parse_operation 538 environment.filters['parse_dictionary'] = parse_dictionary 539 environment.filters['idl_type_to_iltype'] = idl_type_to_iltype 540 template = environment.get_template('ChromiumProfile.swift.tmpl') 541 context = { 542 'database': database, 543 } 544 with open(args.outfile, 'w') as f: 545 f.write(template.render(context)) 546 547 548if __name__ == '__main__': 549 main() 550