chromium/build/config/sanitizers/BUILD.gn

# Copyright 2014 The Chromium Authors
# Use of this source code is governed by a BSD-style license that can be
# found in the LICENSE file.

import("//build/config/cast.gni")
import("//build/config/chrome_build.gni")
import("//build/config/clang/clang.gni")
import("//build/config/rust.gni")
import("//build/config/sanitizers/sanitizers.gni")
import("//build/toolchain/toolchain.gni")
import("//build_overrides/build.gni")

if (is_ios) {
  import("//build/config/ios/ios_sdk.gni")
}

# libfuzzer can't cope with shared objects being unloaded, which sometimes
# occurs for large fuzzers that involve our graphics stack. Shim out dlclose
# so that this doesn't occur.
#
# dlclose() is defined by POSIX, making `is_posix` a tempting condition to use.
# However the linker does not necessarily support `-wrap` on all POSIX
# platforms, hence the restriction to Linux and ChromeOS. We might eventually
# want to disable shared library unloading on all platforms, but we have not
# noticed a need for it as the known-affected large fuzzers only run on Linux.
use_dlcloseshim = use_libfuzzer && (is_linux || is_chromeos)

# Contains the dependencies needed for sanitizers to link into executables and
# shared_libraries.
group("deps") {
  deps = []
  data = []
  if (using_sanitizer) {
    public_configs = [
      # Even when a target removes default_sanitizer_flags, it may be depending
      # on a library that did not remove default_sanitizer_flags. Thus, we need
      # to add the ldflags here as well as in default_sanitizer_flags.
      ":default_sanitizer_ldflags",
    ]
    if (!is_fuchsia) {
      if (is_win) {
        exe = ".exe"
      } else {
        exe = ""
      }
      data += [
        "//tools/valgrind/asan/",
        "$clang_base_path/bin/llvm-symbolizer${exe}",
      ]
    }
    if (is_asan || is_lsan || is_msan || is_tsan || is_ubsan || is_ubsan_vptr ||
        is_ubsan_security) {
      public_configs += [ ":sanitizer_options_link_helper" ]
      deps += [ ":options_sources" ]
    }
    if (use_prebuilt_instrumented_libraries ||
        use_locally_built_instrumented_libraries) {
      deps += [ "//third_party/instrumented_libs:deps" ]
    }
  }
  if (fail_on_san_warnings) {
    data += [ "//tools/memory/sanitizer/escalate_sanitizer_warnings.py" ]
  }
  if (is_asan || is_ubsan || is_ubsan_vptr || is_ubsan_security) {
    if ((is_win && is_component_build) || is_apple) {
      data_deps = [ ":copy_sanitizer_runtime" ]
    }
    if (is_apple) {
      public_deps = [ ":sanitizer_runtime_bundle_data" ]
    }
  }
  if (use_centipede || enable_fuzztest_fuzz) {
    # For executables which aren't actual fuzzers, we need stubs for
    # the sanitizer coverage symbols, because we'll still be generating
    # .o files which depend on them.
    deps += [ "//third_party/fuzztest:centipede_weak_sancov_stubs" ]
  }
}

assert(!(is_win && is_asan && current_cpu == "x86"),
       "ASan is only supported in 64-bit builds on Windows.")

