• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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