File: fuzz.py

package info (click to toggle)
swiftlang 6.2.3-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 2,856,264 kB
  • sloc: cpp: 9,995,718; ansic: 2,234,019; asm: 1,092,167; python: 313,940; objc: 82,726; f90: 80,126; lisp: 38,373; pascal: 25,580; sh: 20,378; ml: 5,058; perl: 4,751; makefile: 4,725; awk: 3,535; javascript: 3,018; xml: 918; fortran: 664; cs: 573; ruby: 396
file content (156 lines) | stat: -rwxr-xr-x 4,910 bytes parent folder | download
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
#!/usr/bin/env python3

import argparse
import os
import subprocess


class CommandRunner:
    def __init__(self, verbose: bool = False, dry_run: bool = False):
        self.verbose = verbose
        self.dry_run = dry_run

    def run(self, args, **kwargs):
        if self.verbose or self.dry_run:
            print(' '.join(args))
        if self.dry_run:
            return
        return subprocess.run(args, **kwargs)


def available_libfuzzer_targets():
    import json
    # The list of available library products
    args = ['swift', 'package', 'dump-package']
    result = subprocess.run(args, stdout=subprocess.PIPE, check=True)
    package = result.stdout.decode('utf-8')
    package = json.loads(package)
    return [product['name'] for product in package['products']
            if 'library' in product['type']]


def main():
    parser = argparse.ArgumentParser(description='Build fuzzer')
    # Common options
    parser.add_argument(
        '-v', '--verbose', action='store_true', help='Print commands')
    parser.add_argument(
        '-n', '--dry-run', action='store_true',
        help='Print commands but do not execute them')

    # Subcommands
    subparsers = parser.add_subparsers(required=True)

    available_targets = available_libfuzzer_targets()

    build_parser = subparsers.add_parser('build', help='Build the fuzzer')
    build_parser.add_argument(
        'target_name', type=str, help='Name of the target', choices=available_targets)
    build_parser.add_argument(
        '--sanitizer', type=str, default='address')
    build_parser.set_defaults(func=build)

    run_parser = subparsers.add_parser('run', help='Run the fuzzer')
    run_parser.add_argument(
        'target_name', type=str, help='Name of the target', choices=available_targets)
    run_parser.add_argument(
        '--skip-build', action='store_true',
        help='Skip building the fuzzer')
    run_parser.add_argument(
        'args', nargs=argparse.REMAINDER,
        help='Arguments to pass to the fuzzer')
    run_parser.set_defaults(func=run)

    seed_parser = subparsers.add_parser(
        'seed', help='Generate seed corpus for the fuzzer')
    seed_parser.set_defaults(func=seed)

    args = parser.parse_args()
    runner = CommandRunner(verbose=args.verbose, dry_run=args.dry_run)
    args.func(args, runner)


def seed(args, runner):
    def generate_seed_corpus(output_path: str):
        args = [
            "wasm-tools", "smith", "-o", output_path
        ]
        # Random stdin input
        stdin = os.urandom(1024)
        process = subprocess.Popen(args, stdin=subprocess.PIPE)
        process.communicate(input=stdin)
        if process.returncode != 0:
            raise Exception(f"Failed to generate seed corpus: {output_path}")

    output_dir = ".build/fuzz-corpus"
    os.makedirs(output_dir, exist_ok=True)

    for i in range(100):
        output = f"{output_dir}/corpus-{i}.wasm"
        generate_seed_corpus(output)
        print(f"Generated seed corpus: {output}")


def executable_path(target_name: str) -> str:
    return f'./.build/debug/{target_name}'


def build(args, runner: CommandRunner):
    print(f'Building fuzzer for {args.target_name}')

    driver_flags = []
    if args.sanitizer == 'coverage':
        driver_flags += [
            '-profile-generate', '-profile-coverage-mapping',
            '-sanitize=fuzzer'
        ]
    else:
        driver_flags += [f'-sanitize=fuzzer,{args.sanitizer}']

    build_args = [
        'swift', 'build', '--product', args.target_name,
    ]
    for driver_flag in driver_flags:
        build_args += ['-Xswiftc', driver_flag]

    runner.run(build_args, check=True)

    print('Building fuzzer executable')
    # See "Discussion" in Package.swift for why we need to manually link
    # the library product.
    output = executable_path(args.target_name)
    link_args = [
        'swiftc', f'./.build/debug/lib{args.target_name}.a', '-g',
        # Link Swift runtime statically to allow copying fuzzers to other
        # machines (oss-fuzz does this)
        '-static-stdlib', '-o', output
    ]
    link_args += driver_flags
    runner.run(link_args, check=True)

    print('Fuzzer built successfully: ', output)


def run(args, runner: CommandRunner):

    if not args.skip_build:
        build(args, runner)

    print('Running fuzzer')

    artifact_dir = f'./FailCases/{args.target_name}/'
    os.makedirs(artifact_dir, exist_ok=True)
    fuzzer_args = [
        executable_path(args.target_name), './.build/fuzz-corpus',
        '-fork=2',
        '-timeout=5', '-ignore_timeouts=1',
        # Relax the RSS limit to 5GB (default is 4GB) to allow
        # allocating maximum memory for 32-bit space.
        '-rss_limit_mb=5368709120',
        f'-artifact_prefix={artifact_dir}'
    ] + args.args
    runner.run(fuzzer_args, env={'SWIFT_BACKTRACE': 'enable=off'})


if __name__ == '__main__':
    main()