if ((is_apple || (is_win && is_component_build)) &&
    (is_asan || is_ubsan || is_ubsan_vptr || is_ubsan_security)) {
  if (is_mac || (is_ios && target_environment == "catalyst")) {
    if (is_asan) {
      _clang_rt_dso_path = "darwin/libclang_rt.asan_osx_dynamic.dylib"
    } else {
      assert(is_ubsan || is_ubsan_vptr || is_ubsan_security)
      _clang_rt_dso_path = "darwin/libclang_rt.ubsan_osx_dynamic.dylib"
    }
  } else if (is_ios) {
    if (is_asan) {
      _clang_rt_dso_path = "darwin/libclang_rt.asan_iossim_dynamic.dylib"
    } else {
      assert(is_ubsan || is_ubsan_vptr || is_ubsan_security)
      _clang_rt_dso_path = "darwin/libclang_rt.ubsan_iossim_dynamic.dylib"
    }
  } else if (is_win && current_cpu == "x64" && is_component_build) {
    if (is_asan) {
      _clang_rt_dso_path = "windows/clang_rt.asan_dynamic-x86_64.dll"
    } else {
      assert(is_ubsan || is_ubsan_vptr || is_ubsan_security)
      _clang_rt_dso_path = "windows/clang_rt.ubsan_dynamic-x86_64.dll"
    }
  }

  _clang_rt_dso_full_path =
      "$clang_base_path/lib/clang/$clang_version/lib/$_clang_rt_dso_path"

  if (!is_ios) {
    copy("copy_sanitizer_runtime") {
      sources = [ _clang_rt_dso_full_path ]
      outputs = [ "$root_out_dir/{{source_file_part}}" ]
    }
  } else {
    # On iOS, the runtime library need to be code signed (adhoc signature)
    # starting with Xcode 8, so use an action instead of a copy on iOS.
    action("copy_sanitizer_runtime") {
      script = "//build/config/apple/codesign.py"
      sources = [ _clang_rt_dso_full_path ]
      outputs = [ "$root_out_dir/" + get_path_info(sources[0], "file") ]
      args = [
        "code-sign-file",
        "--identity=" + ios_code_signing_identity,
        "--output=" + rebase_path(outputs[0], root_build_dir),
        rebase_path(sources[0], root_build_dir),
      ]
    }
  }

  if (is_apple) {
    bundle_data("sanitizer_runtime_bundle_data") {
      sources = get_target_outputs(":copy_sanitizer_runtime")
      outputs = [ "{{bundle_executable_dir}}/{{source_file_part}}" ]
      public_deps = [ ":copy_sanitizer_runtime" ]
    }
  }
}

config("sanitizer_options_link_helper") {
  if (is_apple) {
    ldflags = [ "-Wl,-u,__sanitizer_options_link_helper" ]
  } else if (!is_win) {
    ldflags = [ "-Wl,-u_sanitizer_options_link_helper" ]
  }
}

static_library("options_sources") {
  # This is a static_library instead of a source_set, as it shouldn't be
  # unconditionally linked into targets.
  visibility = [
    ":deps",
    "//:gn_all",
  ]
  sources = [ "//build/sanitizers/sanitizer_options.cc" ]

  # Don't compile this target with any sanitizer code. It can be called from
  # the sanitizer runtimes, so instrumenting these functions could cause
  # recursive calls into the runtime if there is an error.
  configs -= [ "//build/config/sanitizers:default_sanitizer_flags" ]

  if (is_asan) {
    if (!defined(asan_suppressions_file)) {
      asan_suppressions_file = "//build/sanitizers/asan_suppressions.cc"
    }
    sources += [ asan_suppressions_file ]
  }

  if (is_lsan) {
    if (!defined(lsan_suppressions_file)) {
      lsan_suppressions_file = "//build/sanitizers/lsan_suppressions.cc"
    }
    sources += [ lsan_suppressions_file ]
  }

  if (is_tsan) {
    if (!defined(tsan_suppressions_file)) {
      tsan_suppressions_file = "//build/sanitizers/tsan_suppressions.cc"
    }
    sources += [ tsan_suppressions_file ]
  }
}

if (use_dlcloseshim) {
  source_set("dlclose_shim") {
    sources = [ "//build/sanitizers/dlcloseshim.c" ]
  }
} else {
  group("dlclose_shim") {
    deps = []
  }
}

