1# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
2# vim: set filetype=python:
3# This Source Code Form is subject to the terms of the Mozilla Public
4# License, v. 2.0. If a copy of the MPL was not distributed with this
5# file, You can obtain one at http://mozilla.org/MPL/2.0/.
6
7from __future__ import print_function
8
9from copy import deepcopy
10from six import iteritems
11from struct import unpack
12import os
13from uuid import UUID
14
15H_HEADER = """/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
16/* vim: set ts=8 sts=2 et sw=2 tw=80: */
17/* This file was auto-generated from {0} by gen_dll_blocklist_data.py.  */
18
19#ifndef mozilla_{1}_h
20#define mozilla_{1}_h
21
22"""
23
24H_FOOTER = """#endif  // mozilla_{1}_h
25
26"""
27
28H_DEFS_BEGIN_DEFAULT = """#include "mozilla/WindowsDllBlocklistCommon.h"
29
30DLL_BLOCKLIST_DEFINITIONS_BEGIN
31
32"""
33
34H_DEFS_END_DEFAULT = """
35DLL_BLOCKLIST_DEFINITIONS_END
36
37"""
38
39H_BEGIN_LSP = """#include <guiddef.h>
40
41static const GUID gLayerGuids[] = {
42
43"""
44
45H_END_LSP = """
46};
47
48"""
49
50H_BEGIN_A11Y = """#include "mozilla/WindowsDllBlocklistCommon.h"
51
52DLL_BLOCKLIST_DEFINITIONS_BEGIN_NAMED(gBlockedInprocDlls)
53
54"""
55
56# These flag names should match the ones defined in WindowsDllBlocklistCommon.h
57FLAGS_DEFAULT = "FLAGS_DEFAULT"
58BLOCK_WIN8_AND_OLDER = "BLOCK_WIN8_AND_OLDER"
59BLOCK_WIN7_AND_OLDER = "BLOCK_WIN7_AND_OLDER"
60USE_TIMESTAMP = "USE_TIMESTAMP"
61CHILD_PROCESSES_ONLY = "CHILD_PROCESSES_ONLY"
62BROWSER_PROCESS_ONLY = "BROWSER_PROCESS_ONLY"
63SUBSTITUTE_LSP_PASSTHROUGH = "SUBSTITUTE_LSP_PASSTHROUGH"
64REDIRECT_TO_NOOP_ENTRYPOINT = "REDIRECT_TO_NOOP_ENTRYPOINT"
65
66# Only these flags are available in the input script
67INPUT_ONLY_FLAGS = {
68    BLOCK_WIN8_AND_OLDER,
69    BLOCK_WIN7_AND_OLDER,
70}
71
72
73def FILTER_ALLOW_ALL(entry):
74    # A11y entries are special, so we always exclude those by default
75    # (so it's not really allowing 'all', but it is simpler to reason about by
76    #  pretending that it is allowing all.)
77    return not isinstance(entry, A11yBlocklistEntry)
78
79
80def FILTER_DENY_ALL(entry):
81    return False
82
83
84def FILTER_ALLOW_ONLY_A11Y(entry):
85    return isinstance(entry, A11yBlocklistEntry)
86
87
88def FILTER_ALLOW_ONLY_LSP(entry):
89    return isinstance(entry, LspBlocklistEntry)
90
91
92def FILTER_TESTS_ONLY(entry):
93    return not isinstance(entry, A11yBlocklistEntry) and entry.is_test()
94
95
96def derive_test_key(key):
97    return key + "_TESTS"
98
99
100ALL_DEFINITION_LISTS = ("ALL_PROCESSES", "BROWSER_PROCESS", "CHILD_PROCESSES")
101
102
103class BlocklistDescriptor(object):
104    """This class encapsulates every file that is output from this script.
105    Each instance has a name, an "input specification", and optional "flag
106    specification" and "output specification" entries.
107    """
108
109    DEFAULT_OUTSPEC = {
110        "mode": "",
111        "filter": FILTER_ALLOW_ALL,
112        "begin": H_DEFS_BEGIN_DEFAULT,
113        "end": H_DEFS_END_DEFAULT,
114    }
115
116    FILE_NAME_TPL = "WindowsDllBlocklist{0}Defs"
117
118    OutputDir = None
119    ExistingFd = None
120    ExistingFdLeafName = None
121
122    def __init__(self, name, inspec, **kwargs):
123        """Positional arguments:
124
125        name -- String containing the name of the output list.
126
127        inspec -- One or more members of ALL_DEFINITION_LISTS. The input used
128        for this blocklist file is the union of all lists specified by this
129        variable.
130
131        Keyword arguments:
132
133        outspec -- an optional list of dicts that specify how the lists output
134        will be written out to a file. Each dict may contain the following keys:
135
136            'mode' -- a string that specifies a mode that is used when writing
137            out list entries to this particular output. This is passed in as the
138            mode argument to DllBlocklistEntry's write method.
139
140            'filter' -- a function that, given a blocklist entry, decides
141            whether or not that entry shall be included in this output file.
142
143            'begin' -- a string that is written to the output file after writing
144            the file's header, but prior to writing out any blocklist entries.
145
146            'end' -- a string that is written to the output file after writing
147            out any blocklist entries but before the file's footer.
148
149        Any unspecified keys will be assigned default values.
150
151        flagspec -- an optional dict whose keys consist of one or more of the
152        list names from inspec. Each corresponding value is a set of flags that
153        should be applied to each entry from that list. For example, the
154        flagspec:
155
156            {'CHILD_PROCESSES': {CHILD_PROCESSES_ONLY}}
157
158        causes any blocklist entries from the CHILD_PROCESSES list to be output
159        with the CHILD_PROCESSES_ONLY flag set.
160
161        """
162
163        self._name = name
164
165        # inspec's elements must all come from ALL_DEFINITION_LISTS
166        assert not (set(inspec).difference(set(ALL_DEFINITION_LISTS)))
167
168        # Internally to the descriptor, we store input specifications as a dict
169        # that maps each input blocklist name to the set of flags to be applied
170        # to each entry in that blocklist.
171        self._inspec = {blocklist: set() for blocklist in inspec}
172
173        self._outspecs = kwargs.get("outspec", BlocklistDescriptor.DEFAULT_OUTSPEC)
174        if isinstance(self._outspecs, dict):
175            # _outspecs should always be stored as a list of dicts
176            self._outspecs = [self._outspecs]
177
178        flagspecs = kwargs.get("flagspec", dict())
179        # flagspec's keys must all come from ALL_DEFINITION_LISTS
180        assert not (set(flagspecs.keys()).difference(set(self._inspec.keys())))
181
182        # Merge the flags from flagspec into _inspec's sets
183        for blocklist, flagspec in iteritems(flagspecs):
184            spec = self._inspec[blocklist]
185            if not isinstance(spec, set):
186                raise TypeError("Flag spec for list %s must be a set!" % blocklist)
187            spec.update(flagspec)
188
189    @staticmethod
190    def set_output_fd(fd):
191        """The build system has already provided an open file descriptor for
192        one of our outputs. We save that here so that we may use that fd once
193        we're ready to process that fd's file. We also obtain the output dir for
194        use as the base directory for the other output files that we open.
195        """
196        BlocklistDescriptor.ExistingFd = fd
197        (
198            BlocklistDescriptor.OutputDir,
199            BlocklistDescriptor.ExistingFdLeafName,
200        ) = os.path.split(fd.name)
201
202    @staticmethod
203    def ensure_no_dupes(defs):
204        """Ensure that defs does not contain any dupes. We raise a ValueError
205        because this is a bug in the input and requires developer intervention.
206        """
207        seen = set()
208        for e in defs:
209            name = e.get_name()
210            if name not in seen:
211                seen.add(name)
212            else:
213                raise ValueError("Duplicate entry found: %s" % name)
214
215    @staticmethod
216    def get_test_entries(exec_env, blocklist):
217        """Obtains all test entries for the corresponding blocklist, and also
218        ensures that each entry has its test flag set.
219
220        Positional arguments:
221
222        exec_env -- dict containing the globals that were passed to exec to
223        when the input script was run.
224
225        blocklist -- The name of the blocklist to obtain tests from. This
226        should be one of the members of ALL_DEFINITION_LISTS
227        """
228        test_key = derive_test_key(blocklist)
229
230        def set_is_test(elem):
231            elem.set_is_test()
232            return elem
233
234        return map(set_is_test, exec_env[test_key])
235
236    def gen_list(self, exec_env, filter_func):
237        """Generates a sorted list of blocklist entries from the input script,
238        filtered via filter_func.
239
240        Positional arguments:
241
242        exec_env -- dict containing the globals that were passed to exec to
243        when the input script was run. This function expects exec_env to
244        contain lists of blocklist entries, keyed using one of the members of
245        ALL_DEFINITION_LISTS.
246
247        filter_func -- a filter function that evaluates each blocklist entry
248        to determine whether or not it should be included in the results.
249        """
250
251        # This list contains all the entries across all blocklists that we
252        # potentially want to process
253        unified_list = []
254
255        # For each blocklist specified in the _inspec, we query the globals
256        # for their entries, add any flags, and then add them to the
257        # unified_list.
258        for blocklist, listflags in iteritems(self._inspec):
259
260            def add_list_flags(elem):
261                # We deep copy so that flags set for an entry in one blocklist
262                # do not interfere with flags set for an entry in a different
263                # list.
264                result = deepcopy(elem)
265                result.add_flags(listflags)
266                return result
267
268            # We add list flags *before* filtering because the filters might
269            # want to access flags as part of their operation.
270            unified_list.extend(map(add_list_flags, exec_env[blocklist]))
271
272            # We also add any test entries for the lists specified by _inspec
273            unified_list.extend(
274                map(add_list_flags, self.get_test_entries(exec_env, blocklist))
275            )
276
277        # There should be no dupes in the input. If there are, raise an error.
278        self.ensure_no_dupes(unified_list)
279
280        # Now we filter out any unwanted list entries
281        filtered_list = filter(filter_func, unified_list)
282
283        # Sort the list on entry name so that the blocklist code may use
284        # binary search if it so chooses.
285        return sorted(filtered_list, key=lambda e: e.get_name())
286
287    @staticmethod
288    def get_fd(outspec_leaf_name):
289        """If BlocklistDescriptor.ExistingFd corresponds to outspec_leaf_name,
290        then we return that. Otherwise, we construct a new absolute path to
291        outspec_leaf_name and open a new file descriptor for writing.
292        """
293        if (
294            not BlocklistDescriptor.ExistingFd
295            or BlocklistDescriptor.ExistingFdLeafName != outspec_leaf_name
296        ):
297            new_name = os.path.join(BlocklistDescriptor.OutputDir, outspec_leaf_name)
298            return open(new_name, "w")
299
300        fd = BlocklistDescriptor.ExistingFd
301        BlocklistDescriptor.ExistingFd = None
302        return fd
303
304    def write(self, src_file, exec_env):
305        """Write out all output files that are specified by this descriptor.
306
307        Positional arguments:
308
309        src_file -- name of the input file from which the lists were generated.
310
311        exec_env -- dictionary containing the lists that were parsed from the
312        input file when it was executed.
313        """
314
315        for outspec in self._outspecs:
316            # Use DEFAULT_OUTSPEC to supply defaults for any unused outspec keys
317            effective_outspec = BlocklistDescriptor.DEFAULT_OUTSPEC.copy()
318            effective_outspec.update(outspec)
319
320            entries = self.gen_list(exec_env, effective_outspec["filter"])
321            if not entries:
322                continue
323
324            mode = effective_outspec["mode"]
325
326            # Since each output descriptor may generate output across multiple
327            # modes, each list is uniquified via the concatenation of our name
328            # and the mode.
329            listname = self._name + mode
330            leafname_no_ext = BlocklistDescriptor.FILE_NAME_TPL.format(listname)
331            leafname = leafname_no_ext + ".h"
332
333            with self.get_fd(leafname) as output:
334                print(H_HEADER.format(src_file, leafname_no_ext), file=output, end="")
335                print(effective_outspec["begin"], file=output, end="")
336
337                for e in entries:
338                    e.write(output, mode)
339
340                print(effective_outspec["end"], file=output, end="")
341                print(H_FOOTER.format(src_file, leafname_no_ext), file=output, end="")
342
343
344A11Y_OUTPUT_SPEC = {
345    "filter": FILTER_ALLOW_ONLY_A11Y,
346    "begin": H_BEGIN_A11Y,
347}
348
349LSP_MODE_GUID = "Guid"
350
351LSP_OUTPUT_SPEC = [
352    {
353        "mode": LSP_MODE_GUID,
354        "filter": FILTER_ALLOW_ONLY_LSP,
355        "begin": H_BEGIN_LSP,
356        "end": H_END_LSP,
357    },
358]
359
360GENERATED_BLOCKLIST_FILES = [
361    BlocklistDescriptor("A11y", ["BROWSER_PROCESS"], outspec=A11Y_OUTPUT_SPEC),
362    BlocklistDescriptor(
363        "Launcher",
364        ALL_DEFINITION_LISTS,
365        flagspec={
366            "BROWSER_PROCESS": {BROWSER_PROCESS_ONLY},
367            "CHILD_PROCESSES": {CHILD_PROCESSES_ONLY},
368        },
369    ),
370    BlocklistDescriptor(
371        "Legacy",
372        ALL_DEFINITION_LISTS,
373        flagspec={
374            "BROWSER_PROCESS": {BROWSER_PROCESS_ONLY},
375            "CHILD_PROCESSES": {CHILD_PROCESSES_ONLY},
376        },
377    ),
378    # Roughed-in for the moment; we'll enable this in bug 1238735
379    # BlocklistDescriptor('LSP', ALL_DEFINITION_LISTS, outspec=LSP_OUTPUT_SPEC),
380    BlocklistDescriptor(
381        "Test", ALL_DEFINITION_LISTS, outspec={"filter": FILTER_TESTS_ONLY}
382    ),
383]
384
385
386class PETimeStamp(object):
387    def __init__(self, ts):
388        max_timestamp = (2 ** 32) - 1
389        if ts < 0 or ts > max_timestamp:
390            raise ValueError("Invalid timestamp value")
391        self._value = ts
392
393    def __str__(self):
394        return "0x%08XU" % self._value
395
396
397class Version(object):
398    """Encapsulates a DLL version."""
399
400    ALL_VERSIONS = 0xFFFFFFFFFFFFFFFF
401    UNVERSIONED = 0
402
403    def __init__(self, *args):
404        """There are multiple ways to construct a Version:
405
406        As a tuple containing four elements (recommended);
407        As four integral arguments;
408        As a PETimeStamp;
409        As a long integer.
410
411        The tuple and list formats require the value of each element to be
412        between 0 and 0xFFFF, inclusive.
413        """
414
415        self._ver = Version.UNVERSIONED
416
417        if len(args) == 1:
418            if isinstance(args[0], tuple):
419                self.validate_iterable(args[0])
420
421                self._ver = "MAKE_VERSION%r" % (args[0],)
422            elif isinstance(args[0], PETimeStamp):
423                self._ver = args[0]
424            else:
425                self._ver = int(args[0])
426        elif len(args) == 4:
427            self.validate_iterable(args)
428
429            self._ver = "MAKE_VERSION%r" % (tuple(args),)
430        else:
431            raise ValueError("Bad arguments to Version constructor")
432
433    def validate_iterable(self, arg):
434        if len(arg) != 4:
435            raise ValueError("Versions must be a 4-tuple")
436
437        for component in arg:
438            if not isinstance(component, int) or component < 0 or component > 0xFFFF:
439                raise ValueError(
440                    "Each version component must be a 16-bit " "unsigned integer"
441                )
442
443    def build_long(self, args):
444        self.validate_iterable(args)
445        return (
446            (int(args[0]) << 48)
447            | (int(args[1]) << 32)
448            | (int(args[2]) << 16)
449            | int(args[3])
450        )
451
452    def is_timestamp(self):
453        return isinstance(self._ver, PETimeStamp)
454
455    def __str__(self):
456        if isinstance(self._ver, int):
457            if self._ver == Version.ALL_VERSIONS:
458                return "DllBlockInfo::ALL_VERSIONS"
459
460            if self._ver == Version.UNVERSIONED:
461                return "DllBlockInfo::UNVERSIONED"
462
463            return "0x%016XULL" % self._ver
464
465        return str(self._ver)
466
467
468class DllBlocklistEntry(object):
469    TEST_CONDITION = "defined(ENABLE_TESTS)"
470
471    def __init__(self, name, ver, flags=(), **kwargs):
472        """Positional arguments:
473
474        name -- The leaf name of the DLL.
475
476        ver -- The maximum version to be blocked. NB: The comparison used by the
477        blocklist is <=, so you should specify the last bad version, as opposed
478        to the first good version.
479
480        flags -- iterable containing the flags that should be applicable to
481        this blocklist entry.
482
483        Keyword arguments:
484
485        condition -- a string containing a C++ preprocessor expression. This
486        condition is written as a condition for an #if/#endif block that is
487        generated around the entry during output.
488        """
489
490        self.check_ascii(name)
491        self._name = name.lower()
492        self._ver = Version(ver)
493
494        self._flags = set()
495        self.add_flags(flags)
496        if self._ver.is_timestamp():
497            self._flags.add(USE_TIMESTAMP)
498
499        self._cond = kwargs.get("condition", set())
500        if isinstance(self._cond, str):
501            self._cond = {self._cond}
502
503    @staticmethod
504    def check_ascii(name):
505        try:
506            # Supported in Python 3.7
507            if not name.isascii():
508                raise ValueError('DLL name "%s" must be ASCII!' % name)
509            return
510        except AttributeError:
511            pass
512
513        try:
514            name.encode("ascii")
515        except UnicodeEncodeError:
516            raise ValueError('DLL name "%s" must be ASCII!' % name)
517
518    def get_name(self):
519        return self._name
520
521    def set_condition(self, cond):
522        self._cond.add(cond)
523
524    def get_condition(self):
525        if len(self._cond) == 1:
526            fmt = "{0}"
527        else:
528            fmt = "({0})"
529
530        return " && ".join([fmt.format(c) for c in self._cond])
531
532    def set_is_test(self):
533        self.set_condition(DllBlocklistEntry.TEST_CONDITION)
534
535    def is_test(self):
536        return self._cond and DllBlocklistEntry.TEST_CONDITION in self._cond
537
538    def add_flags(self, new_flags):
539        if isinstance(new_flags, str):
540            self._flags.add(new_flags)
541        else:
542            self._flags.update(new_flags)
543
544    @staticmethod
545    def get_flag_string(flag):
546        return "DllBlockInfo::" + flag
547
548    def get_flags_list(self):
549        return self._flags
550
551    def write(self, output, mode):
552        if self._cond:
553            print("#if %s" % self.get_condition(), file=output)
554
555        flags_str = ""
556
557        flags = self.get_flags_list()
558        if flags:
559            flags_str = ", " + " | ".join(map(self.get_flag_string, flags))
560
561        entry_str = '  DLL_BLOCKLIST_ENTRY("%s", %s%s)' % (
562            self._name,
563            str(self._ver),
564            flags_str,
565        )
566        print(entry_str, file=output)
567
568        if self._cond:
569            print("#endif  // %s" % self.get_condition(), file=output)
570
571
572class A11yBlocklistEntry(DllBlocklistEntry):
573    """Represents a blocklist entry for injected a11y DLLs. This class does
574    not need to do anything special compared to a DllBlocklistEntry; we just
575    use this type to distinguish a11y entries from "regular" blocklist entries.
576    """
577
578    def __init__(self, name, ver, flags=(), **kwargs):
579        """These arguments are identical to DllBlocklistEntry.__init__"""
580
581        super(A11yBlocklistEntry, self).__init__(name, ver, flags, **kwargs)
582
583
584class RedirectToNoOpEntryPoint(DllBlocklistEntry):
585    """Represents a blocklist entry to hook the entrypoint into a function
586    just returning TRUE to keep a module alive and harmless.
587    This entry is intended to block a DLL which is injected by IAT patching
588    which is planted by a kernel callback routine for LoadImage because
589    blocking such a DLL makes a process fail to launch.
590    """
591
592    def __init__(self, name, ver, flags=(), **kwargs):
593        """These arguments are identical to DllBlocklistEntry.__init__"""
594
595        super(RedirectToNoOpEntryPoint, self).__init__(name, ver, flags, **kwargs)
596
597    def get_flags_list(self):
598        flags = super(RedirectToNoOpEntryPoint, self).get_flags_list()
599        # RedirectToNoOpEntryPoint items always include the following flag
600        flags.add(REDIRECT_TO_NOOP_ENTRYPOINT)
601        return flags
602
603
604class LspBlocklistEntry(DllBlocklistEntry):
605    """Represents a blocklist entry for a WinSock Layered Service Provider (LSP)."""
606
607    GUID_UNPACK_FMT_LE = "<IHHBBBBBBBB"
608    Guids = dict()
609
610    def __init__(self, name, ver, guids, flags=(), **kwargs):
611        """Positional arguments:
612
613        name -- The leaf name of the DLL.
614
615        ver -- The maximum version to be blocked. NB: The comparison used by the
616        blocklist is <=, so you should specify the last bad version, as opposed
617        to the first good version.
618
619        guids -- Either a string or list of strings containing the GUIDs that
620        uniquely identify the LSP. These GUIDs are generated by the developer of
621        the LSP and are registered with WinSock alongside the LSP. We record
622        this GUID as part of the "Winsock_LSP" annotation in our crash reports.
623
624        flags -- iterable containing the flags that should be applicable to
625        this blocklist entry.
626
627        Keyword arguments:
628
629        condition -- a string containing a C++ preprocessor expression. This
630        condition is written as a condition for an #if/#endif block that is
631        generated around the entry during output.
632        """
633
634        super(LspBlocklistEntry, self).__init__(name, ver, flags, **kwargs)
635        if not guids:
636            raise ValueError("Missing GUID(s)!")
637
638        if isinstance(guids, str):
639            self.insert(UUID(guids), name)
640        else:
641            for guid in guids:
642                self.insert(UUID(guid), name)
643
644    def insert(self, guid, name):
645        # Some explanation here: Multiple DLLs (and thus multiple instances of
646        # LspBlocklistEntry) may share the same GUIDs. To ensure that we do not
647        # have any duplicates, we store each GUID in a class variable, Guids.
648        # We include the original DLL name from the blocklist definitions so
649        # that we may output a comment above each GUID indicating which entries
650        # correspond to which GUID.
651        LspBlocklistEntry.Guids.setdefault(guid, []).append(name)
652
653    def get_flags_list(self):
654        flags = super(LspBlocklistEntry, self).get_flags_list()
655        # LSP entries always include the following flag
656        flags.add(SUBSTITUTE_LSP_PASSTHROUGH)
657        return flags
658
659    @staticmethod
660    def as_c_struct(guid, names):
661        parts = unpack(LspBlocklistEntry.GUID_UNPACK_FMT_LE, guid.bytes_le)
662        str_guid = (
663            "  // %r\n  // {%s}\n  { 0x%08x, 0x%04x, 0x%04x, "
664            "{ 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x, 0x%02x }"
665            " }"
666            % (
667                names,
668                str(guid),
669                parts[0],
670                parts[1],
671                parts[2],
672                parts[3],
673                parts[4],
674                parts[5],
675                parts[6],
676                parts[7],
677                parts[8],
678                parts[9],
679                parts[10],
680            )
681        )
682        return str_guid
683
684    def write(self, output, mode):
685        if mode != LSP_MODE_GUID:
686            super(LspBlocklistEntry, self).write(output, mode)
687            return
688
689        # We dump the entire contents of Guids on the first call, and then
690        # clear it. Remaining invocations of this method are no-ops.
691        if LspBlocklistEntry.Guids:
692            result = ",\n".join(
693                [
694                    self.as_c_struct(guid, names)
695                    for guid, names in iteritems(LspBlocklistEntry.Guids)
696                ]
697            )
698            print(result, file=output)
699            LspBlocklistEntry.Guids.clear()
700
701
702def exec_script_file(script_name, globals):
703    with open(script_name) as script:
704        exec(compile(script.read(), script_name, "exec"), globals)
705
706
707def gen_blocklists(first_fd, defs_filename):
708
709    BlocklistDescriptor.set_output_fd(first_fd)
710
711    # exec_env defines the global variables that will be present in the
712    # execution environment when defs_filename is run by exec.
713    exec_env = {
714        # Add the blocklist entry types
715        "A11yBlocklistEntry": A11yBlocklistEntry,
716        "DllBlocklistEntry": DllBlocklistEntry,
717        "LspBlocklistEntry": LspBlocklistEntry,
718        "RedirectToNoOpEntryPoint": RedirectToNoOpEntryPoint,
719        # Add the special version types
720        "ALL_VERSIONS": Version.ALL_VERSIONS,
721        "UNVERSIONED": Version.UNVERSIONED,
722        "PETimeStamp": PETimeStamp,
723    }
724
725    # Import definition lists into exec_env
726    for defname in ALL_DEFINITION_LISTS:
727        exec_env[defname] = []
728        # For each defname, add a special list for test-only entries
729        exec_env[derive_test_key(defname)] = []
730
731    # Import flags into exec_env
732    exec_env.update({flag: flag for flag in INPUT_ONLY_FLAGS})
733
734    # Now execute the input script with exec_env providing the globals
735    exec_script_file(defs_filename, exec_env)
736
737    # Tell the output descriptors to write out the output files.
738    for desc in GENERATED_BLOCKLIST_FILES:
739        desc.write(defs_filename, exec_env)
740