Skip to content

Commit

Permalink
Add support for EDK 2 inf files
Browse files Browse the repository at this point in the history
  • Loading branch information
hughsie committed Dec 13, 2024
1 parent 1728c75 commit 1b89a7d
Show file tree
Hide file tree
Showing 5 changed files with 211 additions and 1 deletion.
1 change: 1 addition & 0 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,7 @@
"format_coswid.pyi",
"format_cyclonedx.pyi",
"format_goswid.pyi",
"format_inf.pyi",
"format_ini.pyi",
"format.pyi",
"format_spdx.pyi",
Expand Down
1 change: 1 addition & 0 deletions uswid/__init__.py
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
from uswid.format_coswid import uSwidFormatCoswid
from uswid.format_goswid import uSwidFormatGoswid
from uswid.format_ini import uSwidFormatIni
from uswid.format_inf import uSwidFormatInf
from uswid.format_pkgconfig import uSwidFormatPkgconfig
from uswid.format_swid import uSwidFormatSwid
from uswid.format_uswid import uSwidFormatUswid
Expand Down
3 changes: 3 additions & 0 deletions uswid/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,6 +44,7 @@
from uswid.format_cyclonedx import uSwidFormatCycloneDX
from uswid.format_spdx import uSwidFormatSpdx
from uswid.format_pe import uSwidFormatPe
from uswid.format_inf import uSwidFormatInf
from uswid.vcs import uSwidVcs
from uswid.vex_document import uSwidVexDocument
from uswid.container_utils import container_generate, container_roundtrip
Expand All @@ -63,6 +64,8 @@ def _detect_format(filepath: str) -> Optional[Any]:
return uSwidFormatCoswid()
if ext == "ini":
return uSwidFormatIni()
if ext == "inf":
return uSwidFormatInf()
if ext == "xml":
return uSwidFormatSwid()
if ext == "json":
Expand Down
3 changes: 2 additions & 1 deletion uswid/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,8 +19,9 @@ class uSwidFormatBase:
Available formats are:
* ``uSwidFormatCoswid``
* ``uSwidFormatCycloneDX`` (``.save`` only)
* ``uSwidFormatCycloneDX``
* ``uSwidFormatGoswid``
* ``uSwidFormatInf`` (``.load`` only)
* ``uSwidFormatIni``
* ``uSwidFormatPkgconfig`` (``.load`` only)
* ``uSwidFormatSwid``
Expand Down
204 changes: 204 additions & 0 deletions uswid/format_inf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,204 @@
#!/usr/bin/python3
# -*- coding: utf-8 -*-
#
# Copyright (C) 2024 Richard Hughes <[email protected]>
#
# SPDX-License-Identifier: BSD-2-Clause-Patent

import os
import hashlib
from collections import defaultdict
from typing import Optional

from .component import uSwidComponent, uSwidComponentType
from .container import uSwidContainer
from .entity import uSwidEntity, uSwidEntityRole
from .format import uSwidFormatBase
from .link import uSwidLink, uSwidLinkRel
from .errors import NotSupportedError

# from .payload import uSwidPayload
from .purl import uSwidPurl
from .vcs import uSwidVcs


class uSwidFormatInf(uSwidFormatBase):
"""EDK2 inf file"""

def __init__(self) -> None:
"""Initializes uSwidFormatInf"""
uSwidFormatBase.__init__(self, "inf") # type:ignore[call-arg]
self._inf_data: dict[str, list[str]] = defaultdict(list)
self._inf_defines: dict[str, str] = {}
self._spdx_ids: list[str] = []

def _add_license(self, spdx_id: str) -> None:

if spdx_id not in self._spdx_ids:
self._spdx_ids.append(spdx_id)

def _get_value_from_data(self, group: str, key: str) -> str:

for kv in self._inf_data[group]:
if kv.startswith(key + "="):
return kv[len(key) + 1 :]
raise KeyError(f"no {key}")

def load(self, blob: bytes, path: Optional[str] = None) -> uSwidContainer:

component = uSwidComponent()

group = None
for cnt, line in enumerate(blob.decode().replace("\r", "").split("\n")):

# description
if cnt == 1 and line.startswith("# "):
component.summary = line[3:].strip()

# has license?
lineidx = line.find("SPDX-License-Identifier: ")
if lineidx != -1:
self._add_license(line[lineidx + 25 :])

# remove comments
lineidx = line.find("#")
if lineidx != -1:
line = line[:lineidx]

# group
if line.startswith("[") and line.endswith("]"):
group = line[1:-1]
continue

# empty line
if not line.strip():
continue

# string value
if group and line.startswith(" "):
value_new = line[2:].strip()
if value_new.startswith("DEFINE "):
key, value = value_new[7:].split("=", maxsplit=1)
self._inf_defines[key.strip()] = value.strip()
else:
self._inf_data[group].append(value_new.replace(" ", ""))