# Applies linker flags necessary when either :deps or :default_sanitizer_flags
# are used.
config("default_sanitizer_ldflags") {
  visibility = [
    ":default_sanitizer_flags",
    ":deps",

    # https://crbug.com/360158.
    "//tools/ipc_fuzzer/fuzzer:ipc_fuzzer",
  ]

  if (is_posix || is_fuchsia) {
    sanitizers = []  # sanitizers applicable to both clang and rustc
    ldflags = []
    rustflags = []
    if (is_asan) {
      sanitizers += [ "address" ]
    }
    if (is_hwasan) {
      sanitizers += [ "hwaddress" ]
    }
    if (is_lsan) {
      # In Chromium, is_lsan always implies is_asan. ASAN includes LSAN.
      # It seems harmless to pass both options to clang, but it doesn't
      # work on rustc, so apply this option to clang only.
      ldflags += [ "-fsanitize=leak" ]
    }
    if (is_tsan) {
      sanitizers += [ "thread" ]
    }
    if (is_msan) {
      sanitizers += [ "memory" ]
    }
    if (is_ubsan || is_ubsan_security) {
      ldflags += [ "-fsanitize=undefined" ]
    }
    if (is_ubsan_vptr) {
      ldflags += [ "-fsanitize=vptr" ]
    }
    foreach(sanitizer, sanitizers) {
      ldflags += [ "-fsanitize=$sanitizer" ]
      rustflags += [ "-Zsanitizer=$sanitizer" ]
    }

    if (use_sanitizer_coverage) {
      if (use_libfuzzer) {
        ldflags += [ "-fsanitize=fuzzer-no-link" ]
        if (is_mac) {
          # TODO(crbug.com/40611636): on macOS, dead code stripping does not work
          # well with `pc-table` instrumentation enabled by `fuzzer-no-link`.
          ldflags += [ "-fno-sanitize-coverage=pc-table" ]
        }
      } else {
        ldflags += [ "-fsanitize-coverage=$sanitizer_coverage_flags" ]
      }
    }

    if (is_cfi && current_toolchain == default_toolchain) {
      ldflags += [ "-fsanitize=cfi-vcall" ]
      if (use_cfi_cast) {
        ldflags += [
          "-fsanitize=cfi-derived-cast",
          "-fsanitize=cfi-unrelated-cast",
        ]
      }
      if (use_cfi_icall) {
        ldflags += [ "-fsanitize=cfi-icall" ]
      }
      if (use_cfi_diag) {
        ldflags += [ "-fno-sanitize-trap=cfi" ]
        if (use_cfi_recover) {
          ldflags += [ "-fsanitize-recover=cfi" ]
        }
      }
    }
  } else if (is_win) {
    # Windows directly calls link.exe instead of the compiler driver when
    # linking.  Hence, pass the runtime libraries instead of -fsanitize=address
    # or -fsanitize=fuzzer.
    if (is_asan && is_component_build) {
      # In the static-library build, ASan libraries are different for
      # executables and dlls, see link_executable and link_shared_library below.
      # This here handles only the component build.
      assert(current_cpu == "x64", "WinASan unsupported architecture")
      libs = [
        "clang_rt.asan_dynamic-x86_64.lib",
        "clang_rt.asan_dynamic_runtime_thunk-x86_64.lib",
      ]
    }
    if (use_libfuzzer) {
      assert(current_cpu == "x64", "LibFuzzer unsupported architecture")
      assert(!is_component_build,
             "LibFuzzer only supports non-component builds on Windows")

      # Incremental linking causes padding that messes up SanitizerCoverage.
      # Don't do it.
      ldflags = [ "/INCREMENTAL:NO" ]
    }
  }
  if (use_dlcloseshim) {
    ldflags += [ "-Wl,-wrap,dlclose" ]
  }
}

config("common_sanitizer_flags") {
  cflags = []

  if (using_sanitizer) {
    assert(is_clang, "sanitizers only supported with clang")

    # Allow non-default toolchains to enable sanitizers in toolchain_args even
    # in official builds.
    assert(current_toolchain != default_toolchain || !is_official_build,
           "sanitizers not supported in official builds")

    cflags += [
      # Column info in debug data confuses Visual Studio's debugger, so don't
      # use this by default.  However, clusterfuzz needs it for good
      # attribution of reports to CLs, so turn it on there.
      "-gcolumn-info",
    ]

    # Frame pointers are controlled in //build/config/compiler:default_stack_frames
  }
}

