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 Jan 15, 2025
1 parent db400a2 commit 7fbed64
Show file tree
Hide file tree
Showing 10 changed files with 450 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
7 changes: 7 additions & 0 deletions tests/edk2/Shell.c
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#include "Shell.h"

int
hello(void)
{
return 42;
}
1 change: 1 addition & 0 deletions tests/edk2/Shell.h
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
int hello(void);
23 changes: 23 additions & 0 deletions tests/edk2/Shell.inf
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
## @file
# This is the shell application
#
# (C) Copyright 2024 Richard Hughes<BR>
# Copyright (c) 2009 - 2018, Richard Hughes<BR>
#
# SPDX-License-Identifier: BSD-2-Clause-Patent
##

[Defines]
INF_VERSION = 0x00010006
BASE_NAME = Shell
FILE_GUID = 7C04A583-9E3E-4f1c-AD65-E05268D0B4D1 # gUefiShellFileGuid
MODULE_TYPE = UEFI_APPLICATION
VERSION_STRING = 1.0
ENTRY_POINT = UefiMain

[Sources]
Shell.c
Shell.h

[LibraryClasses]
BaseLib
36 changes: 36 additions & 0 deletions tests/edk2/sbom.cdx.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
{
"bomFormat": "CycloneDX",
"specVersion": "1.6",
"version": 1,
"components": [
{
"type": "firmware",
"bom-ref": "pkg:github/tianocore/edk2@202411",
"cpe": "cpe:2.3:a:tianocore:edk2:202411:*:*:*:*:*:*:*",
"name": "EDK II",
"version": "edk2-stable202411-105-gd55d4e22f4",
"description": "A cross-platform firmware development environment for UEFI and PI specifications",
"authors": [
{
"name": "EDK II authors"
}
],
"supplier": {
"name": "EDK II developers"
},
"licenses": [
{
"license": {
"id": "BSD-2-Clause"
}
}
],
"externalReferences": [
{
"type": "vcs",
"url": "https://github.com/tianocore/edk2"
}
]
}
]
}
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
7 changes: 7 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 Expand Up @@ -171,6 +174,9 @@ def _container_merge_from_filepath(
# get the toplevel so that we can auto-add deps
component.source_dir = vcs.get_toplevel()

# add requirements from the loader
base.incorporate(container, component)

component_new = container.merge(component)
if component_new:
print(
Expand Down Expand Up @@ -396,6 +402,7 @@ def main():
base.objcopy = args.objcopy
with open(filepath, "rb") as f:
for component in base.load(f.read(), filepath):
base.incorporate(container, component)
component_new = container.merge(component)
if component_new:
print(
Expand Down
9 changes: 8 additions & 1 deletion uswid/format.py
Original file line number Diff line number Diff line change
Expand Up @@ -11,6 +11,7 @@

if TYPE_CHECKING:
from .container import uSwidContainer
from .component import uSwidComponent


class uSwidFormatBase:
Expand All @@ -19,8 +20,9 @@ class uSwidFormatBase:
Available formats are:
* ``uSwidFormatCoswid``
* ``uSwidFormatCycloneDX`` (``.save`` only)
* ``uSwidFormatCycloneDX``
* ``uSwidFormatGoswid``
* ``uSwidFormatInf`` (``.load`` only)
* ``uSwidFormatIni``
* ``uSwidFormatPkgconfig`` (``.load`` only)
* ``uSwidFormatSwid``
Expand All @@ -40,3 +42,8 @@ def load(self, blob: bytes, path: Optional[str] = None) -> "uSwidContainer":
def save(self, container: "uSwidContainer") -> bytes:
"""Save into a blob of data"""
raise NotImplementedError

def incorporate(
self, container: "uSwidContainer", component: "uSwidComponent"
) -> None:
"""Depsolve a new component into an existing container"""
231 changes: 231 additions & 0 deletions uswid/format_inf.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,231 @@
#!/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 copy import deepcopy
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 incorporate(self, container: uSwidContainer, component: uSwidComponent) -> None:

# this is defined with a sbom.cdx.json
component_parent = container.get_by_id("pkg:edk2", fuzzy=True)
if not component_parent:
return

# use the parent PURL as a template
if component_parent.purl:
component.purl = deepcopy(component_parent.purl)
component.purl.subpath = component.software_name

# fix up the links too
for link in component.links:
if link.rel != uSwidLinkRel.COMPONENT:
continue
purl = uSwidPurl(link.href)
purl.version = component_parent.purl.version
component.remove_link(link)
link.href = str(purl)
component.add_link(link)

# add a dep to the main EDK package
component_parent.add_link(
uSwidLink(rel=uSwidLinkRel.COMPONENT, href=component.tag_id)
)

# use the parent component supplier
entity_creator = component_parent.get_entity_by_role(
uSwidEntityRole.SOFTWARE_CREATOR
)
if entity_creator:
component.add_entity(entity_creator)

# use the parent VCS link if it doesn't already exist
link_vcs = component.get_link_by_rel(uSwidLinkRel.SEE_ALSO)
if not link_vcs:
link_vcs_parent = component_parent.get_link_by_rel(uSwidLinkRel.SEE_ALSO)
if link_vcs_parent:
component.add_link(link_vcs_parent)

# build CPE
if component_parent.purl:
component.cpe = f"cpe:2.3:a:tianocore:edk2:{component_parent.purl.version}:*:*:*:*:*:*:{component.software_name}"

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()

# add each dep -- but without a version defined
for subpath in self._inf_data.get("LibraryClasses", []):
purl = uSwidPurl("pkg:github/tianocore/edk2")
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:
vcs = uSwidVcs(filepath=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)
)

# success
return uSwidContainer([component])
Loading

0 comments on commit 7fbed64

Please sign in to comment.