mirror of
https://git.proxmox.com/git/fwupd
synced 2025-05-29 16:38:47 +00:00
337 lines
12 KiB
Python
Executable File
337 lines
12 KiB
Python
Executable File
#!/usr/bin/python3
|
|
# pylint: disable=invalid-name,missing-docstring
|
|
#
|
|
# Copyright (C) 2021 Richard Hughes <richard@hughsie.com>
|
|
#
|
|
# SPDX-License-Identifier: LGPL-2.1+
|
|
#
|
|
# pylint: disable=too-many-instance-attributes,no-self-use
|
|
|
|
import os
|
|
import sys
|
|
import subprocess
|
|
import glob
|
|
from typing import Dict, Optional, List, Union
|
|
|
|
DEFAULT_BUILDDIR = ".ossfuzz"
|
|
|
|
|
|
class Builder:
|
|
def __init__(self) -> None:
|
|
|
|
self.cc = self._ensure_environ("CC", "gcc")
|
|
self.cxx = self._ensure_environ("CXX", "g++")
|
|
self.builddir = self._ensure_environ("WORK", os.path.realpath(DEFAULT_BUILDDIR))
|
|
self.installdir = self._ensure_environ(
|
|
"OUT", os.path.realpath(os.path.join(DEFAULT_BUILDDIR, "out"))
|
|
)
|
|
self.srcdir = self._ensure_environ("SRC", os.path.realpath(".."))
|
|
self.ldflags = ["-lpthread", "-lresolv", "-ldl", "-lffi", "-lz"]
|
|
|
|
# defined in env
|
|
self.cflags = ["-Wno-deprecated-declarations"]
|
|
if "CFLAGS" in os.environ:
|
|
self.cflags += os.environ["CFLAGS"].split(" ")
|
|
self.cxxflags = []
|
|
if "CXXFLAGS" in os.environ:
|
|
self.cxxflags += os.environ["CXXFLAGS"].split(" ")
|
|
|
|
# set up shared / static
|
|
os.environ["PKG_CONFIG"] = "pkg-config --static"
|
|
if "PATH" in os.environ:
|
|
os.environ["PATH"] = "{}:{}".format(
|
|
os.environ["PATH"], os.path.join(self.builddir, "bin")
|
|
)
|
|
else:
|
|
os.environ["PATH"] = os.path.join(self.builddir, "bin")
|
|
os.environ["PKG_CONFIG_PATH"] = os.path.join(self.builddir, "lib", "pkgconfig")
|
|
|
|
# writable
|
|
os.makedirs(self.builddir, exist_ok=True)
|
|
os.makedirs(self.installdir, exist_ok=True)
|
|
|
|
def _ensure_environ(self, key: str, value: str) -> str:
|
|
""" set the environment unless already set """
|
|
if key not in os.environ:
|
|
os.environ[key] = value
|
|
return os.environ[key]
|
|
|
|
def checkout_source(self, name: str, url: str, commit: Optional[str] = None) -> str:
|
|
""" checkout source tree, optionally to a specific commit """
|
|
srcdir_name = os.path.join(self.srcdir, name)
|
|
if os.path.exists(srcdir_name):
|
|
return srcdir_name
|
|
subprocess.run(["git", "clone", url], cwd=self.srcdir, check=True)
|
|
if commit:
|
|
subprocess.run(["git", "checkout", commit], cwd=srcdir_name, check=True)
|
|
return srcdir_name
|
|
|
|
def build_meson_project(self, srcdir: str, argv) -> None:
|
|
""" configure and build the meson project """
|
|
srcdir_build = os.path.join(srcdir, DEFAULT_BUILDDIR)
|
|
if not os.path.exists(srcdir_build):
|
|
subprocess.run(
|
|
[
|
|
"meson",
|
|
"--prefix",
|
|
self.builddir,
|
|
"--libdir",
|
|
"lib",
|
|
"--default-library",
|
|
"static",
|
|
]
|
|
+ argv
|
|
+ [DEFAULT_BUILDDIR],
|
|
cwd=srcdir,
|
|
check=True,
|
|
)
|
|
subprocess.run(["ninja", "install"], cwd=srcdir_build, check=True)
|
|
|
|
def add_work_includedir(self, value: str) -> None:
|
|
""" add a CFLAG """
|
|
self.cflags.append("-I{}/{}".format(self.builddir, value))
|
|
|
|
def add_src_includedir(self, value: str) -> None:
|
|
""" add a CFLAG """
|
|
self.cflags.append("-I{}/{}".format(self.srcdir, value))
|
|
|
|
def add_build_ldflag(self, value: str) -> None:
|
|
""" add a LDFLAG """
|
|
self.ldflags.append(os.path.join(self.builddir, value))
|
|
|
|
def substitute(self, src: str, replacements: Dict[str, str]) -> str:
|
|
""" map changes """
|
|
|
|
dst = os.path.basename(src).replace(".in", "")
|
|
with open(os.path.join(self.srcdir, src), "r") as f:
|
|
blob = f.read()
|
|
for key in replacements:
|
|
blob = blob.replace(key, replacements[key])
|
|
with open(os.path.join(self.builddir, dst), "w") as out:
|
|
out.write(blob)
|
|
return dst
|
|
|
|
def compile(self, src: str) -> str:
|
|
""" compile a specific source file """
|
|
argv = [self.cc]
|
|
argv.extend(self.cflags)
|
|
fullsrc = os.path.join(self.srcdir, src)
|
|
if not os.path.exists(fullsrc):
|
|
fullsrc = os.path.join(self.builddir, src)
|
|
dst = os.path.basename(src).replace(".c", ".o")
|
|
argv.extend(["-c", fullsrc, "-o", os.path.join(self.builddir, dst)])
|
|
print("building {} into {}".format(src, dst))
|
|
try:
|
|
subprocess.run(argv, cwd=self.srcdir, check=True)
|
|
except subprocess.CalledProcessError as e:
|
|
print(e)
|
|
sys.exit(1)
|
|
return os.path.join(self.builddir, "{}".format(dst))
|
|
|
|
def link(self, objs: List[str], dst: str) -> None:
|
|
""" link multiple obects into a binary """
|
|
argv = [self.cxx] + self.cxxflags
|
|
for obj in objs:
|
|
if obj.startswith("-"):
|
|
argv.append(obj)
|
|
else:
|
|
argv.append(os.path.join(self.builddir, obj))
|
|
argv += ["-o", os.path.join(self.installdir, dst)]
|
|
argv += self.ldflags
|
|
print("building {} into {}".format(",".join(objs), dst))
|
|
subprocess.run(argv, cwd=self.srcdir, check=True)
|
|
|
|
def write_header(
|
|
self, dst: str, defines: Dict[str, Optional[Union[str, int]]]
|
|
) -> None:
|
|
""" write a header file """
|
|
dstdir = os.path.join(self.builddir, os.path.dirname(dst))
|
|
os.makedirs(dstdir, exist_ok=True)
|
|
print("writing {}".format(dst))
|
|
with open(os.path.join(dstdir, os.path.basename(dst)), "w") as f:
|
|
for key in defines:
|
|
value = defines[key]
|
|
if value is not None:
|
|
if isinstance(value, int):
|
|
f.write("#define {} {}\n".format(key, value))
|
|
else:
|
|
f.write('#define {} "{}"\n'.format(key, value))
|
|
else:
|
|
f.write("#define {}\n".format(key))
|
|
self.add_work_includedir(os.path.dirname(dst))
|
|
|
|
def makezip(self, dst: str, globstr: str) -> None:
|
|
""" create a zip file archive from a glob """
|
|
argv = ["zip", "--junk-paths", os.path.join(self.installdir, dst)] + glob.glob(
|
|
os.path.join(self.srcdir, globstr)
|
|
)
|
|
print("assembling {}".format(dst))
|
|
subprocess.run(argv, cwd=self.srcdir, check=True)
|
|
|
|
def grep_meson(self, src: str, token: str = "fuzzing") -> List[str]:
|
|
""" find source files tagged with a specific comment """
|
|
srcs = []
|
|
with open(os.path.join(self.srcdir, src, "meson.build"), "r") as f:
|
|
for line in f.read().split("\n"):
|
|
if line.find(token) == -1:
|
|
continue
|
|
srcs.append(
|
|
os.path.join(
|
|
src,
|
|
line.strip()
|
|
.replace("'", "")
|
|
.replace(",", "")
|
|
.replace(" ", "")
|
|
.split("#")[0],
|
|
)
|
|
)
|
|
return srcs
|
|
|
|
|
|
class Fuzzer:
|
|
def __init__(self, name, srcdir=None, globstr=None, pattern=None) -> None:
|
|
|
|
self.name = name
|
|
self.srcdir = srcdir or name
|
|
self.globstr = globstr or "{}*".format(name)
|
|
self.pattern = pattern or "{}-firmware".format(name)
|
|
|
|
@property
|
|
def new_gtype(self) -> str:
|
|
return "fu_{}_new".format(self.pattern).replace("-", "_")
|
|
|
|
@property
|
|
def header(self) -> str:
|
|
return "fu-{}.h".format(self.pattern)
|
|
|
|
|
|
def _build(bld: Builder) -> None:
|
|
|
|
# GLib
|
|
src = bld.checkout_source("glib", url="https://gitlab.gnome.org/GNOME/glib.git")
|
|
bld.build_meson_project(
|
|
src,
|
|
[
|
|
"-Dlibmount=disabled",
|
|
"-Dselinux=disabled",
|
|
"-Dnls=disabled",
|
|
"-Dlibelf=disabled",
|
|
"-Dbsymbolic_functions=false",
|
|
"-Dtests=false",
|
|
"-Dinternal_pcre=true",
|
|
],
|
|
)
|
|
bld.add_work_includedir("include/glib-2.0")
|
|
bld.add_work_includedir("lib/glib-2.0/include")
|
|
bld.add_build_ldflag("lib/libgio-2.0.a")
|
|
bld.add_build_ldflag("lib/libgmodule-2.0.a")
|
|
bld.add_build_ldflag("lib/libgobject-2.0.a")
|
|
bld.add_build_ldflag("lib/libglib-2.0.a")
|
|
bld.add_build_ldflag("lib/libgthread-2.0.a")
|
|
|
|
# JSON-GLib
|
|
src = bld.checkout_source(
|
|
"json-glib", url="https://gitlab.gnome.org/GNOME/json-glib.git"
|
|
)
|
|
bld.build_meson_project(
|
|
src, ["-Dgtk_doc=disabled", "-Dtests=false", "-Dintrospection=disabled"]
|
|
)
|
|
bld.add_work_includedir("include/json-glib-1.0/json-glib")
|
|
bld.add_work_includedir("include/json-glib-1.0")
|
|
bld.add_build_ldflag("lib/libjson-glib-1.0.a")
|
|
|
|
# libxmlb
|
|
src = bld.checkout_source("libxmlb", url="https://github.com/hughsie/libxmlb.git")
|
|
bld.build_meson_project(
|
|
src, ["-Dgtkdoc=false", "-Dintrospection=false", "-Dtests=false"]
|
|
)
|
|
bld.add_work_includedir("include/libxmlb-2")
|
|
bld.add_work_includedir("include/libxmlb-2/libxmlb")
|
|
bld.add_build_ldflag("lib/libxmlb.a")
|
|
|
|
# write required headers
|
|
bld.write_header("libfwupd/fwupd-version.h", {})
|
|
bld.write_header(
|
|
"config.h",
|
|
{
|
|
"FWUPD_DATADIR": "/tmp",
|
|
"FWUPD_LOCALSTATEDIR": "/tmp",
|
|
"FWUPD_PLUGINDIR": "/tmp",
|
|
"FWUPD_SYSCONFDIR": "/tmp",
|
|
"HAVE_REALPATH": None,
|
|
"PACKAGE_NAME": "fwupd",
|
|
"PACKAGE_VERSION": "0.0.0",
|
|
},
|
|
)
|
|
|
|
# libfwupd + libfwupdplugin
|
|
built_objs: List[str] = []
|
|
bld.add_src_includedir("fwupd")
|
|
for path in ["fwupd/libfwupd", "fwupd/libfwupdplugin"]:
|
|
bld.add_src_includedir(path)
|
|
for src in bld.grep_meson(path):
|
|
built_objs.append(bld.compile(src))
|
|
|
|
# dummy binary entrypoint
|
|
if "LIB_FUZZING_ENGINE" in os.environ:
|
|
built_objs.append(os.environ["LIB_FUZZING_ENGINE"])
|
|
else:
|
|
built_objs.append(bld.compile("fwupd/libfwupdplugin/fu-fuzzer-main.c"))
|
|
|
|
# built in formats
|
|
for fzr in [Fuzzer("dfuse"), Fuzzer("fmap"), Fuzzer("ihex"), Fuzzer("srec")]:
|
|
src = bld.substitute(
|
|
"fwupd/libfwupdplugin/fu-fuzzer-firmware.c.in",
|
|
{
|
|
"@FIRMWARENEW@": fzr.new_gtype,
|
|
"@INCLUDE@": os.path.join("libfwupdplugin", fzr.header),
|
|
},
|
|
)
|
|
bld.link([bld.compile(src)] + built_objs, "{}_fuzzer".format(fzr.name))
|
|
bld.makezip(
|
|
"{}_fuzzer_seed_corpus.zip".format(fzr.name),
|
|
"fwupd/src/fuzzing/firmware/{}".format(fzr.globstr),
|
|
)
|
|
|
|
# plugins
|
|
for fzr in [
|
|
Fuzzer("bcm57xx"),
|
|
Fuzzer("ccgx-dmc", srcdir="ccgx", globstr="ccgx-dmc*.bin"),
|
|
Fuzzer("ccgx", globstr="ccgx*.cyacd"),
|
|
Fuzzer("cros-ec"),
|
|
Fuzzer("ebitdo"),
|
|
Fuzzer("efi-filesystem", srcdir="intel-spi", pattern="efi-firmware-filesystem"),
|
|
Fuzzer("efi-volume", srcdir="intel-spi", pattern="efi-firmware-volume"),
|
|
Fuzzer("elantp"),
|
|
Fuzzer("hailuck-kbd", srcdir="hailuck", globstr="ihex*"),
|
|
Fuzzer("ifd", srcdir="intel-spi"),
|
|
Fuzzer("pixart", srcdir="pixart-rf", pattern="pxi-firmware"),
|
|
Fuzzer("solokey"),
|
|
Fuzzer("synaprom", srcdir="synaptics-prometheus"),
|
|
Fuzzer("synaptics-mst"),
|
|
Fuzzer("synaptics-rmi"),
|
|
Fuzzer("wacom-usb", pattern="wac-firmware", globstr="wacom*"),
|
|
]:
|
|
fuzz_objs = []
|
|
for obj in bld.grep_meson("fwupd/plugins/{}".format(fzr.srcdir)):
|
|
fuzz_objs.append(bld.compile(obj))
|
|
src = bld.substitute(
|
|
"fwupd/libfwupdplugin/fu-fuzzer-firmware.c.in",
|
|
{
|
|
"@FIRMWARENEW@": fzr.new_gtype,
|
|
"@INCLUDE@": os.path.join("plugins", fzr.srcdir, fzr.header),
|
|
},
|
|
)
|
|
fuzz_objs.append(bld.compile(src))
|
|
bld.link(fuzz_objs + built_objs, "{}_fuzzer".format(fzr.name))
|
|
bld.makezip(
|
|
"{}_fuzzer_seed_corpus.zip".format(fzr.name),
|
|
"fwupd/src/fuzzing/firmware/{}".format(fzr.globstr),
|
|
)
|
|
|
|
|
|
if __name__ == "__main__":
|
|
_builder = Builder()
|
|
_build(_builder)
|
|
sys.exit(0)
|