config("asan_flags") {
  cflags = []
  if (is_asan) {
    cflags += [ "-fsanitize=address" ]
    if (!is_win && !is_apple && !is_fuchsia) {
      # TODO(crbug.com/1459233, crbug.com/1462248): This causes asan
      # odr-violation errors in rust code, and link failures for cros/asan.
      # Clang recently turned it on by default for all ELF targets (it was
      # already on for Fuchsia). Pass the flag to turn it back off.
      cflags += [ "-fno-sanitize-address-globals-dead-stripping" ]
    }
    if (is_win) {
      if (!defined(asan_win_blocklist_path)) {
        asan_win_blocklist_path =
            rebase_path("//tools/memory/asan/blocklist_win.txt", root_build_dir)
      }
      cflags += [ "-fsanitize-ignorelist=$asan_win_blocklist_path" ]
    }
  }
}

config("link_executable") {
  if (is_asan && is_win && !is_component_build) {
    assert(current_cpu == "x64", "WinASan unsupported architecture")
    ldflags = [ "-wholearchive:clang_rt.asan-x86_64.lib" ]
  }
}

config("link_shared_library") {
  if (is_asan && is_win && !is_component_build) {
    assert(current_cpu == "x64", "WinASan unsupported architecture")
    libs = [ "clang_rt.asan_dll_thunk-x86_64.lib" ]
  }
}

config("cfi_flags") {
  cflags = []
  rustflags = []
  if (is_cfi && current_toolchain == default_toolchain) {
    if (!defined(cfi_ignorelist_path)) {
      cfi_ignorelist_path =
          rebase_path("//tools/cfi/ignores.txt", root_build_dir)
    }
    cflags += [
      "-fsanitize=cfi-vcall",
      "-fsanitize-ignorelist=$cfi_ignorelist_path",
    ]

    if (toolchain_supports_rust_thin_lto) {
      # sanitize=cfi implies -fsplit-lto-unit, and Rust needs to match
      # behaviour.  Rust needs to know the linker will be doing LTO in this case
      # or it rejects the Zsplit-lto-unit flag.
      # TODO(crbug.com/40266913): Add -Zsanitize=cfi instead.
      rustflags += [
        "-Zsplit-lto-unit",
        "-Clinker-plugin-lto=yes",
      ]
    } else {
      # Don't include bitcode if it won't be used.
      rustflags += [ "-Cembed-bitcode=no" ]
    }

    if (use_cfi_cast) {
      cflags += [
        "-fsanitize=cfi-derived-cast",
        "-fsanitize=cfi-unrelated-cast",
      ]
    }

    if (use_cfi_icall) {
      cflags += [ "-fsanitize=cfi-icall" ]
      # TODO(crbug.com/40266913): Add cflags += [
      # "-fsanitize-cfi-icall-experimental-normalize-integers" ]
      # TODO(crbug.com/40266913): Add rustflags += [
      # "-Zsanitizer-cfi-normalize-integers" ].
    }

    if (use_cfi_diag) {
      cflags += [ "-fno-sanitize-trap=cfi" ]
      if (is_win) {
        cflags += [
          "/Oy-",
          "/Ob0",
        ]
      } else {
        cflags += [
          "-fno-inline-functions",
          "-fno-inline",
          "-fno-omit-frame-pointer",
          "-O1",
        ]
      }
      if (use_cfi_recover) {
        cflags += [ "-fsanitize-recover=cfi" ]
      }
    }
  }
}

# crbug.com/785442: Fix cfi-icall failures for code that casts pointer argument
# types in function pointer type signatures.
config("cfi_icall_generalize_pointers") {
  if (is_clang && is_cfi && use_cfi_icall) {
    cflags = [ "-fsanitize-cfi-icall-generalize-pointers" ]
  }
}

config("cfi_icall_disable") {
  if (is_clang && is_cfi && use_cfi_icall) {
    cflags = [ "-fno-sanitize=cfi-icall" ]
  }
}