# all modules should have this
try:
component.software_name = self._get_value_from_data("Defines", "BASE_NAME")
except KeyError as e:
raise NotSupportedError("no BASE_NAME in [Defines]") from e

# map from MODULE_TYPE to uSwidComponentType
try:
component.type = {
"BASE": uSwidComponentType.LIBRARY,
"DXE_CORE": uSwidComponentType.LIBRARY,
"DXE_DRIVER": uSwidComponentType.LIBRARY,
"DXE_RUNTIME_DRIVER": uSwidComponentType.LIBRARY,
"DXE_SMM_DRIVER": uSwidComponentType.LIBRARY,
"HOST_APPLICATION": uSwidComponentType.APPLICATION,
"MM_CORE_STANDALONE": uSwidComponentType.LIBRARY,
"MM_STANDALONE": uSwidComponentType.LIBRARY,
"PEI_CORE": uSwidComponentType.LIBRARY,
"PEIM": uSwidComponentType.LIBRARY,
"SEC": uSwidComponentType.LIBRARY,
"SMM_CORE": uSwidComponentType.LIBRARY,
"UEFI_APPLICATION": uSwidComponentType.APPLICATION,
"UEFI_DRIVER": uSwidComponentType.LIBRARY,
"USER_DEFINED": uSwidComponentType.LIBRARY,
}[self._get_value_from_data("Defines", "MODULE_TYPE")]
except KeyError:
component.type = uSwidComponentType.FIRMWARE

# ugh, see SecurityPkg/Tcg/Tcg2Smm/Tcg2MmDependencyDxe.inf
try:
component.software_version = self._get_value_from_data(
"Defines", "VERSION_STRING"
)
except KeyError:
component.software_version = "NOASSERTION"

# get the source hash and licence from each source file
colloquial_version = hashlib.sha256()
for fn in self._inf_data.get("Sources", []):

# e.g. CryptoPkg/Library/OpensslLib/OpensslLib.inf
for key, value in self._inf_defines.items():
fn = fn.replace(f"$({key})", value)

# e.g. MdePkg/Library/StackCheckLib/StackCheckLibStaticInit.inf
if fn.endswith("|GCC"):
fn = fn[:-4]
if fn.endswith("|MSFT"):
fn = fn[:-5]

if not path:
continue
with open(os.path.join(os.path.dirname(path), fn), "rb") as f:
buf = f.read()
colloquial_version.update(buf)
for line in buf.decode(errors="ignore").split("\n"):
lineidx = line.find("SPDX-License-Identifier: ")
if lineidx != -1:
self._add_license(line[lineidx + 25 :].strip())
# payload: uSwidPayload = uSwidPayload(name=fn)
# payload.ensure_from_filename(
# os.path.join(os.path.dirname(path), fn)
# )
# component.add_payload(payload)

# add all licenses
for spdx_id in self._spdx_ids:
component.add_link(uSwidLink(rel=uSwidLinkRel.LICENSE, spdx_id=spdx_id))

# of all of the sources, in the order specified in the .inf file
component.colloquial_version = colloquial_version.hexdigest()

if path:
vcs = uSwidVcs(filepath=path)
vcs_version = vcs.get_tag()
else:
vcs_version = "NOASSERTION"

# build tag-id
purl = uSwidPurl("pkg:github/tianocore/edk2")
purl.version = vcs_version
purl.subpath = component.software_name
component.tag_id = str(purl)

# build CPE
component.cpe = f"cpe:2.3:a:tianocore:edk2:{vcs_version}:*:*:*:*:*:*:{component.software_name}"

# add each dep
for subpath in self._inf_data.get("LibraryClasses", []):
purl.subpath = subpath
component.add_link(uSwidLink(rel=uSwidLinkRel.COMPONENT, href=str(purl)))

# GUID, not sure if useful...
try:
component.persistent_id = self._get_value_from_data(
"Defines", "FILE_GUID"
).lower()
except KeyError:
pass

# add per-module authors
if path:
for author in vcs.get_authors(relpath="."):
component.add_entity(
uSwidEntity(roles=[uSwidEntityRole.MAINTAINER], name=author)
)
for sbom_author in vcs.get_sbom_authors():
component.add_entity(
uSwidEntity(roles=[uSwidEntityRole.TAG_CREATOR], name=sbom_author)
)

# hardcoded for now, perhaps look for the parent component?
component.add_entity(
uSwidEntity(
roles=[uSwidEntityRole.SOFTWARE_CREATOR], name="EDK II developers"
)
)

return uSwidContainer([component])

0 comments on commit 1b89a7d

Please sign in to comment.