0

fuzzing: introduce automated IPC fuzzing

This CL introduces a fuzzer that automatically detects renderer process
exposed IPC interfaces and fuzzes them.

The design of this fuzzer has been described at
https://docs.google.com/document/d/1Y5yy8YTJRLPQUPHzPO-KhmT-gZoBkLjWp2eRaJbBpq4/edit?usp=sharing.

Because GN does not support dynamic dependencies, this patch needs to
introduce a gni file at `chrome/browser_exposed_mojom_targets.gni`
which lists all the targets of browser exposed mojom interfaces on
Linux. Please, check this doc to read about pros and cons about the
different considered solutions:
https://docs.google.com/document/d/1BSgrxtXVKGEtVU5KoKZbvZfQOxIZb2fqYcAX4FXYw8E.

Thanks to this automated IPC fuzzer, any new browser-exposed IPC
endpoint will be fuzzed as soon as it gets in the codebase. Doing this,
we aim at detecting potential issues as soon as possible.

Bug: 40282115
Cq-Include-Trybots: luci.chromium.try:linux-centipede-asan-rel
Change-Id: I1f097b871d53c4f2a34c62c43bbede1fd23d760a
Reviewed-on: https://chromium-review.googlesource.com/c/chromium/src/+/5490095
Reviewed-by: Adrian Taylor <adetaylor@chromium.org>
Reviewed-by: Daniel Cheng <dcheng@chromium.org>
Commit-Queue: Paul Semel <paulsemel@chromium.org>
Cr-Commit-Position: refs/heads/main@{#1310532}
This commit is contained in:
Paul Semel
2024-06-05 10:57:35 +00:00
committed by Chromium LUCI CQ
parent 11a700e804
commit 90ef9055a5
7 changed files with 850 additions and 1304 deletions

@ -0,0 +1,345 @@
# Copyright 2024 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.
#
# This file represents global knowledge about all the mojom targets building
# browser-to-renderer exposed mojom interfaces. Developers are responsible for
# maintaining this file to match addition and removal of newly exposed targets.
# This is part of our automated fuzzing of the browser/renderer interface that
# lives at `//chrome/test/fuzzing/renderer_fuzzing/ipc_fuzzing`.
# As for now, we are only listing those targets for interfaces exposed on
# Linux.
browser_exposed_mojom_targets = [
"//cc/mojom:layer_type",
"//cc/mojom:mojom",
"//chrome/browser/cart:mojo_bindings",
"//chrome/browser/companion/core/mojom:mojo_bindings",
"//chrome/browser/lens/core/mojom:mojo_bindings",
"//chrome/browser/media:mojo_bindings",
"//chrome/browser/new_tab_page/modules/feed:mojo_bindings",
"//chrome/browser/new_tab_page/modules/file_suggestion:mojo_bindings",
"//chrome/browser/new_tab_page/modules/history_clusters:mojo_bindings",
"//chrome/browser/new_tab_page/modules/history_clusters/cart:mojo_bindings",
"//chrome/browser/new_tab_page/modules/history_clusters/discount:mojo_bindings",
"//chrome/browser/new_tab_page/modules/photos:mojo_bindings",
"//chrome/browser/new_tab_page/modules/recipes:mojo_bindings",
"//chrome/browser/new_tab_page/modules/safe_browsing:mojo_bindings",
"//chrome/browser/new_tab_page/modules/v2/calendar:mojo_bindings",
"//chrome/browser/new_tab_page/modules/v2/history_clusters:mojo_bindings",
"//chrome/browser/new_tab_page/modules/v2/most_relevant_tab_resumption:mojo_bindings",
"//chrome/browser/new_tab_page/modules/v2/tab_resumption:mojo_bindings",
"//chrome/browser/resource_coordinator:mojo_bindings",
"//chrome/browser/ui/webui/access_code_cast:mojo_bindings",
"//chrome/browser/ui/webui/app_home:mojo_bindings",
"//chrome/browser/ui/webui/app_service_internals:mojo_bindings",
"//chrome/browser/ui/webui/bluetooth_internals:mojo_bindings",
"//chrome/browser/ui/webui/connectors_internals:mojo_bindings",
"//chrome/browser/ui/webui/data_sharing_internals:mojo_bindings",
"//chrome/browser/ui/webui/discards:mojo_bindings",
"//chrome/browser/ui/webui/downloads:mojo_bindings",
"//chrome/browser/ui/webui/hats:mojo_bindings",
"//chrome/browser/ui/webui/internals/user_education:mojo_bindings",
"//chrome/browser/ui/webui/location_internals:mojo_bindings",
"//chrome/browser/ui/webui/new_tab_page_third_party:mojo_bindings",
"//chrome/browser/ui/webui/new_tab_page:mojo_bindings",
"//chrome/browser/ui/webui/new_tab_page/foo:mojo_bindings",
"//chrome/browser/ui/webui/omnibox:mojo_bindings",
"//chrome/browser/ui/webui/on_device_internals:mojom",
"//chrome/browser/ui/webui/privacy_sandbox:mojo_bindings",
"//chrome/browser/ui/webui/reset_password:mojo_bindings",
"//chrome/browser/ui/webui/search_engine_choice:mojo_bindings",
"//chrome/browser/ui/webui/segmentation_internals:mojo_bindings",
"//chrome/browser/ui/webui/side_panel/bookmarks:mojo_bindings",
"//chrome/browser/ui/webui/side_panel/customize_chrome:mojo_bindings",
"//chrome/browser/ui/webui/side_panel/performance_controls:mojo_bindings",
"//chrome/browser/ui/webui/side_panel/reading_list:mojo_bindings",
"//chrome/browser/ui/webui/suggest_internals:mojo_bindings",
"//chrome/browser/ui/webui/tab_search:mojo_bindings",
"//chrome/browser/ui/webui/tab_strip:mojo_bindings",
"//chrome/browser/ui/webui/tabs:mojo_bindings",
"//chrome/browser/ui/webui/usb_internals:mojo_bindings",
"//chrome/browser/ui/webui/web_app_internals:mojo_bindings",
"//chrome/browser/ui/webui/whats_new:mojo_bindings",
"//chrome/browser/web_applications/mojom:mojom_web_apps_enum",
"//chrome/common:available_offline_content_mojom",
"//chrome/common:mojo_bindings",
"//chrome/common:offline_page_auto_fetcher_mojom",
"//chrome/common:supervised_user_commands_mojom",
"//chrome/common/accessibility:mojo_bindings",
"//chrome/common/cart:mojo_bindings",
"//chrome/common/companion:mojo_bindings",
"//chrome/common/compose:mojo_bindings",
"//chrome/common/importer:interfaces",
"//chrome/common/search:mojo_bindings",
"//chrome/services/file_util/public/mojom:mojom",
"//chrome/services/media_gallery_util/public/mojom:mojom",
"//chrome/services/on_device_translation/public/mojom:mojom",
"//chrome/services/printing/public/mojom:mojom",
"//chrome/services/removable_storage_writer/public/mojom:mojom",
"//components/attribution_reporting:mojom",
"//components/attribution_reporting:registration_header_error_mojom",
"//components/attribution_reporting:registration_mojom",
"//components/attribution_reporting:source_type_mojom",
"//components/autofill/content/common/mojom:mojom",
"//components/autofill/core/common/mojom:mojo_types",
"//components/browsing_topics/mojom:mojo_bindings",
"//components/commerce/core/internals/mojom:mojo_bindings",
"//components/commerce/core/mojom:mojo_bindings",
"//components/compose/core/browser:mojo_bindings",
"//components/content_capture/common:mojo_interfaces",
"//components/content_capture/common:mojo_types",
"//components/content_settings/common:mojom",
"//components/content_settings/core/common:content_settings_enums",
"//components/content_settings/core/common:content_settings_types",
"//components/content_settings/core/common:mojo_bindings",
"//components/continuous_search/common/public/mojom:mojom",
"//components/device_signals/core/common/mojom:mojom",
"//components/digital_goods/mojom:mojom",
"//components/discardable_memory/public/mojom:mojom",
"//components/dom_distiller/content/common/mojom:mojom",
"//components/dom_distiller/core/mojom:mojom",
"//components/download/public/common:interfaces",
"//components/facilitated_payments/core/mojom:facilitated_payments_agent_mojom",
"//components/facilitated_payments/core/mojom:pix_code_validator_mojom",
"//components/feed/mojom:mojo_bindings",
"//components/global_media_controls/public/mojom:device_service",
"//components/guest_view/common:mojom",
"//components/heap_profiling/in_process:mojom",
"//components/history_clusters/history_clusters_internals/webui:mojo_bindings",
"//components/history_clusters/public/mojom:mojo_bindings",
"//components/history/core/browser/mojom:mojo_bindings",
"//components/lens:lens_mojo",
"//components/media_router/common/mojom:debugger",
"//components/media_router/common/mojom:logger",
"//components/media_router/common/mojom:media_controller",
"//components/media_router/common/mojom:media_route_provider_id",
"//components/media_router/common/mojom:media_router",
"//components/media_router/common/mojom:route_request_result_code",
"//components/metrics/public/mojom:call_stack_mojo_bindings",
"//components/metrics/public/mojom:histogram_fetcher_mojo_bindings",
"//components/metrics/public/mojom:single_sample_metrics_mojo_bindings",
"//components/metrics/structured/mojom:mojom",
"//components/mirroring/mojom:common",
"//components/mirroring/mojom:service",
"//components/ml/mojom:mojom",
"//components/ml/webnn:features",
"//components/network_hints/common:mojo_bindings",
"//components/no_state_prefetch/common:mojo_bindings",
"//components/omnibox/browser:mojo_bindings",
"//components/optimization_guide/content/mojom:mojo_interfaces",
"//components/optimization_guide/core:interfaces",
"//components/optimization_guide/optimization_guide_internals/webui:mojo_bindings",
"//components/os_crypt/async/common:algorithm_mojom",
"//components/os_crypt/async/common:common_mojom",
"//components/page_image_service/mojom:mojo_bindings",
"//components/page_load_metrics/common:page_load_metrics_mojom",
"//components/paint_preview/common/mojom:mojom",
"//components/password_manager/services/csv_password/public/mojom:mojom",
"//components/payments/mojom:mojom",
"//components/performance_manager/public/mojom:mojom",
"//components/printing/common:mojo_interfaces",
"//components/safe_browsing/content/common:interfaces",
"//components/safe_browsing/core/common:interfaces",
"//components/schema_org/common:improved_mojom",
"//components/schema_org/common:mojom",
"//components/security_interstitials/core/common/mojom:mojom",
"//components/services/filesystem/public/mojom:mojom",
"//components/services/font/public/mojom:mojom",
"//components/services/heap_profiling/public/mojom:mojom",
"//components/services/language_detection/public/mojom:mojom",
"//components/services/paint_preview_compositor/public/mojom:mojom",
"//components/services/patch/public/mojom:mojom",
"//components/services/print_compositor/public/mojom:mojom",
"//components/services/quarantine/public/mojom:mojom",
"//components/services/storage/privileged/mojom:mojom_bucket",
"//components/services/storage/privileged/mojom:mojom",
"//components/services/storage/public/mojom:mojom",
"//components/services/storage/public/mojom/buckets:buckets",
"//components/services/storage/public/mojom/filesystem:filesystem",
"//components/services/unzip/public/mojom:mojom",
"//components/site_engagement/core/mojom:mojo_bindings",
"//components/spellcheck/common:interfaces",
"//components/subresource_filter/content/mojom:mojom",
"//components/subresource_filter/core/mojom:mojom",
"//components/tab_groups/public/mojom:mojo_bindings",
"//components/translate/content/common:common",
"//components/variations:variations_mojom",
"//components/visitedlink/common:interfaces",
"//components/viz/service/debugger/mojom:mojom",
"//components/web_cache/public/mojom:mojom",
"//components/web_package/mojom:mojom",
"//components/webapps/common:mojo_bindings",
"//components/webapps/services/web_app_origin_association/public/mojom:mojom",
"//content/browser/attribution_reporting:internals_mojo_bindings",
"//content/browser/attribution_reporting:mojo_bindings",
"//content/browser/attribution_reporting:registration_result_mojom",
"//content/browser/indexed_db:internals_mojo_bindings",
"//content/browser/private_aggregation:mojo_bindings",
"//content/browser/process_internals:mojo_bindings",
"//content/browser/tracing/trace_report:mojo_bindings",
"//content/browser/xr/webxr_internals/mojom:mojo_bindings",
"//content/common:mojo_bindings",
"//content/public/common:interfaces",
"//content/public/common:renderer_type",
"//device/bluetooth/public/mojom:deprecated_experimental_interfaces",
"//device/bluetooth/public/mojom:mojom",
"//device/gamepad/public/mojom:mojom",
"//device/vr/public/mojom:isolated_xr_service",
"//device/vr/public/mojom:test_mojom",
"//device/vr/public/mojom:vr_service",
"//device/vr/public/mojom:xr_common",
"//extensions/common:mojom",
"//extensions/common/api:mojom",
"//gpu/ipc/common:gmb_interface",
"//gpu/ipc/common:gpu_channel_mojom",
"//gpu/ipc/common:gpu_preferences_interface",
"//gpu/ipc/common:interfaces",
"//gpu/ipc/common:surface_handle",
"//gpu/ipc/common:vulkan_interface",
"//ipc:mojom_constants",
"//ipc:mojom",
"//media/capture/mojom:image_capture",
"//media/capture/mojom:video_capture_buffer",
"//media/capture/mojom:video_capture_types",
"//media/capture/mojom:video_capture",
"//media/capture/mojom:video_effects_manager",
"//media/learning/mojo/public/mojom:mojom",
"//media/midi:mojo",
"//media/mojo/mojom:audio_data",
"//media/mojo/mojom:encryption_pattern",
"//media/mojo/mojom:mojom",
"//media/mojo/mojom:remoting_common",
"//media/mojo/mojom:remoting",
"//media/mojo/mojom:speech_recognition_audio_forwarder",
"//media/mojo/mojom:speech_recognition",
"//media/mojo/mojom:web_speech_recognition",
"//media/mojo/mojom/stable:native_pixmap_handle",
"//media/mojo/mojom/stable:stable_video_decoder",
"//mojo/public/interfaces/bindings:bindings",
"//mojo/public/mojom/base:base",
"//mojo/public/mojom/base:protobuf_support",
"//pdf/mojom:mojom",
"//printing/backend/mojom:mojom",
"//printing/mojom:mojom",
"//printing/mojom:printing_context",
"//sandbox/policy/mojom:mojom",
"//services/accessibility/public/mojom:automation_client",
"//services/accessibility/public/mojom:automation",
"//services/accessibility/public/mojom:mojom",
"//services/audio/public/mojom:mojom",
"//services/cert_verifier/public/mojom:mojom",
"//services/data_decoder/public/mojom:mojom_xml_parser",
"//services/data_decoder/public/mojom:mojom",
"//services/device/public/mojom:device_service",
"//services/device/public/mojom:generic_sensor",
"//services/device/public/mojom:geolocation_internals",
"//services/device/public/mojom:geoposition",
"//services/device/public/mojom:mojom",
"//services/device/public/mojom:usb_test",
"//services/device/public/mojom:usb",
"//services/image_annotation/public/mojom:mojom",
"//services/media_session/public/mojom:mojom",
"//services/metrics/public/mojom:mojom",
"//services/network/public/mojom:cookies_mojom",
"//services/network/public/mojom:mojom_attribution",
"//services/network/public/mojom:mojom_first_party_sets",
"//services/network/public/mojom:mojom_host_resolver",
"//services/network/public/mojom:mojom_ip_address",
"//services/network/public/mojom:mojom_network_anonymization_key",
"//services/network/public/mojom:mojom_network_isolation_key",
"//services/network/public/mojom:mojom_network_param",
"//services/network/public/mojom:mojom_proxy_config",
"//services/network/public/mojom:mojom_schemeful_site",
"//services/network/public/mojom:mojom_shared_dictionary",
"//services/network/public/mojom:mojom_structured_headers",
"//services/network/public/mojom:mojom",
"//services/network/public/mojom:url_loader_base",
"//services/network/public/mojom:websocket_mojom",
"//services/on_device_model/public/mojom:mojom",
"//services/passage_embeddings/public/mojom:mojom",
"//services/preferences/public/mojom:mojom",
"//services/proxy_resolver/public/mojom:mojom",
"//services/resource_coordinator/public/mojom:mojom",
"//services/screen_ai/public/mojom:factory",
"//services/screen_ai/public/mojom:mojom",
"//services/service_manager/public/mojom:constants",
"//services/service_manager/public/mojom:mojom",
"//services/shape_detection/public/mojom:mojom",
"//services/tracing/public/mojom:mojom",
"//services/video_capture/public/mojom:constants",
"//services/video_capture/public/mojom:mojom",
"//services/video_effects/public/mojom:mojom",
"//services/viz/privileged/mojom:mojom",
"//services/viz/privileged/mojom/compositing:compositing",
"//services/viz/privileged/mojom/gl:gl",
"//services/viz/public/mojom:mojom",
"//services/viz/public/mojom:shared_image_format",
"//services/viz/public/mojom:singleplanar_format",
"//services/webnn/public/mojom:mojom",
"//skia/public/mojom:mojom",
"//storage/browser/quota:mojo_bindings",
"//third_party/blink/public/mojom:android_mojo_bindings",
"//third_party/blink/public/mojom:authenticator_test_mojo_bindings",
"//third_party/blink/public/mojom:color_scheme_mojo_bindings",
"//third_party/blink/public/mojom:embedded_frame_sink_mojo_bindings",
"//third_party/blink/public/mojom:memory_usage_monitor_linux_mojo_bindings",
"//third_party/blink/public/mojom:mojom_core",
"//third_party/blink/public/mojom:mojom_mhtml_load_result",
"//third_party/blink/public/mojom:mojom_modules",
"//third_party/blink/public/mojom:mojom_platform",
"//third_party/blink/public/mojom:script_type_mojo_bindings",
"//third_party/blink/public/mojom:web_bluetooth_mojo_bindings",
"//third_party/blink/public/mojom:web_feature_mojo_bindings",
"//third_party/blink/public/mojom/gpu:gpu",
"//third_party/blink/public/mojom/origin_trial_feature:origin_trial_feature",
"//third_party/blink/public/mojom/origin_trial_state:origin_trial_state",
"//third_party/blink/public/mojom/private_network_device:private_network_device",
"//third_party/blink/public/mojom/quota:quota",
"//third_party/blink/public/mojom/runtime_feature_state:runtime_feature_state",
"//third_party/blink/public/mojom/service_worker:storage",
"//third_party/blink/public/mojom/storage_key:storage_key",
"//third_party/blink/public/mojom/tokens:tokens",
"//third_party/blink/public/mojom/usb:usb",
"//ui/accessibility:ax_constants_mojo",
"//ui/accessibility:ax_enums_mojo",
"//ui/accessibility:ax_features_mojo",
"//ui/accessibility/mojom:mojom",
"//ui/base/cursor/mojom:cursor_type",
"//ui/base/cursor/mojom:mojom",
"//ui/base/dragdrop/mojom:mojom",
"//ui/base/ime/mojom:mojom",
"//ui/base/mojom:mojom",
"//ui/color:mojom",
"//ui/display/mojom:mojom",
"//ui/events/mojom:event_latency_metadata_mojom",
"//ui/events/mojom:mojom",
"//ui/gfx/geometry/mojom:mojom",
"//ui/gfx/image/mojom:mojom",
"//ui/gfx/mojom:hdr_metadata",
"//ui/gfx/mojom:mojom",
"//ui/gfx/mojom:native_handle_types",
"//ui/gfx/range/mojom:mojom",
"//ui/gl/mojom:mojom",
"//ui/latency/mojom:mojom",
"//ui/ozone/platform/wayland/mojom:mojom",
"//ui/ozone/public/mojom:gesture_properties_service",
"//ui/webui/resources/cr_components/app_management:mojo_bindings",
"//ui/webui/resources/cr_components/certificate_manager:mojom",
"//ui/webui/resources/cr_components/color_change_listener:mojom",
"//ui/webui/resources/cr_components/commerce:mojo_bindings",
"//ui/webui/resources/cr_components/customize_color_scheme_mode:mojom",
"//ui/webui/resources/cr_components/customize_themes:mojom",
"//ui/webui/resources/cr_components/help_bubble:mojo_bindings",
"//ui/webui/resources/cr_components/history_clusters:mojo_bindings",
"//ui/webui/resources/cr_components/history_embeddings:mojo_bindings",
"//ui/webui/resources/cr_components/most_visited:mojom",
"//ui/webui/resources/cr_components/searchbox:mojo_bindings",
"//ui/webui/resources/cr_components/theme_color_picker:mojom",
"//ui/webui/resources/js/browser_command:mojo_bindings",
"//ui/webui/resources/js/metrics_reporter:mojo_bindings",
"//url/mojom:url_mojom_gurl",
"//url/mojom:url_mojom_origin",
"//url/mojom:url_mojom_scheme_host_port",
]

@ -2,15 +2,22 @@
# Use of this source code is governed by a BSD-style license that can be # Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file. # found in the LICENSE file.
import("//chrome/browser_exposed_mojom_targets.gni")
import("//chrome/test/fuzzing/in_process_fuzzer.gni") import("//chrome/test/fuzzing/in_process_fuzzer.gni")
import("//chrome/test/fuzzing/renderer_fuzzing/in_process_renderer_fuzzing.gni") import("//chrome/test/fuzzing/renderer_fuzzing/in_process_renderer_fuzzing.gni")
import(
"//chrome/test/fuzzing/renderer_fuzzing/ipc_fuzzing/mojom_interfaces.gni")
group("test") { group("test") {
testonly = true testonly = true
} }
# We want to make sure to only enable this fuzzer on platforms that have a CQ
# bot so that the mojom target list is maintained up-to-date.
# Similarly, if something goes wrong with those targets, this will "only" break
# fuzzer CQ bots, so we this allows for damage control.
# crbug.com/343669713: enable this on Windows once the Linux version sticks in.
renderer_ipc_fuzzing_enabled =
fuzzing_engine_supports_custom_main && is_linux && enable_mojom_fuzzer
if (fuzzing_engine_supports_custom_main) { if (fuzzing_engine_supports_custom_main) {
source_set("renderer_in_process_fuzzer_runner") { source_set("renderer_in_process_fuzzer_runner") {
testonly = true testonly = true
@ -25,164 +32,77 @@ if (fuzzing_engine_supports_custom_main) {
} }
} }
# As for now, we cannot compile this fuzzer when `dcheck_always_on=true`, if (renderer_ipc_fuzzing_enabled) {
# because InProcessFuzzer aren't running properly. See crbug.com/40949031. _mojolpm_deps = []
# Relatedly, we are also checking for `fuzzing_engine_supports_custom_main` foreach(target, browser_exposed_mojom_targets) {
# because this is a necessary condition to most of the in_process_fuzzer _mojolpm_deps += [ "${target}_mojolpm" ]
# dependencies, which we are heavily relying upon here. }
if (!is_android && !dcheck_always_on && fuzzing_engine_supports_custom_main) {
# This tool aims at replicating an environment similar to how
# in_process_fuzzer are running, so that we can fetch a list of mojom
# interfaces that make sense for `renderer_in_process_mojolpm_fuzzer`.
executable("ipc_interfaces_dumper") { executable("ipc_interfaces_dumper") {
testonly = true testonly = true
sources = [ defines = [ "HAS_OUT_OF_PROC_TEST_RUNNER" ]
# We include those files so that we take into consideration the exact sources = [ "ipc_fuzzing/ipc_interfaces_dumper.cc" ]
# same setup as in_process_fuzzer, including the runtime flags. This is
# important because this can have an effect on which interfaces are
# being exposed.
"../in_process_fuzzer.cc",
"../in_process_fuzzer.h",
"ipc_fuzzing/ipc_interfaces_dumper.cc",
]
deps = [ deps = [
"//chrome/test:chrome_test_launcher", "//base",
"//chrome/test:browser_tests_runner",
"//chrome/test:test_support", "//chrome/test:test_support",
"//chrome/test/fuzzing:in_process_fuzzer_buildflags",
"//content/test:test_support", "//content/test:test_support",
# This is so that in_process_fuzzer internals work, but
# `ipc_interfaces_dumper` is not recognized as an actual fuzzer.
"//testing/libfuzzer:fuzzing_engine_no_main_core",
] ]
} }
action("renderer_in_process_mojolpm_fuzzer_generator") { action("renderer_in_process_mojolpm_fuzzer_generator") {
testonly = true testonly = true
deps = [ ":ipc_interfaces_dumper" ]
depfile = "$target_out_dir/$target_name.d"
inputs = []
foreach(target, browser_exposed_mojom_targets) {
inputs += [ get_label_info(target, "target_gen_dir") + "/" +
get_label_info(target, "name") + ".build_metadata" ]
}
# We cannot use the GN `metadata` mechanism here, because our initial
# deps could depend on other mojom targets which would also generate some
# metadata, but we would actually not depend on their `mojolpm` variant.
# Doing things the current way allows for ensuring that we are only listing
# meta files for mojolpm targets we directly depend upon.
_metafiles = []
foreach(file, inputs) {
_metafiles += [ rebase_path(file, root_build_dir) ]
}
write_file("$target_gen_dir/metadata", _metafiles)
script = "//chrome/test/fuzzing/renderer_fuzzing/ipc_fuzzing/generate_testcase.py" script = "//chrome/test/fuzzing/renderer_fuzzing/ipc_fuzzing/generate_testcase.py"
args = [ args = [
"-n", "-p",
"renderer_in_process_mojolpm_fuzzer", rebase_path("${root_build_dir}/ipc_interfaces_dumper", root_build_dir),
"-i",
rebase_path("${target_gen_dir}/interfaces.json", root_build_dir),
"-r",
rebase_path(root_gen_dir, root_build_dir),
"-m",
rebase_path("$target_gen_dir/metadata", root_build_dir),
"-t",
rebase_path("${target_gen_dir}/testcase.h", root_build_dir),
"-d", "-d",
rebase_path("${target_gen_dir}/", root_gen_dir), rebase_path("${target_gen_dir}/", root_gen_dir),
"-o", "-n",
rebase_path("${target_gen_dir}/testcase.h", root_build_dir), "renderer_in_process_mojolpm_fuzzer",
"-c", "-f",
rebase_path(depfile, root_build_dir),
] ]
foreach(interface, context_browser_exposed_interfaces) { outputs = [
args += [ interface[1] ] "${target_gen_dir}/interfaces.json",
} "${target_gen_dir}/testcase.h",
args += [ "-p" ] ]
foreach(interface, process_browser_exposed_interfaces) { deps += _mojolpm_deps
args += [ interface[1] ]
}
outputs = [ "${target_gen_dir}/testcase.h" ]
} }
in_process_renderer_mojolpm_generated_fuzzer( in_process_renderer_mojolpm_generated_fuzzer(
"renderer_in_process_mojolpm_fuzzer") { "renderer_in_process_mojolpm_fuzzer") {
sources = [ "renderer_in_process_mojolpm_fuzzer.cc" ] sources = [ "renderer_in_process_mojolpm_fuzzer.cc" ]
interfaces = context_browser_exposed_interfaces interface_file = "${target_gen_dir}/interfaces.json"
interfaces += process_browser_exposed_interfaces
_interfaces_deps = [
"//chrome/browser/lens/core/mojom:mojo_bindings_mojolpm",
"//chrome/browser/media:mojo_bindings_mojolpm",
"//chrome/browser/new_tab_page/modules/file_suggestion:mojo_bindings_mojolpm",
"//chrome/browser/new_tab_page/modules/photos:mojo_bindings_mojolpm",
"//chrome/browser/new_tab_page/modules/v2/history_clusters:mojo_bindings_mojolpm",
"//chrome/browser/new_tab_page/modules/v2/tab_resumption:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/access_code_cast:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/app_home:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/app_service_internals:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/bluetooth_internals:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/connectors_internals:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/discards:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/downloads:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/internals/user_education:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/location_internals:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/new_tab_page:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/new_tab_page/foo:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/new_tab_page_third_party:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/omnibox:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/privacy_sandbox:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/reset_password:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/search_engine_choice:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/segmentation_internals:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/side_panel/bookmarks:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/side_panel/customize_chrome:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/side_panel/reading_list:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/suggest_internals:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/tab_search:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/tab_strip:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/usb_internals:mojo_bindings_mojolpm",
"//chrome/browser/ui/webui/web_app_internals:mojo_bindings_mojolpm",
"//chrome/common:mojo_bindings_mojolpm",
"//chrome/common/accessibility:mojo_bindings_mojolpm",
"//chrome/common/cart:mojo_bindings_mojolpm",
"//components/browsing_topics/mojom:mojo_bindings_mojolpm",
"//components/commerce/core/internals/mojom:mojo_bindings_mojolpm",
"//components/dom_distiller/content/common/mojom:mojom_mojolpm",
"//components/history_clusters/history_clusters_internals/webui:mojo_bindings_mojolpm",
"//components/metrics/public/mojom:call_stack_mojo_bindings_mojolpm",
"//components/metrics/public/mojom:single_sample_metrics_mojo_bindings_mojolpm",
"//components/network_hints/common:mojo_bindings_mojolpm",
"//components/no_state_prefetch/common:mojo_bindings_mojolpm",
"//components/optimization_guide/optimization_guide_internals/webui:mojo_bindings_mojolpm",
"//components/page_image_service/mojom:mojo_bindings_mojolpm",
"//components/performance_manager/public/mojom:mojom_mojolpm",
"//components/safe_browsing/content/common:interfaces_mojolpm",
"//components/site_engagement/core/mojom:mojo_bindings_mojolpm",
"//components/spellcheck/common:interfaces_mojolpm",
"//components/translate/content/common:common_mojolpm",
"//content/browser/attribution_reporting:internals_mojo_bindings_mojolpm",
"//content/browser/indexed_db:internals_mojo_bindings_mojolpm",
"//content/browser/private_aggregation:mojo_bindings_mojolpm",
"//content/browser/process_internals:mojo_bindings_mojolpm",
"//content/browser/tracing/trace_report:mojo_bindings_mojolpm",
"//content/browser/xr/webxr_internals/mojom:mojo_bindings_mojolpm",
"//content/common:mojo_bindings_mojolpm",
"//device/gamepad/public/mojom:mojom_mojolpm",
"//device/vr/public/mojom:vr_service_mojolpm",
"//extensions/common/api:mojom_mojolpm",
"//media/capture/mojom:image_capture_mojolpm",
"//media/capture/mojom:video_capture_mojolpm",
"//media/midi:mojo_mojolpm",
"//media/mojo/mojom:mojom_mojolpm",
"//media/mojo/mojom:remoting_mojolpm",
"//media/mojo/mojom:speech_recognition_mojolpm",
"//media/mojo/mojom:web_speech_recognition_mojolpm",
"//services/device/public/mojom:mojom_mojolpm",
"//services/image_annotation/public/mojom:mojom_mojolpm",
"//services/network/public/mojom:cookies_mojom_mojolpm",
"//services/network/public/mojom:mojom_mojolpm",
"//services/network/public/mojom:url_loader_base_mojolpm",
"//services/resource_coordinator/public/mojom:mojom_mojolpm",
"//services/screen_ai/public/mojom:mojom_mojolpm",
"//services/shape_detection/public/mojom:mojom_mojolpm",
"//services/viz/public/mojom:mojom_mojolpm",
"//storage/browser/quota:mojo_bindings_mojolpm",
"//third_party/blink/public/mojom:android_mojo_bindings_mojolpm",
"//third_party/blink/public/mojom:authenticator_test_mojo_bindings_mojolpm",
"//third_party/blink/public/mojom:embedded_frame_sink_mojo_bindings_mojolpm",
"//third_party/blink/public/mojom:mojom_core_mojolpm",
"//third_party/blink/public/mojom:mojom_modules_mojolpm",
"//third_party/blink/public/mojom:mojom_platform_mojolpm",
"//third_party/blink/public/mojom:web_bluetooth_mojo_bindings_mojolpm",
"//third_party/blink/public/mojom/gpu:gpu_mojolpm",
"//third_party/blink/public/mojom/origin_trial_state:origin_trial_state_mojolpm",
"//third_party/blink/public/mojom/quota:quota_mojolpm",
"//third_party/blink/public/mojom/usb:usb_mojolpm",
"//ui/webui/resources/cr_components/app_management:mojo_bindings_mojolpm",
"//ui/webui/resources/cr_components/certificate_manager:mojom_mojolpm",
"//ui/webui/resources/cr_components/color_change_listener:mojom_mojolpm",
"//ui/webui/resources/cr_components/commerce:mojo_bindings_mojolpm",
"//ui/webui/resources/cr_components/customize_color_scheme_mode:mojom_mojolpm",
"//ui/webui/resources/cr_components/customize_themes:mojom_mojolpm",
"//ui/webui/resources/cr_components/help_bubble:mojo_bindings_mojolpm",
"//ui/webui/resources/cr_components/history_clusters:mojo_bindings_mojolpm",
"//ui/webui/resources/cr_components/most_visited:mojom_mojolpm",
"//ui/webui/resources/cr_components/searchbox:mojo_bindings_mojolpm",
"//ui/webui/resources/cr_components/theme_color_picker:mojom_mojolpm",
"//ui/webui/resources/js/browser_command:mojo_bindings_mojolpm",
"//ui/webui/resources/js/metrics_reporter:mojo_bindings_mojolpm",
]
deps = [ deps = [
":renderer_in_process_mojolpm_fuzzer_generator", ":renderer_in_process_mojolpm_fuzzer_generator",
@ -193,8 +113,10 @@ if (!is_android && !dcheck_always_on && fuzzing_engine_supports_custom_main) {
"//testing/libfuzzer/proto:url_proto_converter", "//testing/libfuzzer/proto:url_proto_converter",
"//third_party/blink/public/common:storage_key_proto_converter", "//third_party/blink/public/common:storage_key_proto_converter",
] ]
deps += _interfaces_deps
proto_deps = _interfaces_deps deps += _mojolpm_deps
proto_deps = [ ":renderer_in_process_mojolpm_fuzzer_generator" ]
proto_deps += _mojolpm_deps
} }
} }

@ -7,7 +7,40 @@
This script must be used with MojoLPMGenerator and a RendererFuzzer. It This script must be used with MojoLPMGenerator and a RendererFuzzer. It
generates code to handle interface creation requested by MojoLPM by using the generates code to handle interface creation requested by MojoLPM by using the
interface brokers provided by the internal RendererFuzzer mechanism. `ipc_interfaces_dumper` binary. It then formats a JSON file that
MojoLPMGenerator will understand.
JSON Format:
{
# Those are the context (frame, document...) bound interfaces.
"context_interfaces": [
[
"//path/to/mojom.mojom",
"qualified.interface.name",
"{Associated,}Remote",
],
...
],
# Those are the process bound interfaces.
"process_interfaces": [
{
"//path/to/mojom.mojom",
"qualified.interface.name",
"{Associated,}Remote",
},
...
],
# Those are the format MojoLPMGenerator undertands.
# This groups all interfaces together.
"interfaces": [
{
"gen/path/to/generator/file.mojom-module",
"qualified.interface.name",
"{Associated,}Remote",
},
...
]
}
This script uses the jinja2 and the `testcase.h.tmpl` template to generate C++ This script uses the jinja2 and the `testcase.h.tmpl` template to generate C++
code. A class named `RendererTestcase` will be created. code. A class named `RendererTestcase` will be created.
@ -15,45 +48,270 @@ code. A class named `RendererTestcase` will be created.
from __future__ import annotations from __future__ import annotations
import abc
import argparse import argparse
import dataclasses import json
import os import os
import pathlib import pathlib
import re import re
import subprocess
import sys import sys
import tempfile
import typing import typing
import enum
# Copied from //mojo/public/tools/mojom/mojom/fileutil.py.
def AddLocalRepoThirdPartyDirToModulePath():
"""Helper function to find the top-level directory of this script's repository
assuming the script falls somewhere within a 'chrome' directory, and insert
the top-level 'third_party' directory early in the module search path. Used to
ensure that third-party dependencies provided within the repository itself
(e.g. Chromium sources include snapshots of jinja2 and ply) are preferred over
locally installed system library packages."""
def _GetDirAbove(dirname: str):
"""Returns the directory "above" this file containing |dirname| (which must
also be "above" this file)."""
path = os.path.abspath(__file__)
while True:
path, tail = os.path.split(path)
if not tail:
return None
if tail == dirname:
return path
toplevel_dir = _GetDirAbove('chrome') def _GetDirAbove(dirname: str):
if toplevel_dir: """Returns the directory "above" this file containing |dirname| (which must
sys.path.insert(1, os.path.join(toplevel_dir, 'third_party')) also be "above" this file)."""
path = os.path.abspath(__file__)
while True:
path, tail = os.path.split(path)
if not tail:
return None
if tail == dirname:
return path
# This is needed in order to be able to import jinja2.
AddLocalRepoThirdPartyDirToModulePath()
SOURCE_DIR = _GetDirAbove('chrome')
sys.path.insert(1, os.path.join(SOURCE_DIR, 'third_party'))
sys.path.append(os.path.join(SOURCE_DIR, 'build'))
sys.path.append(os.path.join(SOURCE_DIR, 'mojo/public/tools/mojom/'))
from mojom.parse import parser as mojom_parser, ast
import action_helpers
import jinja2 import jinja2
XVFB_PATH = os.path.join(SOURCE_DIR, 'testing/xvfb.py')
def strip_end(text: str, suffix: str) -> str:
"""Similar to python 3.9 `removesuffix` function.
Args:
text: input text.
suffix: the suffix to remove if present.
Returns:
the input string with suffix removed if present.
"""
if suffix and text.endswith(suffix):
return text[:-len(suffix)]
return text
def get_all_interfaces(metadata_file: str) -> typing.List[str]:
"""Returns the list of every mojo interfaces in src_dir. Recurses through
subdirectories.
Args:
src_dir: the chromium source directory.
Returns:
the list of paths to mojom interfaces.
"""
res = []
with open(metadata_file, 'r', encoding='utf-8') as file:
lines = [line.rstrip() for line in file]
for line in lines:
with open(line, 'r') as metadata:
data = json.load(metadata)
for mojom_file in data['sources']:
if mojom_file.endswith('.mojom'):
path = os.path.join(os.path.dirname(line), mojom_file)
path = os.path.abspath(path)
res.append(path)
return res
def is_defined_in_module(qualified_name: str, interface: ast.Mojom) -> bool:
namespace = ".".join(qualified_name.split('.')[:-1])
name = qualified_name.split('.')[-1]
if not interface.module:
return False
m_namespace = str(interface.module.mojom_namespace)
if m_namespace != namespace:
return False
if not interface.definition_list:
return False
for definition in interface.definition_list:
if (isinstance(definition, ast.Interface) and
str(definition.mojom_name) == name):
return True
return False
def find_matching_interface(qualified_name: str,
modules: typing.List[ast.Mojom]) -> str:
"""Finds the correct mojom file for the given interface. The interface name
must be qualified.
Args:
qualified_name: the qualified interface name (e.g.
'blink.mojom.BlobRegistry').
modules: the list of parsed mojom modules.
Returns:
the path to the mojom file corresponding to the input interface.
"""
for module in modules:
if is_defined_in_module(qualified_name, module):
return module.module.filename
return None
def ensure_interface_deps_complete(interfaces: typing.List[str],
modules: typing.List[ast.Mojom],
build_dir: str):
"""Ensures that all the interfaces can be fetched from the parsed mojom
modules.
Args:
interfaces: the list of interfaces (qualified names).
modules: the list of mojom modules to search into.
Raises:
Exception: if at least one interface could not be found.
"""
missing_interfaces = []
for interface in interfaces:
res = find_matching_interface(interface, modules)
if not res:
missing_interfaces.append(interface)
if len(missing_interfaces) != 0:
raise Exception('Missing browser exposed targets for the following '
'interfaces:\n'
f'{missing_interfaces}\n'
'Please add the corresponding targets to '
'`//chrome/browser_exposed_mojom_targets.gni`.')
def handle_interfaces(interfaces,
mojom_files: typing.List[ast.Mojom],
source_path: str,
output):
"""Finds the mojom files for the given interfaces and append the formatted
result to the output list.
Args:
interfaces: the interfaces to handle.
mojom_files: the list of parsed mojom files to look into.
source_path: the path to chromium's root source directory.
output: the output list.
"""
for interface in interfaces:
qualified_name = interface['qualified_name']
interface_type = interface['type']
path = pathlib.Path(find_matching_interface(qualified_name, mojom_files))
path = path.relative_to(source_path)
output.append([
f"//{path}", qualified_name, interface_type
])
def filter_data(data):
"""Filters the JSON data. As for now, we filter out:
- AssociatedRemote
- Duplicate interfaces for context and process interfaces.
Args:
data: the JSON data.
"""
data_filter = lambda x : x['type'] != 'AssociatedRemote'
data['context_interfaces'] = list(filter(data_filter,
data['context_interfaces']))
data['process_interfaces'] = list(filter(data_filter,
data['process_interfaces']))
ctx_interfaces = [s['qualified_name'] for s in data['context_interfaces']]
data_filter = lambda x: x['qualified_name'] not in ctx_interfaces
data['process_interfaces'] = list(filter(data_filter,
data['process_interfaces']))
def run_ipc_dumper(dumper_path: str, out_file: str):
"""This runs the ipc_dumper executable at `dumper_path` and redirects its
output to `out_file` so that this tool can use it to generate the list of
interfaces.
Args:
dumper_path: path to the `ipc_interfaces_dumper` executable.
out_file: the file to which we'll dump the interfaces.
"""
env = os.environ.copy()
env["IPC_DUMP_PATH"] = out_file
# Since we're running these at compile time, we need to make sure this will
# run regardless of the building flags being used.
# When enabling ASAN, we have a `detect_ord_violation` issue when running
# this tool.
env["ASAN_OPTIONS"] = 'detect_odr_violation=0'
args = [XVFB_PATH, os.path.abspath(dumper_path)]
subprocess.run(args, capture_output=True, env=env, check=True)
def generate_interfaces(ipc_interfaces_dumper: str,
interfaces_f: str,
gen_dir: str,
metadata_file: str,
depfile: str):
"""Generates the appropriate interfaces file given the output of the
`ipc_interfaces_dumper`.
Args:
ipc_interfaces_dumper: the path to the `ipc_interfaces_dumper` binary.
interfaces_f: the output path to the JSON interfaces file.
gen_dir: the path to the root gen directory.
metadata_file: the path to the mojo metadata file.
depfile: the depfile to write to.
"""
interfaces = get_all_interfaces(metadata_file)
parsed_interfaces = []
for interface in interfaces:
with open(interface, 'r', encoding="utf-8") as f:
parsed_interfaces.append(mojom_parser.Parse(f.read(), interface))
output = {"context_interfaces": [], "process_interfaces": []}
with tempfile.NamedTemporaryFile() as input_file:
run_ipc_dumper(ipc_interfaces_dumper, input_file.name)
with open(input_file.name, 'r') as in_f:
data = json.load(in_f)
filter_data(data)
all_interfaces = data['context_interfaces'] + data['process_interfaces']
qualified_names = [e['qualified_name'] for e in all_interfaces]
ensure_interface_deps_complete(qualified_names,
parsed_interfaces,
os.path.join(gen_dir, os.pardir))
handle_interfaces(data['context_interfaces'],
parsed_interfaces,
SOURCE_DIR,
output['context_interfaces'])
handle_interfaces(data['process_interfaces'],
parsed_interfaces,
SOURCE_DIR,
output['process_interfaces'])
# MojoLPMGenerator expects a particular format for generating MojoLPM
# boilerplate. This part will generate the expected format and rebase the
# mojom module paths in order for MojoLPMGenerator to be able to find them.
output['interfaces'] = []
for interface in output['context_interfaces'] + output['process_interfaces']:
path = interface[0]
path = os.path.join(gen_dir, path.lstrip('/')) + '-module'
output['interfaces'].append([
path, interface[1], interface[2]
])
with action_helpers.atomic_output(interfaces_f, mode="w") as f:
json.dump(output, f)
# Now, we want to write the depfile so that ninja knows that we're depending
# on the mojom files. If one gets modified, we want to re-run this action.
all_interfaces = output['context_interfaces'] + output['process_interfaces']
paths = [i[0].lstrip('//') for i in all_interfaces]
paths = [pathlib.Path(os.path.join(SOURCE_DIR, p)) for p in paths]
action_helpers.write_depfile(depfile,
interfaces_f,
[os.path.relpath(p) for p in paths])
def split_interface_name(interface: str): def split_interface_name(interface: str):
"""Helper that splits a qualified mojo interface name into a dictionary """Helper that splits a qualified mojo interface name into a dictionary
containing the key 'name' that contains the name of the interface, and the containing the key 'name' that contains the name of the interface, and the
@ -106,23 +364,50 @@ def camel_to_snake_case(name: str) -> str:
# character but only add the '_'. # character but only add the '_'.
return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower() return re.sub(r"(?<!^)(?=[A-Z])", "_", name).lower()
def generate_testcase(interfaces_f: str,
fuzzer_dir: str,
fuzzer_name: str,
testcase_f: str):
"""Generates the testcase file given the interface list and the
MojoLPMGenerator fuzzer name.
Args:
interfaces_f: the path to the JSON interface file.
fuzzer_dir: the path to the fuzzer directory.
fuzzer_name: the name of the MojoLPM fuzzer.
testcase_f: the output path to the testcase .h file.
"""
template_dir = os.path.dirname(os.path.abspath(__file__))
environment = jinja2.Environment(loader=jinja2.FileSystemLoader(
template_dir))
template = environment.get_template('testcase.h.tmpl')
fuzzer_path = os.path.join(fuzzer_dir, fuzzer_name)
fuzzer_name = snake_to_camel_case(fuzzer_name)
mojolpm_classname = f"mojolpmgenerator::{fuzzer_name}Testcase"
with open(interfaces_f, 'r') as f:
data = json.load(f)
context = [c[1] for c in data['context_interfaces']]
process = [p[1] for p in data['process_interfaces']]
context = {
"filename": testcase_f,
"mojolpm_generator_filepath": f"{fuzzer_path}.h",
"mojolpm_generator_classname": mojolpm_classname,
"process_interfaces": [split_interface_name(p) for p in process],
"context_interfaces": [split_interface_name(c) for c in context],
}
with action_helpers.atomic_output(testcase_f, mode="w") as f:
f.write(template.render(context))
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Generate an IPC fuzzer based on MojoLPM Generator.') description='Generate an IPC fuzzer based on MojoLPM Generator.')
parser.add_argument(
'-c',
'--context',
default=[],
nargs='+',
required=True,
help="Context bound interfaces to fuzz.")
parser.add_argument( parser.add_argument(
'-p', '-p',
'--process', '--path',
default=[],
nargs='+',
required=True, required=True,
help="Process bound interfaces to fuzz.") help="The path to ipc_interfaces_dumper binary.")
parser.add_argument( parser.add_argument(
'-d', '-d',
'--fuzzer_dir', '--fuzzer_dir',
@ -135,28 +420,42 @@ def main():
help="""The name of the MojoLPMGenerator fuzzing target. help="""The name of the MojoLPMGenerator fuzzing target.
This will used to deduce the name of the generated MojoLPM testcase.""") This will used to deduce the name of the generated MojoLPM testcase.""")
parser.add_argument( parser.add_argument(
'-o', '-t',
'--output', '--testcase-output-path',
required=True, required=True,
help="Output file name.") help="The path where the testcase file will be written to.")
parser.add_argument(
'-i',
'--interface-output-path',
required=True,
help="The path where the interface file will be written to.")
parser.add_argument(
'-r',
'--root-gen-dir',
required=True,
help="The path to the root gen dir.")
parser.add_argument(
'-m',
'--metadata-file',
required=True,
help="Path to the metadata file.")
parser.add_argument(
'-f',
'--depfile',
required=True,
help="The path to the depfile.")
args = parser.parse_args() args = parser.parse_args()
template_dir = os.path.dirname(os.path.abspath(__file__)) generate_interfaces(args.path,
environment = jinja2.Environment(loader=jinja2.FileSystemLoader( args.interface_output_path,
template_dir)) args.root_gen_dir,
template = environment.get_template('testcase.h.tmpl') args.metadata_file,
fuzzer_path = os.path.join(args.fuzzer_dir, args.name) args.depfile)
fuzzer_name = snake_to_camel_case(args.name) generate_testcase(args.interface_output_path,
mojolpm_classname = f"mojolpmgenerator::{fuzzer_name}Testcase" args.fuzzer_dir,
context = { args.name,
"filename": args.output, args.testcase_output_path)
"mojolpm_generator_filepath": f"{fuzzer_path}.h",
"mojolpm_generator_classname": mojolpm_classname,
"process_interfaces": [split_interface_name(p) for p in args.process],
"context_interfaces": [split_interface_name(c) for c in args.context],
}
with pathlib.Path(args.output).open(mode="w") as f:
f.write(template.render(context))
if __name__ == "__main__": if __name__ == "__main__":
main() main()

@ -14,12 +14,14 @@
#include "base/strings/escape.h" #include "base/strings/escape.h"
#include "base/strings/utf_string_conversions.h" #include "base/strings/utf_string_conversions.h"
#include "base/test/bind.h" #include "base/test/bind.h"
#include "base/threading/thread_restrictions.h"
#include "base/time/time.h" #include "base/time/time.h"
#include "base/values.h" #include "base/values.h"
#include "chrome/browser/ui/browser.h" #include "chrome/browser/ui/browser.h"
#include "chrome/test/base/in_process_browser_test.h"
#include "chrome/test/base/ui_test_utils.h" #include "chrome/test/base/ui_test_utils.h"
#include "chrome/test/fuzzing/in_process_fuzzer.h"
#include "content/public/browser/browser_thread.h" #include "content/public/browser/browser_thread.h"
#include "content/public/test/browser_test.h"
// Format for the outputted JSON: // Format for the outputted JSON:
// { // {
@ -41,19 +43,6 @@
// ] // ]
// } // }
class IPCInterfaceDumper : public InProcessFuzzer {
public:
IPCInterfaceDumper() = default;
void RunTestOnMainThread() override;
int Fuzz(const uint8_t* data, size_t size) override { return 0; }
};
// We register this tool as an in_process_fuzzer so that it has the exact same
// setup as an in_process_fuzzer. Indeed, this is important because this can
// have an effect on which interfaces are being exposed.
REGISTER_IN_PROCESS_FUZZER(IPCInterfaceDumper)
namespace { namespace {
void RegisterInterfaces(const std::vector<std::string>& interfaces, void RegisterInterfaces(const std::vector<std::string>& interfaces,
@ -72,7 +61,12 @@ void RegisterInterfaces(const std::vector<std::string>& interfaces,
} // namespace } // namespace
void IPCInterfaceDumper::RunTestOnMainThread() { class IPCInterfacesDumper : public InProcessBrowserTest {
public:
IPCInterfacesDumper() = default;
};
IN_PROC_BROWSER_TEST_F(IPCInterfacesDumper, DumperTest) {
auto env = base::Environment::Create(); auto env = base::Environment::Create();
if (!env->HasVar("IPC_DUMP_PATH")) { if (!env->HasVar("IPC_DUMP_PATH")) {
LOG(ERROR) << "IPC_DUMP_PATH not set. Nothing will be done."; LOG(ERROR) << "IPC_DUMP_PATH not set. Nothing will be done.";
@ -107,6 +101,8 @@ void IPCInterfaceDumper::RunTestOnMainThread() {
// Write the JSON to a file in the IPC_DUMP_PATH directory. // Write the JSON to a file in the IPC_DUMP_PATH directory.
std::string file_path; std::string file_path;
env->GetVar("IPC_DUMP_PATH", &file_path); env->GetVar("IPC_DUMP_PATH", &file_path);
base::ScopedAllowBlockingForTesting allow_blocking;
#if BUILDFLAG(IS_WIN) #if BUILDFLAG(IS_WIN)
base::FilePath filepath = base::FilePath(base::UTF8ToWide(file_path)); base::FilePath filepath = base::FilePath(base::UTF8ToWide(file_path));
#else #else

File diff suppressed because it is too large Load Diff

@ -196,6 +196,17 @@ template("mojolpm_fuzzer_test") {
# lists. # lists.
# The remote type must be either "Remote" or "AssociatedRemote". # The remote type must be either "Remote" or "AssociatedRemote".
# #
# interface_file
# JSON file containing the list of interfaces to generate.
# Format:
# {
# "interfaces": {
# ["InterfaceName", "//path/to/interface.mojom", "Remote"],
# ["AnotherInterface", "//path/to/another.mojom", "AssociatedRemote" ],
# ...
# }
# }
#
# deps # deps
# List of dependencies to compile this target. # List of dependencies to compile this target.
# #
@ -236,19 +247,22 @@ template("mojolpm_fuzzer_test") {
template("mojolpm_generated_fuzzer") { template("mojolpm_generated_fuzzer") {
assert(defined(invoker.sources), assert(defined(invoker.sources),
"\"sources\" must be defined for $target_name") "\"sources\" must be defined for $target_name")
assert(defined(invoker.interfaces), assert(
"\"interfaces\" must be defined for $target_name") defined(invoker.interfaces) || defined(invoker.interface_file),
"\"interfaces\" or \"interface_file\" must be defined for $target_name")
if (enable_mojom_fuzzer) { if (enable_mojom_fuzzer) {
_target_name = target_name _target_name = target_name
_generate_target_name = _target_name + "_mojolpm_generator_generate" _generate_target_name = _target_name + "_mojolpm_generator_generate"
# Generates the correct argument format give invoker.interfaces. if (defined(invoker.interfaces)) {
_script_inputs = [] # Generates the correct argument format give invoker.interfaces.
foreach(elt, invoker.interfaces) { _script_inputs = []
_script_inputs += foreach(elt, invoker.interfaces) {
[ rebase_path("$root_gen_dir/" + elt[0] + "-module", root_build_dir) + _script_inputs +=
":" + elt[1] + ":" + elt[2] ] [ rebase_path("$root_gen_dir/" + elt[0] + "-module",
root_build_dir) + ":" + elt[1] + ":" + elt[2] ]
}
} }
action(_generate_target_name) { action(_generate_target_name) {
@ -261,8 +275,16 @@ template("mojolpm_generated_fuzzer") {
deps += invoker.proto_deps deps += invoker.proto_deps
} }
args = [ "--input" ] args = []
args += _script_inputs if (defined(invoker.interfaces)) {
args += [ "--input" ]
args += _script_inputs
} else if (defined(invoker.interface_file)) {
args += [
"-f",
rebase_path(invoker.interface_file, root_build_dir),
]
}
args += [ args += [
"--output_file_format", "--output_file_format",
rebase_path("${target_gen_dir}/${_target_name}", root_build_dir), rebase_path("${target_gen_dir}/${_target_name}", root_build_dir),

@ -45,6 +45,7 @@ from __future__ import annotations
import abc import abc
import argparse import argparse
import dataclasses import dataclasses
import json
import os import os
import pathlib import pathlib
import re import re
@ -62,6 +63,7 @@ from mojom import fileutil
from mojom.generate import module from mojom.generate import module
fileutil.AddLocalRepoThirdPartyDirToModulePath() fileutil.AddLocalRepoThirdPartyDirToModulePath()
CHROME_SRC_DIR = fileutil._GetDirAbove('mojo')
import jinja2 import jinja2
@ -654,17 +656,48 @@ def build(interface: module.Interface,
return actions return actions
def get_interface_list_from_file(
file_path: str) -> typing.List[typing.List[str]]:
"""Reads the JSON input file and returns the interfaces list that it
contains.
Args:
file_path: the path to the input file.
Returns:
the list of interfaces.
"""
with open(file_path, 'r') as f:
data = json.load(f)
return data['interfaces']
def get_interface_list_from_input(
interfaces: typing.List[str]) -> typing.List[typing.List[str]]:
"""Parses the input list of interfaces and returns a list of list that
matches the expected format.
Args:
interfaces: the list of strings listing the interfaces.
Returns:
the list of interfaces.
"""
return [interface.split(':') for interface in interfaces]
def main(): def main():
parser = argparse.ArgumentParser( parser = argparse.ArgumentParser(
description='Generate MojoLPM proto and cpp/h files.') description='Generate MojoLPM proto and cpp/h files.')
parser.add_argument( group = parser.add_mutually_exclusive_group(required=True)
group.add_argument(
'-i', '-i',
'--input', '--input',
default=[], default=[],
nargs='+', nargs='+',
required=True,
help="input(s) with format: " help="input(s) with format: "
"path/to/interface.mojom-module:InterfaceName:{Remote|AssociatedRemote}") "path/to/interface.mojom-module:InterfaceName:{Remote|AssociatedRemote}")
group.add_argument('-f', '--file', help="")
parser.add_argument('--output_file_format', parser.add_argument('--output_file_format',
required=True, required=True,
help="output file format. Files with extensions '.h' and" help="output file format. Files with extensions '.h' and"
@ -677,11 +710,11 @@ def main():
[MojoLPMProtoGenerator(output_file), [MojoLPMProtoGenerator(output_file),
MojoLPMCppGenerator(output_file)]) MojoLPMCppGenerator(output_file)])
actions = MojoLPMActionSet() actions = MojoLPMActionSet()
for file_interface in args.input: if args.file:
custom_format = file_interface.split(':') interfaces = get_interface_list_from_file(args.file)
if len(custom_format) != 3: else:
print(f"Wrong format: {file_interface}. See help for usage.") interfaces = get_interface_list_from_input(args.input)
return for custom_format in interfaces:
(file, interface_name, remote_type_str) = custom_format (file, interface_name, remote_type_str) = custom_format
if remote_type_str == 'Remote': if remote_type_str == 'Remote':
remote_type = MojoLPMActionType.REMOTE_ACTION remote_type = MojoLPMActionType.REMOTE_ACTION