config("coverage_flags") {
  cflags = []
  if (use_sanitizer_coverage) {
    # Used by sandboxing code to allow coverage dump to be written on the disk.
    defines = [ "SANITIZER_COVERAGE" ]

    if (use_libfuzzer) {
      cflags += [ "-fsanitize=fuzzer-no-link" ]
      if (is_mac) {
        # TODO(crbug.com/40611636): on macOS, dead code stripping does not work
        # well with `pc-table` instrumentation enabled by `fuzzer-no-link`.
        cflags += [ "-fno-sanitize-coverage=pc-table" ]
      }
    } else {
      cflags += [
        "-fsanitize-coverage=$sanitizer_coverage_flags",
        "-mllvm",
        "-sanitizer-coverage-prune-blocks=1",
      ]
      if (current_cpu == "arm") {
        # http://crbug.com/517105
        cflags += [
          "-mllvm",
          "-sanitizer-coverage-block-threshold=0",
        ]
      }
    }
    if (sanitizer_coverage_allowlist != "") {
      cflags += [ "-fsanitize-coverage-allowlist=" +
                  rebase_path(sanitizer_coverage_allowlist, root_build_dir) ]
    }
  }
  if (use_centipede) {
    # Centipede intercepts calls such as memcmp and memcpy in order to improve
    # its testcase generation.
    cflags += [ "-fno-builtin" ]
  }
}

config("hwasan_flags") {
  if (is_hwasan) {
    asmflags = [ "-fsanitize=hwaddress" ]
    cflags = [ "-fsanitize=hwaddress" ]
  }
}

config("lsan_flags") {
  if (is_lsan) {
    cflags = [ "-fsanitize=leak" ]
  }
}

config("msan_flags") {
  if (is_msan) {
    assert(is_linux || is_chromeos,
           "msan only supported on linux x86_64/ChromeOS")
    if (!defined(msan_ignorelist_path)) {
      msan_ignorelist_path =
          rebase_path("//tools/msan/ignorelist.txt", root_build_dir)
    }
    cflags = [
      "-fsanitize=memory",
      "-fsanitize-memory-track-origins=$msan_track_origins",
      "-fsanitize-ignorelist=$msan_ignorelist_path",
    ]

    if (!msan_check_use_after_dtor) {
      # TODO(crbug.com/40222690): evaluate and possibly enable
      cflags += [ "-fno-sanitize-memory-use-after-dtor" ]
    }

    if (!msan_eager_checks) {
      cflags += [ "-fno-sanitize-memory-param-retval" ]
    }
  }
}

config("tsan_flags") {
  if (is_tsan) {
    assert(is_linux || is_chromeos, "tsan only supported on linux x86_64")
    if (!defined(tsan_ignorelist_path)) {
      tsan_ignorelist_path =
          rebase_path("//tools/memory/tsan_v2/ignores.txt", root_build_dir)
    }
    cflags = [
      "-fsanitize=thread",
      "-fsanitize-ignorelist=$tsan_ignorelist_path",
    ]
  }
}

config("ubsan_flags") {
  cflags = []
  if (is_ubsan) {
    if (!defined(ubsan_ignorelist_path)) {
      ubsan_ignorelist_path =
          rebase_path("//tools/ubsan/ignorelist.txt", root_build_dir)
    }

    # TODO(crbug.com/40942951): Enable all of -fsanitize=undefined. Note that
    # both this list and Clang's defaults omit -fsanitize=float-divide-by-zero.
    # C and C++ leave it undefined to accommodate non-IEEE floating point, but
    # we assume the compiler implements IEEE floating point, which does define
    # division by zero.
    cflags += [
      "-fsanitize=alignment",
      "-fsanitize=bool",
      "-fsanitize=bounds",
      "-fsanitize=builtin",
      "-fsanitize=integer-divide-by-zero",
      "-fsanitize=null",
      "-fsanitize=nonnull-attribute",
      "-fsanitize=object-size",
      "-fsanitize=return",
      "-fsanitize=returns-nonnull-attribute",
      "-fsanitize=shift",
      "-fsanitize=signed-integer-overflow",
      "-fsanitize=unreachable",
      "-fsanitize=vla-bound",
      "-fsanitize-ignorelist=$ubsan_ignorelist_path",
    ]
  }
}

config("ubsan_no_recover") {
  if (is_ubsan_no_recover) {
    cflags = [ "-fno-sanitize-recover=undefined" ]
  }
}

config("ubsan_security_flags") {
  if (is_ubsan_security) {
    if (!defined(ubsan_security_ignorelist_path)) {
      ubsan_security_ignorelist_path =
          rebase_path("//tools/ubsan/security_ignorelist.txt", root_build_dir)
    }
    cflags = [
      "-fsanitize=function",
      "-fsanitize=shift",
      "-fsanitize=signed-integer-overflow",
      "-fsanitize=vla-bound",
      "-fsanitize-ignorelist=$ubsan_security_ignorelist_path",
    ]
  }
}

config("ubsan_vptr_flags") {
  if (is_ubsan_vptr) {
    if (!defined(ubsan_vptr_ignorelist_path)) {
      ubsan_vptr_ignorelist_path =
          rebase_path("//tools/ubsan/vptr_ignorelist.txt", root_build_dir)
    }
    cflags = [
      "-fsanitize=vptr",
      "-fsanitize-ignorelist=$ubsan_vptr_ignorelist_path",
    ]
  }
}

config("fuzzing_build_mode") {
  if (use_fuzzing_engine) {
    defines = [ "FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION" ]
  }
}

all_sanitizer_configs = [
  ":common_sanitizer_flags",
  ":coverage_flags",
  ":default_sanitizer_ldflags",
  ":asan_flags",
  ":cfi_flags",
  ":hwasan_flags",
  ":lsan_flags",
  ":msan_flags",
  ":tsan_flags",
  ":ubsan_flags",
  ":ubsan_no_recover",
  ":ubsan_security_flags",
  ":ubsan_vptr_flags",
  ":fuzzing_build_mode",
]

# This config is applied by default to all targets. It sets the compiler flags
# for sanitizer usage, or, if no sanitizer is set, does nothing.
#
# This needs to be in a separate config so that targets can opt out of
# sanitizers (by removing the config) if they desire. Even if a target
# removes this config, executables & shared libraries should still depend on
# :deps if any of their dependencies have not opted out of sanitizers.
# Keep this list in sync with default_sanitizer_flags_but_ubsan_vptr.
config("default_sanitizer_flags") {
  configs = all_sanitizer_configs

  if (use_sanitizer_configs_without_instrumentation) {
    configs = []
  }
}

# This config is equivalent to default_sanitizer_flags, but excludes ubsan_vptr.
# This allows to selectively disable ubsan_vptr, when needed. In particular,
# if some third_party code is required to be compiled without rtti, which
# is a requirement for ubsan_vptr.
config("default_sanitizer_flags_but_ubsan_vptr") {
  configs = all_sanitizer_configs - [ ":ubsan_vptr_flags" ]

  if (use_sanitizer_configs_without_instrumentation) {
    configs = []
  }
}

config("default_sanitizer_flags_but_coverage") {
  configs = all_sanitizer_configs - [ ":coverage_flags" ]

  if (use_sanitizer_configs_without_instrumentation) {
    configs = []
  }
}

# This config is used by parts of code that aren't targeted in fuzzers and
# therefore don't need coverage instrumentation and possibly wont need
# sanitizer instrumentation either. The config also tells the compiler to
# perform additional optimizations on the configured code and ensures that
# linking it to the rest of the binary which is instrumented with sanitizers
# works. The config only does anything if the build is a fuzzing build.
config("not_fuzzed") {
  if (use_fuzzing_engine) {
    # Since we aren't instrumenting with coverage, code size is less of a
    # concern, so use a more aggressive optimization level than
    # optimize_for_fuzzing (-O1). When given multiple optimization flags, clang
    # obeys the last one, so as long as this flag comes after -O1, it should work.
    # Since this config will always be depended on after
    # "//build/config/compiler:default_optimization" (which adds -O1 when
    # optimize_for_fuzzing is true), -O2 should always be the second flag. Even
    # though this sounds fragile, it isn't a big deal if it breaks, since proto
    # fuzzers will still work, they will just be slightly slower.
    cflags = [ "-O2" ]

    # We need to include this config when we remove default_sanitizer_flags or
    # else there will be linking errors. We would remove default_sanitizer_flags
    # here as well, but gn doesn't permit this.
    if (!is_msan) {
      # We don't actually remove sanitization when MSan is being used so there
      # is no need to add default_sanitizer_ldflags in that case
      configs = [ ":default_sanitizer_ldflags" ]
    }
  }
}