diff --git a/.gitignore b/.gitignore index 1e7d69d..a884cd2 100644 --- a/.gitignore +++ b/.gitignore @@ -1 +1,2 @@ +SOURCES/rhel-net-naming-sysattrs-v0.4.tar.gz SOURCES/systemd-252.tar.gz diff --git a/.systemd.metadata b/.systemd.metadata index 9d85202..f84c6ac 100644 --- a/.systemd.metadata +++ b/.systemd.metadata @@ -1 +1,2 @@ +89b6d8ec519fd7d8b64f8d96b6cd0a65af97822b SOURCES/rhel-net-naming-sysattrs-v0.4.tar.gz 7c961dc6e8bb950825b85129f59dc80f4536cabb SOURCES/systemd-252.tar.gz diff --git a/SOURCES/0370-ci-Extend-source-git-automation.patch b/SOURCES/0370-ci-Extend-source-git-automation.patch new file mode 100644 index 0000000..f032219 --- /dev/null +++ b/SOURCES/0370-ci-Extend-source-git-automation.patch @@ -0,0 +1,223 @@ +From 55d337de1940076855c1687ffd588498d068724e Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Mon, 18 Sep 2023 13:51:43 +0200 +Subject: [PATCH] ci: Extend source-git-automation + +* on schedule and on demand workflows +* Added Tracker validation for Bugzilla and Jira + +rhel-only + +Resolves: RHEL-1086 +--- + .github/advanced-commit-linter.yml | 2 + + .github/tracker-validator.yml | 18 ++++ + .../source-git-automation-on-demand.yml | 100 ++++++++++++++++++ + .github/workflows/source-git-automation.yml | 29 ++++- + 4 files changed, 147 insertions(+), 2 deletions(-) + create mode 100644 .github/tracker-validator.yml + create mode 100644 .github/workflows/source-git-automation-on-demand.yml + +diff --git a/.github/advanced-commit-linter.yml b/.github/advanced-commit-linter.yml +index 0fb74a9dc8..86f0e911f2 100644 +--- a/.github/advanced-commit-linter.yml ++++ b/.github/advanced-commit-linter.yml +@@ -11,6 +11,7 @@ policy: + - 'Resolves: #?' + - 'Related: #?' + - 'Reverts: #?' ++ type: bugzilla + issue-format: + - '\d+$' + url: 'https://bugzilla.redhat.com/show_bug.cgi?id=' +@@ -18,6 +19,7 @@ policy: + - 'Resolves: ' + - 'Related: ' + - 'Reverts: ' ++ type: jira + issue-format: + - 'RHEL-\d+$' + url: 'https://issues.redhat.com/browse/' +diff --git a/.github/tracker-validator.yml b/.github/tracker-validator.yml +new file mode 100644 +index 0000000000..9e43e4e7d5 +--- /dev/null ++++ b/.github/tracker-validator.yml +@@ -0,0 +1,18 @@ ++labels: ++ missing-tracker: tracker/missing ++ invalid-product: tracker/invalid-product ++ invalid-component: tracker/invalid-component ++ unapproved: tracker/unapproved ++products: ++ - Red Hat Enterprise Linux 9 ++ - CentOS Stream 9 ++ - rhel-9.0.0 ++ - rhel-9.2.0 ++ - rhel-9.3.0 ++ - rhel-9.4.0 ++ - rhel-9.5.0 ++ - rhel-9.6.0 ++ - rhel-9.7.0 ++ - rhel-9.8.0 ++ - rhel-9.9.0 ++ - rhel-9.10.0 +diff --git a/.github/workflows/source-git-automation-on-demand.yml b/.github/workflows/source-git-automation-on-demand.yml +new file mode 100644 +index 0000000000..60d7bcf32d +--- /dev/null ++++ b/.github/workflows/source-git-automation-on-demand.yml +@@ -0,0 +1,100 @@ ++--- ++ ++name: Source git Automation Scheduled/On Demand ++on: ++ schedule: ++ # Workflow runs every 15 minutes ++ - cron: '*/15 * * * *' ++ workflow_dispatch: ++ inputs: ++ pr-number: ++ description: 'Pull Request number/s ; when not provided, the workflow will run for all open PRs' ++ required: true ++ default: '0' ++ ++permissions: ++ contents: read ++ ++jobs: ++ # Get all open PRs ++ gather-pull-requests: ++ if: github.repository == 'redhat-plumbers/systemd-rhel9' ++ runs-on: ubuntu-latest ++ ++ outputs: ++ pr-numbers: ${{ steps.get-pr-numbers.outputs.result }} ++ pr-numbers-manual: ${{ steps.parse-manual-input.outputs.result }} ++ ++ steps: ++ - id: get-pr-numbers ++ if: inputs.pr-number == '0' ++ name: Get all open PRs ++ uses: actions/github-script@v6 ++ with: ++ # !FIXME: this is not working if there is more than 100 PRs opened ++ script: | ++ const { data: pullRequests } = await github.rest.pulls.list({ ++ owner: context.repo.owner, ++ repo: context.repo.repo, ++ state: 'open', ++ per_page: 100 ++ }); ++ return pullRequests.map(pr => pr.number); ++ ++ - id: parse-manual-input ++ if: inputs.pr-number != '0' ++ name: Parse manual input ++ run: | ++ # shellcheck disable=SC2086 ++ echo "result="[ ${{ inputs.pr-number }} ]"" >> $GITHUB_OUTPUT ++ shell: bash ++ ++ validate-pr: ++ name: 'Validation of Pull Request #${{ matrix.pr-number }}' ++ needs: [ gather-pull-requests ] ++ runs-on: ubuntu-latest ++ ++ strategy: ++ fail-fast: false ++ matrix: ++ pr-number: ${{ inputs.pr-number == 0 && fromJSON(needs.gather-pull-requests.outputs.pr-numbers) || fromJSON(needs.gather-pull-requests.outputs.pr-numbers-manual) }} ++ ++ permissions: ++ statuses: write ++ checks: write ++ pull-requests: write ++ ++ steps: ++ - name: Repository checkout ++ uses: actions/checkout@v3 ++ ++ - id: metadata ++ name: Gather Pull Request Metadata ++ uses: redhat-plumbers-in-action/gather-pull-request-metadata@v1 ++ with: ++ pr-number: ${{ matrix.pr-number }} ++ ++ - id: commit-linter ++ name: Lint Commits ++ uses: redhat-plumbers-in-action/advanced-commit-linter@v2 ++ with: ++ pr-metadata: ${{ steps.metadata.outputs.metadata }} ++ token: ${{ secrets.GITHUB_TOKEN }} ++ ++ # Validates tracker, changes tracker status, updates PR title ++ - id: tracker-validator ++ name: Validate Tracker ++ uses: redhat-plumbers-in-action/tracker-validator@v1 ++ with: ++ pr-metadata: ${{ steps.metadata.outputs.metadata }} ++ component: systemd ++ tracker: ${{ fromJSON(steps.commit-linter.outputs.validated-pr-metadata).validation.tracker.id }} ++ tracker-type: ${{ fromJSON(steps.commit-linter.outputs.validated-pr-metadata).validation.tracker.type }} ++ bugzilla-instance: https://bugzilla.redhat.com ++ bugzilla-api-token: ${{ secrets.BUGZILLA_API_TOKEN }} ++ jira-instance: https://issues.redhat.com ++ jira-api-token: ${{ secrets.JIRA_API_TOKEN }} ++ token: ${{ secrets.GITHUB_TOKEN }} ++ ++ # TODO: merge PR if all checks passed ++ # TODO: add comment to Tracker that PR was merged ... +diff --git a/.github/workflows/source-git-automation.yml b/.github/workflows/source-git-automation.yml +index e653e28a7f..7fabb88a83 100644 +--- a/.github/workflows/source-git-automation.yml ++++ b/.github/workflows/source-git-automation.yml +@@ -12,7 +12,8 @@ jobs: + download-metadata: + if: > + github.event.workflow_run.event == 'pull_request' && +- github.event.workflow_run.conclusion == 'success' ++ github.event.workflow_run.conclusion == 'success' && ++ github.repository == 'redhat-plumbers/systemd-rhel9' + runs-on: ubuntu-latest + + outputs: +@@ -33,13 +34,37 @@ jobs: + validated-pr-metadata: ${{ steps.commit-linter.outputs.validated-pr-metadata }} + + permissions: ++ statuses: write + checks: write + pull-requests: write + + steps: + - id: commit-linter + name: Lint Commits +- uses: redhat-plumbers-in-action/advanced-commit-linter@v1 ++ uses: redhat-plumbers-in-action/advanced-commit-linter@v2 + with: + pr-metadata: ${{ needs.download-metadata.outputs.pr-metadata }} + token: ${{ secrets.GITHUB_TOKEN }} ++ ++ # Validates tracker, changes tracker status, updates PR title ++ tracker-validation: ++ needs: [ download-metadata, commit-linter ] ++ runs-on: ubuntu-latest ++ ++ permissions: ++ checks: write ++ pull-requests: write ++ ++ steps: ++ - name: Validate Tracker ++ uses: redhat-plumbers-in-action/tracker-validator@v1 ++ with: ++ pr-metadata: ${{ needs.download-metadata.outputs.pr-metadata }} ++ component: systemd ++ tracker: ${{ fromJSON(needs.commit-linter.outputs.validated-pr-metadata).validation.tracker.id }} ++ tracker-type: ${{ fromJSON(needs.commit-linter.outputs.validated-pr-metadata).validation.tracker.type }} ++ bugzilla-instance: https://bugzilla.redhat.com ++ bugzilla-api-token: ${{ secrets.BUGZILLA_API_TOKEN }} ++ jira-instance: https://issues.redhat.com ++ jira-api-token: ${{ secrets.JIRA_API_TOKEN }} ++ token: ${{ secrets.GITHUB_TOKEN }} diff --git a/SOURCES/0371-netif-naming-scheme-let-s-also-include-rhel8-schemes.patch b/SOURCES/0371-netif-naming-scheme-let-s-also-include-rhel8-schemes.patch new file mode 100644 index 0000000..b8e2501 --- /dev/null +++ b/SOURCES/0371-netif-naming-scheme-let-s-also-include-rhel8-schemes.patch @@ -0,0 +1,159 @@ +From dcc59dffa5116bf96618065cd60742cb660224b8 Mon Sep 17 00:00:00 2001 +From: Lukas Nykryn +Date: Fri, 22 Sep 2023 13:28:02 +0200 +Subject: [PATCH] netif-naming-scheme: let's also include rhel8 schemes + +With this patch user in rhel9 can also pick a scheme from rhel8. +This could be useful on in-place upgrades. + +rhel-only +Resolves: RHEL-7026 +--- + man/systemd.net-naming-scheme.xml | 88 +++++++++++++++++++++++++++++++ + src/shared/netif-naming-scheme.c | 10 ++++ + src/shared/netif-naming-scheme.h | 12 +++++ + 3 files changed, 110 insertions(+) + +diff --git a/man/systemd.net-naming-scheme.xml b/man/systemd.net-naming-scheme.xml +index ade4e27e31..c6ab86906a 100644 +--- a/man/systemd.net-naming-scheme.xml ++++ b/man/systemd.net-naming-scheme.xml +@@ -459,6 +459,94 @@ + + + ++ ++ rhel-8.0 ++ ++ Naming was changed for virtual network interfaces created with SR-IOV and NPAR and ++ for devices where the PCI network controller device does not have a slot number associated. ++ ++ SR-IOV virtual devices are named based on the name of the parent interface, with a suffix of ++ vport, where port is the ++ virtual device number. Previously those virtual devices were named as if completely independent. ++ ++ ++ The ninth and later NPAR virtual devices are named following the scheme used for the first ++ eight NPAR partitions. Previously those devices were not renamed and the kernel default ++ ("ethN") was used. ++ ++ Names are also generated for PCI devices where the PCI network controller device does not ++ have an associated slot number itself, but one of its parents does. Previously those devices were ++ not renamed and the kernel default was used. ++ ++ ++ ++ ++ rhel-8.1 ++ ++ Same as naming scheme rhel-8.0. ++ ++ ++ ++ rhel-8.2 ++ ++ Same as naming scheme rhel-8.0. ++ ++ ++ ++ rhel-8.3 ++ ++ Same as naming scheme rhel-8.0. ++ ++ ++ ++ rhel-8.4 ++ ++ If the PCI slot is assocated with PCI bridge and that has multiple child network ++ controllers then all of them might derive the same value of ID_NET_NAME_SLOT ++ property. That could cause naming conflict if the property is selected as a device name. Now, we detect the ++ situation, slot - bridge relation, and we don't produce the ID_NET_NAME_SLOT property to ++ avoid possible naming conflict. ++ ++ ++ ++ rhel-8.5 ++ ++ Same as naming scheme rhel-8.4. ++ ++ ++ ++ rhel-8.6 ++ ++ Same as naming scheme rhel-8.4. ++ ++ ++ ++ rhel-8.7 ++ ++ PCI hotplug slot names for the s390 PCI driver are a hexadecimal representation ++ of the function_id device attribute. This attribute is now used to build the ++ ID_NET_NAME_SLOT. Before that, all slot names were parsed as decimal ++ numbers, which could either result in an incorrect value of the ID_NET_NAME_SLOT ++ property or none at all. ++ ++ Some firmware and hypervisor implementations report unreasonable high numbers for the onboard ++ index. To prevent the generation of bogus onbard interface names, index numbers greater than 16381 ++ (2^14-1) were ignored. For s390 PCI devices index values up to 65535 (2^16-1) are valid. To account ++ for that, the limit is increased to now 65535. ++ ++ ++ ++ rhel-8.8 ++ ++ Same as naming scheme rhel-8.7. ++ ++ ++ ++ rhel-8.9 ++ ++ Same as naming scheme rhel-8.7. ++ ++ + + rhel-9.0 + +diff --git a/src/shared/netif-naming-scheme.c b/src/shared/netif-naming-scheme.c +index 0f50533279..9cfa5ca8e6 100644 +--- a/src/shared/netif-naming-scheme.c ++++ b/src/shared/netif-naming-scheme.c +@@ -25,6 +25,16 @@ static const NamingScheme naming_schemes[] = { + { "v250", NAMING_V250 }, + { "v251", NAMING_V251 }, + { "v252", NAMING_V252 }, ++ { "rhel-8.0", NAMING_RHEL_8_0 }, ++ { "rhel-8.1", NAMING_RHEL_8_1 }, ++ { "rhel-8.2", NAMING_RHEL_8_2 }, ++ { "rhel-8.3", NAMING_RHEL_8_3 }, ++ { "rhel-8.4", NAMING_RHEL_8_4 }, ++ { "rhel-8.5", NAMING_RHEL_8_5 }, ++ { "rhel-8.6", NAMING_RHEL_8_6 }, ++ { "rhel-8.7", NAMING_RHEL_8_7 }, ++ { "rhel-8.8", NAMING_RHEL_8_8 }, ++ { "rhel-8.9", NAMING_RHEL_8_9 }, + { "rhel-9.0", NAMING_RHEL_9_0 }, + { "rhel-9.1", NAMING_RHEL_9_1 }, + { "rhel-9.2", NAMING_RHEL_9_2 }, +diff --git a/src/shared/netif-naming-scheme.h b/src/shared/netif-naming-scheme.h +index fb3c8eb9b3..ed45536f65 100644 +--- a/src/shared/netif-naming-scheme.h ++++ b/src/shared/netif-naming-scheme.h +@@ -52,6 +52,18 @@ typedef enum NamingSchemeFlags { + NAMING_V250 = NAMING_V249 | NAMING_XEN_VIF, + NAMING_V251 = NAMING_V250 | NAMING_BRIDGE_MULTIFUNCTION_SLOT, + NAMING_V252 = NAMING_V251 | NAMING_DEVICETREE_ALIASES, ++ ++ NAMING_RHEL_8_0 = NAMING_V239, ++ NAMING_RHEL_8_1 = NAMING_V239, ++ NAMING_RHEL_8_2 = NAMING_V239, ++ NAMING_RHEL_8_3 = NAMING_V239, ++ NAMING_RHEL_8_4 = NAMING_V239 | NAMING_BRIDGE_NO_SLOT, ++ NAMING_RHEL_8_5 = NAMING_RHEL_8_4, ++ NAMING_RHEL_8_6 = NAMING_RHEL_8_4, ++ NAMING_RHEL_8_7 = NAMING_RHEL_8_4 | NAMING_SLOT_FUNCTION_ID | NAMING_16BIT_INDEX, ++ NAMING_RHEL_8_8 = NAMING_RHEL_8_7, ++ NAMING_RHEL_8_9 = NAMING_RHEL_8_7, ++ + NAMING_RHEL_9_0 = NAMING_V250 | NAMING_BRIDGE_MULTIFUNCTION_SLOT, + NAMING_RHEL_9_1 = NAMING_RHEL_9_0, + NAMING_RHEL_9_2 = NAMING_RHEL_9_0, diff --git a/SOURCES/0372-systemd-analyze-Add-table-and-JSON-output-implementa.patch b/SOURCES/0372-systemd-analyze-Add-table-and-JSON-output-implementa.patch new file mode 100644 index 0000000..7a632af --- /dev/null +++ b/SOURCES/0372-systemd-analyze-Add-table-and-JSON-output-implementa.patch @@ -0,0 +1,368 @@ +From f898f9d1290a25d70f2be99f4cf142060ca7ae63 Mon Sep 17 00:00:00 2001 +From: Joshua Zivkovic +Date: Wed, 2 Nov 2022 08:55:50 +0000 +Subject: [PATCH] systemd-analyze: Add table and JSON output implementation to + plot + +(cherry picked from commit ff46b2f97c42d73401ca3ffaaef54a017dc23923) + +Resolves: RHEL-5070 +--- + src/analyze/analyze-plot.c | 168 ++++++++++++++++++++++++++----------- + src/analyze/analyze.c | 36 ++++++-- + src/analyze/analyze.h | 2 + + 3 files changed, 154 insertions(+), 52 deletions(-) + +diff --git a/src/analyze/analyze-plot.c b/src/analyze/analyze-plot.c +index 100bdc3787..24f4add099 100644 +--- a/src/analyze/analyze-plot.c ++++ b/src/analyze/analyze-plot.c +@@ -5,6 +5,7 @@ + #include "analyze-time-data.h" + #include "bus-error.h" + #include "bus-map-properties.h" ++#include "format-table.h" + #include "sort-util.h" + #include "version.h" + +@@ -37,7 +38,7 @@ typedef struct HostInfo { + char *architecture; + } HostInfo; + +-static HostInfo* free_host_info(HostInfo *hi) { ++static HostInfo *free_host_info(HostInfo *hi) { + if (!hi) + return NULL; + +@@ -87,7 +88,7 @@ static int acquire_host_info(sd_bus *bus, HostInfo **hi) { + } + + r = bus_map_all_properties( +- system_bus ?: bus, ++ system_bus ? : bus, + "org.freedesktop.hostname1", + "/org/freedesktop/hostname1", + hostname_map, +@@ -156,15 +157,14 @@ static void svg_graph_box(double height, double begin, double end) { + SCALE_Y * height); + } + } +- + static int plot_unit_times(UnitTimes *u, double width, int y) { + bool b; + + if (!u->name) + return 0; + +- svg_bar("activating", u->activating, u->activated, y); +- svg_bar("active", u->activated, u->deactivating, y); ++ svg_bar("activating", u->activating, u->activated, y); ++ svg_bar("active", u->activated, u->deactivating, y); + svg_bar("deactivating", u->deactivating, u->deactivated, y); + + /* place the text on the left if we have passed the half of the svg width */ +@@ -178,41 +178,27 @@ static int plot_unit_times(UnitTimes *u, double width, int y) { + return 1; + } + +-int verb_plot(int argc, char *argv[], void *userdata) { +- _cleanup_(free_host_infop) HostInfo *host = NULL; +- _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; +- _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL; +- _cleanup_free_ char *pretty_times = NULL; +- bool use_full_bus = arg_scope == LOOKUP_SCOPE_SYSTEM; +- BootTimes *boot; ++static void limit_times_to_boot(const BootTimes *boot, UnitTimes *u) { ++ if (u->deactivated > u->activating && u->deactivated <= boot->finish_time && u->activated == 0 ++ && u->deactivating == 0) ++ u->activated = u->deactivating = u->deactivated; ++ if (u->activated < u->activating || u->activated > boot->finish_time) ++ u->activated = boot->finish_time; ++ if (u->deactivating < u->activated || u->deactivating > boot->finish_time) ++ u->deactivating = boot->finish_time; ++ if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time) ++ u->deactivated = boot->finish_time; ++} ++ ++static int produce_plot_as_svg( ++ UnitTimes *times, ++ const HostInfo *host, ++ const BootTimes *boot, ++ const char *pretty_times) { ++ int m = 1, y = 0; + UnitTimes *u; +- int n, m = 1, y = 0, r; + double width; + +- r = acquire_bus(&bus, &use_full_bus); +- if (r < 0) +- return bus_log_connect_error(r, arg_transport); +- +- n = acquire_boot_times(bus, &boot); +- if (n < 0) +- return n; +- +- n = pretty_boot_time(bus, &pretty_times); +- if (n < 0) +- return n; +- +- if (use_full_bus || arg_scope != LOOKUP_SCOPE_SYSTEM) { +- n = acquire_host_info(bus, &host); +- if (n < 0) +- return n; +- } +- +- n = acquire_time_data(bus, ×); +- if (n <= 0) +- return n; +- +- typesafe_qsort(times, n, compare_unit_start); +- + width = SCALE_X * (boot->firmware_time + boot->finish_time); + if (width < 800.0) + width = 800.0; +@@ -245,16 +231,8 @@ int verb_plot(int argc, char *argv[], void *userdata) { + if (text_width > text_start && text_width + text_start > width) + width = text_width + text_start; + +- if (u->deactivated > u->activating && +- u->deactivated <= boot->finish_time && +- u->activated == 0 && u->deactivating == 0) +- u->activated = u->deactivating = u->deactivated; +- if (u->activated < u->activating || u->activated > boot->finish_time) +- u->activated = boot->finish_time; +- if (u->deactivating < u->activated || u->deactivating > boot->finish_time) +- u->deactivating = boot->finish_time; +- if (u->deactivated < u->deactivating || u->deactivated > boot->finish_time) +- u->deactivated = boot->finish_time; ++ limit_times_to_boot(boot, u); ++ + m++; + } + +@@ -391,5 +369,101 @@ int verb_plot(int argc, char *argv[], void *userdata) { + + svg("\n"); + ++ return 0; ++} ++ ++static int show_table(Table *table, const char *word) { ++ int r; ++ ++ assert(table); ++ assert(word); ++ ++ if (table_get_rows(table) > 1) { ++ table_set_header(table, arg_legend); ++ ++ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) ++ r = table_print_json(table, NULL, arg_json_format_flags | JSON_FORMAT_COLOR_AUTO); ++ else ++ r = table_print(table, NULL); ++ if (r < 0) ++ return table_log_print_error(r); ++ } ++ ++ if (arg_legend) { ++ if (table_get_rows(table) > 1) ++ printf("\n%zu %s listed.\n", table_get_rows(table) - 1, word); ++ else ++ printf("No %s.\n", word); ++ } ++ ++ return 0; ++} ++ ++static int produce_plot_as_text(UnitTimes *times, const BootTimes *boot) { ++ _cleanup_(table_unrefp) Table *table = NULL; ++ int r; ++ ++ table = table_new("name", "activated", "activating", "time", "deactivated", "deactivating"); ++ if (!table) ++ return log_oom(); ++ ++ for (; times->has_data; times++) { ++ limit_times_to_boot(boot, times); ++ ++ r = table_add_many( ++ table, ++ TABLE_STRING, times->name, ++ TABLE_TIMESPAN_MSEC, times->activated, ++ TABLE_TIMESPAN_MSEC, times->activating, ++ TABLE_TIMESPAN_MSEC, times->time, ++ TABLE_TIMESPAN_MSEC, times->deactivated, ++ TABLE_TIMESPAN_MSEC, times->deactivating); ++ if (r < 0) ++ return table_log_add_error(r); ++ } ++ ++ return show_table(table, "Units"); ++} ++ ++int verb_plot(int argc, char *argv[], void *userdata) { ++ _cleanup_(free_host_infop) HostInfo *host = NULL; ++ _cleanup_(sd_bus_flush_close_unrefp) sd_bus *bus = NULL; ++ _cleanup_(unit_times_free_arrayp) UnitTimes *times = NULL; ++ _cleanup_free_ char *pretty_times = NULL; ++ bool use_full_bus = arg_scope == LOOKUP_SCOPE_SYSTEM; ++ BootTimes *boot; ++ int n, r; ++ ++ r = acquire_bus(&bus, &use_full_bus); ++ if (r < 0) ++ return bus_log_connect_error(r, arg_transport); ++ ++ n = acquire_boot_times(bus, &boot); ++ if (n < 0) ++ return n; ++ ++ n = pretty_boot_time(bus, &pretty_times); ++ if (n < 0) ++ return n; ++ ++ if (use_full_bus || arg_scope != LOOKUP_SCOPE_SYSTEM) { ++ n = acquire_host_info(bus, &host); ++ if (n < 0) ++ return n; ++ } ++ ++ n = acquire_time_data(bus, ×); ++ if (n <= 0) ++ return n; ++ ++ typesafe_qsort(times, n, compare_unit_start); ++ ++ if (!FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF) || arg_table) ++ r = produce_plot_as_text(times, boot); ++ else ++ r = produce_plot_as_svg(times, host, boot, pretty_times); ++ if (r < 0) ++ return r; ++ + return EXIT_SUCCESS; + } +diff --git a/src/analyze/analyze.c b/src/analyze/analyze.c +index 6e47357a11..825c19c6f4 100644 +--- a/src/analyze/analyze.c ++++ b/src/analyze/analyze.c +@@ -105,6 +105,8 @@ char *arg_unit = NULL; + JsonFormatFlags arg_json_format_flags = JSON_FORMAT_OFF; + bool arg_quiet = false; + char *arg_profile = NULL; ++bool arg_legend = true; ++bool arg_table = false; + + STATIC_DESTRUCTOR_REGISTER(arg_dot_from_patterns, strv_freep); + STATIC_DESTRUCTOR_REGISTER(arg_dot_to_patterns, strv_freep); +@@ -217,8 +219,10 @@ static int help(int argc, char *argv[], void *userdata) { + " --security-policy=PATH Use custom JSON security policy instead\n" + " of built-in one\n" + " --json=pretty|short|off Generate JSON output of the security\n" +- " analysis table\n" ++ " analysis table, or plot's raw time data\n" + " --no-pager Do not pipe output into a pager\n" ++ " --no-legend Disable column headers and hints in plot\n" ++ " with either --table or --json=\n" + " --system Operate on system systemd instance\n" + " --user Operate on user systemd instance\n" + " --global Operate on global user configuration\n" +@@ -238,6 +242,7 @@ static int help(int argc, char *argv[], void *userdata) { + " specified time\n" + " --profile=name|PATH Include the specified profile in the\n" + " security review of the unit(s)\n" ++ " --table Output plot's raw time data as a table\n" + " -h --help Show this help\n" + " --version Show package version\n" + " -q --quiet Do not emit hints\n" +@@ -280,6 +285,8 @@ static int parse_argv(int argc, char *argv[]) { + ARG_SECURITY_POLICY, + ARG_JSON, + ARG_PROFILE, ++ ARG_TABLE, ++ ARG_NO_LEGEND, + }; + + static const struct option options[] = { +@@ -310,6 +317,8 @@ static int parse_argv(int argc, char *argv[]) { + { "unit", required_argument, NULL, 'U' }, + { "json", required_argument, NULL, ARG_JSON }, + { "profile", required_argument, NULL, ARG_PROFILE }, ++ { "table", optional_argument, NULL, ARG_TABLE }, ++ { "no-legend", optional_argument, NULL, ARG_NO_LEGEND }, + {} + }; + +@@ -448,14 +457,12 @@ static int parse_argv(int argc, char *argv[]) { + r = safe_atou(optarg, &arg_iterations); + if (r < 0) + return log_error_errno(r, "Failed to parse iterations: %s", optarg); +- + break; + + case ARG_BASE_TIME: + r = parse_timestamp(optarg, &arg_base_time); + if (r < 0) + return log_error_errno(r, "Failed to parse --base-time= parameter: %s", optarg); +- + break; + + case ARG_PROFILE: +@@ -486,6 +493,15 @@ static int parse_argv(int argc, char *argv[]) { + free_and_replace(arg_unit, mangled); + break; + } ++ ++ case ARG_TABLE: ++ arg_table = true; ++ break; ++ ++ case ARG_NO_LEGEND: ++ arg_legend = false; ++ break; ++ + case '?': + return -EINVAL; + +@@ -497,9 +513,9 @@ static int parse_argv(int argc, char *argv[]) { + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "Option --offline= is only supported for security right now."); + +- if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf")) ++ if (arg_json_format_flags != JSON_FORMAT_OFF && !STRPTR_IN_SET(argv[optind], "security", "inspect-elf", "plot")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), +- "Option --json= is only supported for security and inspect-elf right now."); ++ "Option --json= is only supported for security, inspect-elf, and plot right now."); + + if (arg_threshold != 100 && !streq_ptr(argv[optind], "security")) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), +@@ -536,6 +552,16 @@ static int parse_argv(int argc, char *argv[]) { + if (streq_ptr(argv[optind], "condition") && arg_unit && optind < argc - 1) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "No conditions can be passed if --unit= is used."); + ++ if ((!arg_legend && !streq_ptr(argv[optind], "plot")) || ++ (streq_ptr(argv[optind], "plot") && !arg_legend && !arg_table && FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF))) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --no-legend is only supported for plot with either --table or --json=."); ++ ++ if (arg_table && !streq_ptr(argv[optind], "plot")) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Option --table is only supported for plot right now."); ++ ++ if (arg_table && !FLAGS_SET(arg_json_format_flags, JSON_FORMAT_OFF)) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--table and --json= are mutually exclusive."); ++ + return 1; /* work to do */ + } + +diff --git a/src/analyze/analyze.h b/src/analyze/analyze.h +index da12058c43..e4af7b47e0 100644 +--- a/src/analyze/analyze.h ++++ b/src/analyze/analyze.h +@@ -36,6 +36,8 @@ extern char *arg_unit; + extern JsonFormatFlags arg_json_format_flags; + extern bool arg_quiet; + extern char *arg_profile; ++extern bool arg_legend; ++extern bool arg_table; + + int acquire_bus(sd_bus **bus, bool *use_full_bus); + diff --git a/SOURCES/0373-systemd-analyze-Update-man-systemd-analyze.xml-with-.patch b/SOURCES/0373-systemd-analyze-Update-man-systemd-analyze.xml-with-.patch new file mode 100644 index 0000000..2a57865 --- /dev/null +++ b/SOURCES/0373-systemd-analyze-Update-man-systemd-analyze.xml-with-.patch @@ -0,0 +1,69 @@ +From 09514fbcaf51f1c12b651420e24400ff7319c638 Mon Sep 17 00:00:00 2001 +From: joshuazivkovic +Date: Wed, 14 Dec 2022 12:31:22 +0000 +Subject: [PATCH] systemd-analyze: Update man/systemd-analyze.xml with Plot + JSON and table + +(cherry picked from commit dc57a3387bbe7770491f35e0d993f411237636b5) + +Resolves: RHEL-5070 +--- + man/systemd-analyze.xml | 31 ++++++++++++++++++++++++++++--- + 1 file changed, 28 insertions(+), 3 deletions(-) + +diff --git a/man/systemd-analyze.xml b/man/systemd-analyze.xml +index 0d91cdd7d0..5ba0d40fa0 100644 +--- a/man/systemd-analyze.xml ++++ b/man/systemd-analyze.xml +@@ -274,8 +274,8 @@ Timestamp units-load-finish: Thu 2019-03-14 23:28:07 CET + + <command>systemd-analyze plot</command> + +- This command prints an SVG graphic detailing which system services have been started at what +- time, highlighting the time they spent on initialization. ++ This command prints either an SVG graphic, detailing which system services have been started at what ++ time, highlighting the time they spent on initialization, or the raw time data in JSON or table format. + + + <command>Plot a bootchart</command> +@@ -1204,7 +1204,17 @@ $ systemd-analyze verify /tmp/source:alias.service + corresponds to a higher security threat. The JSON version of the table is printed to standard + output. The MODE passed to the option can be one of three: + which is the default, and +- which respectively output a prettified or shorted JSON version of the security table. ++ which respectively output a prettified or shorted JSON version of the security table. ++ ++ With the plot command, genereate a JSON formatted output of the raw time data. ++ The format is a JSON array with objects containing the following fields: name ++ which is the unit name, activated which is the time after startup the ++ service was activated, activating which is how long after startup the service ++ was initially started, time which is how long the service took to activate ++ from when it was initially started, deactivated which is the time after startup ++ that the service was deactivated, deactivating whcih is the time after startup ++ that the service was initially told to deactivate. ++ + + + +@@ -1235,6 +1245,21 @@ $ systemd-analyze verify /tmp/source:alias.service + other paths. + + ++ ++ ++ ++ When used with the plot command, the raw time data is output in a table. ++ ++ ++ ++ ++ ++ ++ When used with the plot command in combination with either ++ or , no legends or hints are included in the output. ++ ++ ++ + + + diff --git a/SOURCES/0374-systemd-analyze-Add-tab-complete-logic-for-plot.patch b/SOURCES/0374-systemd-analyze-Add-tab-complete-logic-for-plot.patch new file mode 100644 index 0000000..51575cf --- /dev/null +++ b/SOURCES/0374-systemd-analyze-Add-tab-complete-logic-for-plot.patch @@ -0,0 +1,87 @@ +From a1bc66bfeac890107411686cb7567c9ffa926972 Mon Sep 17 00:00:00 2001 +From: joshuazivkovic +Date: Wed, 14 Dec 2022 12:31:59 +0000 +Subject: [PATCH] systemd-analyze: Add tab complete logic for plot + +(cherry picked from commit f21a6502d81ca5690467cb161dafd4b875e4430e) + +Resolves: RHEL-5070 +--- + shell-completion/bash/systemd-analyze | 8 +++++++- + shell-completion/zsh/_systemd-analyze | 14 ++++++++++++-- + 2 files changed, 19 insertions(+), 3 deletions(-) + +diff --git a/shell-completion/bash/systemd-analyze b/shell-completion/bash/systemd-analyze +index b1baec9978..5edba7bf58 100644 +--- a/shell-completion/bash/systemd-analyze ++++ b/shell-completion/bash/systemd-analyze +@@ -62,7 +62,7 @@ _systemd_analyze() { + ) + + local -A VERBS=( +- [STANDALONE]='time blame plot unit-paths exit-status calendar timestamp timespan' ++ [STANDALONE]='time blame unit-paths exit-status calendar timestamp timespan' + [CRITICAL_CHAIN]='critical-chain' + [DOT]='dot' + [DUMP]='dump' +@@ -72,6 +72,7 @@ _systemd_analyze() { + [SECURITY]='security' + [CONDITION]='condition' + [INSPECT_ELF]='inspect-elf' ++ [PLOT]='plot' + ) + + local CONFIGS='systemd/bootchart.conf systemd/coredump.conf systemd/journald.conf +@@ -195,6 +196,11 @@ _systemd_analyze() { + comps=$( compgen -A file -- "$cur" ) + compopt -o filenames + fi ++ ++ elif __contains_word "$verb" ${VERBS[PLOT]}; then ++ if [[ $cur = -* ]]; then ++ comps='--help --version --system --user --global --no-pager --json=off --json=pretty --json=short --table --no-legend' ++ fi + fi + + COMPREPLY=( $(compgen -W '$comps' -- "$cur") ) +diff --git a/shell-completion/zsh/_systemd-analyze b/shell-completion/zsh/_systemd-analyze +index e305995cef..2e046ea111 100644 +--- a/shell-completion/zsh/_systemd-analyze ++++ b/shell-completion/zsh/_systemd-analyze +@@ -40,6 +40,13 @@ + _describe -t groups 'file system groups' _groups || compadd "$@" + } + ++(( $+functions[_systemd-analyze_plot] )) || ++ _systemd-analyze_plot() { ++ local -a _options ++ _options=( '--json=off' '--json=pretty' '--json=short' '--table' '--no-legend' ) ++ _describe 'plot options' _options ++ } ++ + (( $+functions[_systemd-analyze_commands] )) || + _systemd-analyze_commands(){ + local -a _systemd_analyze_cmds +@@ -48,7 +55,8 @@ + 'time:Print time spent in the kernel before reaching userspace' + 'blame:Print list of running units ordered by time to init' + 'critical-chain:Print a tree of the time critical chain of units' +- 'plot:Output SVG graphic showing service initialization' ++ 'plot:Output SVG graphic showing service initialization, or raw time data in ++JSON or table format' + 'dot:Dump dependency graph (in dot(1) format)' + 'dump:Dump server status' + 'cat-config:Cat systemd config files' +@@ -97,9 +105,11 @@ _arguments \ + '--offline=[Perform a security review of the specified unit files]:BOOL:(yes no)' \ + '--threshold=[Set a value to compare the overall security exposure level with]: NUMBER' \ + '--security-policy=[Use customized requirements to compare unit files against]: PATH' \ +- '--json=[Generate a JSON output of the security analysis table]:MODE:(pretty short off)' \ ++ "--json=[Generate a JSON output of the security analysis table or plot's raw time data]:MODE:(pretty short off)" \ ++ "--table=[Generate a table of plot's raw time data]" \ + '--profile=[Include the specified profile in the security review of units]: PATH' \ + '--no-pager[Do not pipe output into a pager]' \ ++ "--no-legend[Do not show the headers and footers for plot's raw time data formats]" \ + '--man=[Do (not) check for existence of man pages]:BOOL:(yes no)' \ + '--generators=[Do (not) run unit generators]:BOOL:(yes no)' \ + '--order[When generating graph for dot, show only order]' \ diff --git a/SOURCES/0375-systemd-analyze-Add-json-table-and-no-legend-tests-f.patch b/SOURCES/0375-systemd-analyze-Add-json-table-and-no-legend-tests-f.patch new file mode 100644 index 0000000..9bd4da4 --- /dev/null +++ b/SOURCES/0375-systemd-analyze-Add-json-table-and-no-legend-tests-f.patch @@ -0,0 +1,34 @@ +From 503c6777cc0a05e4ba174ff674fdf9ce3ed87341 Mon Sep 17 00:00:00 2001 +From: joshuazivkovic +Date: Fri, 13 Jan 2023 09:17:27 +0000 +Subject: [PATCH] systemd-analyze: Add --json=, --table and -no-legend tests + for plot + +(cherry picked from commit a23be57de3a5c5afb9ca878775ae838c3341f90c) + +Resolves: RHEL-5070 +--- + test/units/testsuite-65.sh | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/test/units/testsuite-65.sh b/test/units/testsuite-65.sh +index ebe1f57b52..4093c5a2a7 100755 +--- a/test/units/testsuite-65.sh ++++ b/test/units/testsuite-65.sh +@@ -18,7 +18,16 @@ systemd-analyze || : + systemd-analyze time || : + systemd-analyze blame || : + systemd-analyze critical-chain || : ++# plot + systemd-analyze plot >/dev/null || : ++systemd-analyze plot --json=pretty >/dev/null || : ++systemd-analyze plot --json=short >/dev/null || : ++systemd-analyze plot --json=off >/dev/null || : ++systemd-analyze plot --json=pretty --no-legend >/dev/null || : ++systemd-analyze plot --json=short --no-legend >/dev/null || : ++systemd-analyze plot --json=off --no-legend >/dev/null || : ++systemd-analyze plot --table >/dev/null || : ++systemd-analyze plot --table --no-legend >/dev/null || : + # legacy/deprecated options (moved to systemctl, but still usable from analyze) + systemd-analyze log-level + systemd-analyze log-level "$(systemctl log-level)" diff --git a/SOURCES/0376-ci-enable-source-git-automation-to-validate-reviews-.patch b/SOURCES/0376-ci-enable-source-git-automation-to-validate-reviews-.patch new file mode 100644 index 0000000..23279b8 --- /dev/null +++ b/SOURCES/0376-ci-enable-source-git-automation-to-validate-reviews-.patch @@ -0,0 +1,95 @@ +From 16f06c8cfbdf660e1c4e2052b7dd121f3497ff0f Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Wed, 18 Oct 2023 15:24:23 +0200 +Subject: [PATCH] ci: enable source-git automation to validate reviews and ci + results + +rhel-only + +Related: RHEL-1086 +--- + .github/pull-request-validator.yml | 4 ++++ + .../source-git-automation-on-demand.yml | 14 ++++++++++---- + .github/workflows/source-git-automation.yml | 18 +++++++++++++++++- + 3 files changed, 31 insertions(+), 5 deletions(-) + create mode 100644 .github/pull-request-validator.yml + +diff --git a/.github/pull-request-validator.yml b/.github/pull-request-validator.yml +new file mode 100644 +index 0000000000..4bb5bbec12 +--- /dev/null ++++ b/.github/pull-request-validator.yml +@@ -0,0 +1,4 @@ ++labels: ++ missing-review: pr/needs-review ++ changes-requested: pr/changes-requested ++ missing-failing-ci: pr/needs-ci +diff --git a/.github/workflows/source-git-automation-on-demand.yml b/.github/workflows/source-git-automation-on-demand.yml +index 60d7bcf32d..2dd6af3113 100644 +--- a/.github/workflows/source-git-automation-on-demand.yml ++++ b/.github/workflows/source-git-automation-on-demand.yml +@@ -74,7 +74,8 @@ jobs: + with: + pr-number: ${{ matrix.pr-number }} + +- - id: commit-linter ++ - if: ${{ !cancelled() }} ++ id: commit-linter + name: Lint Commits + uses: redhat-plumbers-in-action/advanced-commit-linter@v2 + with: +@@ -82,7 +83,8 @@ jobs: + token: ${{ secrets.GITHUB_TOKEN }} + + # Validates tracker, changes tracker status, updates PR title +- - id: tracker-validator ++ - if: ${{ !cancelled() }} ++ id: tracker-validator + name: Validate Tracker + uses: redhat-plumbers-in-action/tracker-validator@v1 + with: +@@ -96,5 +98,9 @@ jobs: + jira-api-token: ${{ secrets.JIRA_API_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} + +- # TODO: merge PR if all checks passed +- # TODO: add comment to Tracker that PR was merged ... ++ - if: ${{ !cancelled() }} ++ name: Pull Request Validator ++ uses: redhat-plumbers-in-action/pull-request-validator@v1 ++ with: ++ pr-metadata: ${{ steps.metadata.outputs.metadata }} ++ token: ${{ secrets.GITHUB_TOKEN }} +diff --git a/.github/workflows/source-git-automation.yml b/.github/workflows/source-git-automation.yml +index 7fabb88a83..214e72de6f 100644 +--- a/.github/workflows/source-git-automation.yml ++++ b/.github/workflows/source-git-automation.yml +@@ -47,7 +47,8 @@ jobs: + token: ${{ secrets.GITHUB_TOKEN }} + + # Validates tracker, changes tracker status, updates PR title +- tracker-validation: ++ tracker-validator: ++ if: ${{ !cancelled() }} + needs: [ download-metadata, commit-linter ] + runs-on: ubuntu-latest + +@@ -68,3 +69,18 @@ jobs: + jira-instance: https://issues.redhat.com + jira-api-token: ${{ secrets.JIRA_API_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} ++ ++ pull-request-validator: ++ needs: [ download-metadata ] ++ runs-on: ubuntu-latest ++ ++ permissions: ++ checks: write ++ pull-requests: write ++ ++ steps: ++ - name: Pull Request Validator ++ uses: redhat-plumbers-in-action/pull-request-validator@v1 ++ with: ++ pr-metadata: ${{ needs.download-metadata.outputs.pr-metadata }} ++ token: ${{ secrets.GITHUB_TOKEN }} diff --git a/SOURCES/0377-ci-remove-Mergify-config-replaced-by-Pull-Request-Va.patch b/SOURCES/0377-ci-remove-Mergify-config-replaced-by-Pull-Request-Va.patch new file mode 100644 index 0000000..f5e40b8 --- /dev/null +++ b/SOURCES/0377-ci-remove-Mergify-config-replaced-by-Pull-Request-Va.patch @@ -0,0 +1,181 @@ +From c31597c9112c4676f918b14999506a586d6ef8f4 Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Wed, 18 Oct 2023 15:47:54 +0200 +Subject: [PATCH] ci: remove Mergify config - replaced by Pull Request + Validator + +rhel-only + +Related: RHEL-1086 +--- + .mergify.yml | 161 --------------------------------------------------- + 1 file changed, 161 deletions(-) + delete mode 100644 .mergify.yml + +diff --git a/.mergify.yml b/.mergify.yml +deleted file mode 100644 +index e862808ca7..0000000000 +--- a/.mergify.yml ++++ /dev/null +@@ -1,161 +0,0 @@ +-# doc: https://docs.mergify.com +---- +- +-pull_request_rules: +- - name: Add `needs-ci` label on CI fail - v252 +- conditions: +- # Policy is relevant for rhel-9.2.0 branches and newer & main branch +- - base~=^main$|^rhel-9.([2-9]|\d{2,}).0$ +- - label!=ci-waived +- - or: +- # Build test +- # test build check only when the tests have been run ... +- - and: +- - files~=^(\S+\/meson\.build|\.github\/workflows\/\S+|meson_options\.txt|src\/\S+|test\/fuzz\/\S+) +- - or: +- - -check-success=build (gcc, 11, bfd, gcrypt) +- - -check-success=build (gcc, 12, gold, openssl) +- - -check-success=build (clang, 13, mold, gcrypt) +- - -check-success=build (clang, 14, lld, openssl) +- - -check-success=build (clang, 15, bfd, auto) +- # Unit tests +- - -check-success=build (GCC, auto) +- - -check-success=build (GCC_ASAN_UBSAN, auto) +- - -check-success=build (CLANG, auto) +- - -check-success=build (CLANG_RELEASE, auto) +- - -check-success=build (CLANG_ASAN_UBSAN, auto) +- - -check-success=build (CLANG_ASAN_UBSAN_NO_DEPS, auto) +- - -check-success=build (GCC, openssl) +- - -check-success=build (CLANG, gcrypt) +- # ClusterFuzzingLite +- - -check-success=PR (address) +- - -check-success=PR (undefined) +- - -check-success=PR (memory) +- # CentOS CI +- - -check-success=CentOS CI (CentOS Stream 9) +- - -check-success=CentOS CI (CentOS Stream 9 + sanitizers) +- # Packit +- - -check-success=rpm-build:centos-stream-9-aarch64 +- - -check-success=rpm-build:centos-stream-9-x86_64 +- # Other +- - -check-success=Lint Code Base +- - -check-success=Differential ShellCheck +- # CodeQL +- # test CodeQL check only when the CodeQL have been run ... +- - and: +- - files~=^(\S+\/meson\.build|\.github\/\S+\/codeql|src\/\S+|test\/\S+|tools\/\S+) +- - -check-success=CodeQL +- actions: +- label: +- add: +- - needs-ci +- +- - name: Add `needs-ci` label on CI fail - v250 +- conditions: +- # Policy is relevant branches before rhel-9.2.0 +- - base~=^rhel-9.0.0-beta$|^rhel-9.[0-1].0$ +- - label!=ci-waived +- - or: +- # Build test +- - -check-success=build (gcc, 10, bfd) +- - -check-success=build (gcc, 11, gold) +- - -check-success=build (clang, 11, bfd) +- - -check-success=build (clang, 12, gold) +- - -check-success=build (clang, 13, lld) +- # Unit tests +- - -check-success=build (GCC, auto) +- - -check-success=build (GCC_ASAN_UBSAN, auto) +- - -check-success=build (CLANG, auto) +- - -check-success=build (CLANG_ASAN_UBSAN, auto) +- - -check-success=build (GCC, openssl) +- - -check-success=build (CLANG, gcrypt) +- # CentOS CI +- - -check-success=CentOS CI (CentOS Stream 9) +- - -check-success=CentOS CI (CentOS Stream 9 + sanitizers) +- # Packit +- - -check-success=rpm-build:centos-stream-9-aarch64 +- - -check-success=rpm-build:centos-stream-9-x86_64 +- actions: +- label: +- add: +- - needs-ci +- +- - name: Remove `needs-ci` label on CI success - v252 +- conditions: +- # Policy is relevant for rhel-9.2.0 branches and newer & main branch +- - base~=^main$|^rhel-9.([2-9]|\d{2,}).0$ +- - or: +- - label=ci-waived +- - and: +- # Build test +- # test build check only when specific files are changed ... +- - or: +- - -files~=^(\S+\/meson\.build|\.github\/workflows\/\S+|meson_options\.txt|src\/\S+|test\/fuzz\/\S+) +- - and: +- - check-success=build (gcc, 11, bfd, gcrypt) +- - check-success=build (gcc, 12, gold, openssl) +- - check-success=build (clang, 13, mold, gcrypt) +- - check-success=build (clang, 14, lld, openssl) +- - check-success=build (clang, 15, bfd, auto) +- # Unit tests +- - check-success=build (GCC, auto) +- - check-success=build (GCC_ASAN_UBSAN, auto) +- - check-success=build (CLANG, auto) +- - check-success=build (CLANG_RELEASE, auto) +- - check-success=build (CLANG_ASAN_UBSAN, auto) +- - check-success=build (CLANG_ASAN_UBSAN_NO_DEPS, auto) +- - check-success=build (GCC, openssl) +- - check-success=build (CLANG, gcrypt) +- # ClusterFuzzingLite +- - check-success=PR (address) +- - check-success=PR (undefined) +- - check-success=PR (memory) +- # CentOS CI +- - check-success=CentOS CI (CentOS Stream 9) +- - check-success=CentOS CI (CentOS Stream 9 + sanitizers) +- # CodeQL +- # test CodeQL check only when specific files are changed ... +- - or: +- - -files~=^(\S+\/meson\.build|\.github\/\S+\/codeql|src\/\S+|test\/\S+|tools\/\S+) +- - check-success=CodeQL +- # Packit +- - check-success=rpm-build:centos-stream-9-aarch64 +- - check-success=rpm-build:centos-stream-9-x86_64 +- # Other +- - check-success=Lint Code Base +- - check-success=Differential ShellCheck +- actions: +- label: +- remove: +- - needs-ci +- +- - name: Remove `needs-ci` label on CI success - v250 +- conditions: +- # Policy is relevant branches before rhel-9.2.0 +- - base~=^rhel-9.0.0-beta$|^rhel-9.[0-1].0$ +- - or: +- - label=ci-waived +- - and: +- # Build test +- - check-success=build (gcc, 10, bfd) +- - check-success=build (gcc, 11, gold) +- - check-success=build (clang, 11, bfd) +- - check-success=build (clang, 12, gold) +- - check-success=build (clang, 13, lld) +- # Unit tests +- - check-success=build (GCC, auto) +- - check-success=build (GCC_ASAN_UBSAN, auto) +- - check-success=build (CLANG, auto) +- - check-success=build (CLANG_ASAN_UBSAN, auto) +- - check-success=build (GCC, openssl) +- - check-success=build (CLANG, gcrypt) +- # CentOS CI +- - check-success=CentOS CI (CentOS Stream 9) +- - check-success=CentOS CI (CentOS Stream 9 + sanitizers) +- # Packit +- - check-success=rpm-build:centos-stream-9-aarch64 +- - check-success=rpm-build:centos-stream-9-x86_64 +- actions: +- label: +- remove: +- - needs-ci diff --git a/SOURCES/0378-ci-enable-auto-merge-GH-Action.patch b/SOURCES/0378-ci-enable-auto-merge-GH-Action.patch new file mode 100644 index 0000000..ca7b6ab --- /dev/null +++ b/SOURCES/0378-ci-enable-auto-merge-GH-Action.patch @@ -0,0 +1,84 @@ +From 34adeef90a0a8cc1210742e5623968cbb39222eb Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Fri, 3 Nov 2023 12:32:18 +0100 +Subject: [PATCH] ci: enable auto-merge GH Action + +rhel-only + +Related: RHEL-1086 +--- + .github/auto-merge.yml | 4 ++++ + .../source-git-automation-on-demand.yml | 14 ++++++++++++ + .github/workflows/source-git-automation.yml | 22 +++++++++++++++++++ + 3 files changed, 40 insertions(+) + create mode 100644 .github/auto-merge.yml + +diff --git a/.github/auto-merge.yml b/.github/auto-merge.yml +new file mode 100644 +index 0000000000..35c2539295 +--- /dev/null ++++ b/.github/auto-merge.yml +@@ -0,0 +1,4 @@ ++labels: ++ dont-merge: dont-merge ++ manual-merge: pr/needs-manual-merge ++target-branch': ['main'] +diff --git a/.github/workflows/source-git-automation-on-demand.yml b/.github/workflows/source-git-automation-on-demand.yml +index 2dd6af3113..2c506f2b3e 100644 +--- a/.github/workflows/source-git-automation-on-demand.yml ++++ b/.github/workflows/source-git-automation-on-demand.yml +@@ -60,6 +60,7 @@ jobs: + pr-number: ${{ inputs.pr-number == 0 && fromJSON(needs.gather-pull-requests.outputs.pr-numbers) || fromJSON(needs.gather-pull-requests.outputs.pr-numbers-manual) }} + + permissions: ++ contents: write + statuses: write + checks: write + pull-requests: write +@@ -104,3 +105,16 @@ jobs: + with: + pr-metadata: ${{ steps.metadata.outputs.metadata }} + token: ${{ secrets.GITHUB_TOKEN }} ++ ++ - id: auto-merge ++ name: Auto Merge ++ uses: redhat-plumbers-in-action/auto-merge@v1 ++ with: ++ pr-metadata: ${{ steps.metadata.outputs.metadata }} ++ tracker: ${{ fromJSON(steps.commit-linter.outputs.validated-pr-metadata).validation.tracker.id }} ++ tracker-type: ${{ fromJSON(steps.commit-linter.outputs.validated-pr-metadata).validation.tracker.type }} ++ bugzilla-instance: https://bugzilla.redhat.com ++ bugzilla-api-token: ${{ secrets.BUGZILLA_API_TOKEN }} ++ jira-instance: https://issues.redhat.com ++ jira-api-token: ${{ secrets.JIRA_API_TOKEN }} ++ token: ${{ secrets.GITHUB_TOKEN }} +diff --git a/.github/workflows/source-git-automation.yml b/.github/workflows/source-git-automation.yml +index 214e72de6f..17135b590f 100644 +--- a/.github/workflows/source-git-automation.yml ++++ b/.github/workflows/source-git-automation.yml +@@ -84,3 +84,25 @@ jobs: + with: + pr-metadata: ${{ needs.download-metadata.outputs.pr-metadata }} + token: ${{ secrets.GITHUB_TOKEN }} ++ ++ auto-merge: ++ needs: [ download-metadata, commit-linter, tracker-validator, pull-request-validator ] ++ runs-on: ubuntu-latest ++ ++ permissions: ++ contents: write ++ checks: write ++ pull-requests: write ++ ++ steps: ++ - name: Auto Merge ++ uses: redhat-plumbers-in-action/auto-merge@v1 ++ with: ++ pr-metadata: ${{ needs.download-metadata.outputs.pr-metadata }} ++ tracker: ${{ fromJSON(needs.commit-linter.outputs.validated-pr-metadata).validation.tracker.id }} ++ tracker-type: ${{ fromJSON(needs.commit-linter.outputs.validated-pr-metadata).validation.tracker.type }} ++ bugzilla-instance: https://bugzilla.redhat.com ++ bugzilla-api-token: ${{ secrets.BUGZILLA_API_TOKEN }} ++ jira-instance: https://issues.redhat.com ++ jira-api-token: ${{ secrets.JIRA_API_TOKEN }} ++ token: ${{ secrets.GITHUB_TOKEN }} diff --git a/SOURCES/0379-ci-add-missing-permissions.patch b/SOURCES/0379-ci-add-missing-permissions.patch new file mode 100644 index 0000000..db67a24 --- /dev/null +++ b/SOURCES/0379-ci-add-missing-permissions.patch @@ -0,0 +1,41 @@ +From 715b05f97cde12424bb6d425264569f7f921dc72 Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Tue, 7 Nov 2023 13:33:40 +0100 +Subject: [PATCH] ci: add missing permissions + +issues: write is required for the pull request merging according to: + +https://github.com/cli/cli/discussions/6379#discussioncomment-3806051 + +rhel-only + +Related: RHEL-1086 +--- + .github/workflows/source-git-automation-on-demand.yml | 1 + + .github/workflows/source-git-automation.yml | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/.github/workflows/source-git-automation-on-demand.yml b/.github/workflows/source-git-automation-on-demand.yml +index 2c506f2b3e..bf2ea2260c 100644 +--- a/.github/workflows/source-git-automation-on-demand.yml ++++ b/.github/workflows/source-git-automation-on-demand.yml +@@ -63,6 +63,7 @@ jobs: + contents: write + statuses: write + checks: write ++ issues: write + pull-requests: write + + steps: +diff --git a/.github/workflows/source-git-automation.yml b/.github/workflows/source-git-automation.yml +index 17135b590f..d71664efa0 100644 +--- a/.github/workflows/source-git-automation.yml ++++ b/.github/workflows/source-git-automation.yml +@@ -92,6 +92,7 @@ jobs: + permissions: + contents: write + checks: write ++ issues: write + pull-requests: write + + steps: diff --git a/SOURCES/0380-ci-permissions-write-all.patch b/SOURCES/0380-ci-permissions-write-all.patch new file mode 100644 index 0000000..1ebd8a7 --- /dev/null +++ b/SOURCES/0380-ci-permissions-write-all.patch @@ -0,0 +1,47 @@ +From 3d7593697ac29c7308ef72453a621a1d24662415 Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Tue, 7 Nov 2023 14:25:02 +0100 +Subject: [PATCH] ci: `permissions: write-all` + +rhel-only + +Related: RHEL-1086 +--- + .github/workflows/source-git-automation-on-demand.yml | 11 +++++------ + .github/workflows/source-git-automation.yml | 1 - + 2 files changed, 5 insertions(+), 7 deletions(-) + +diff --git a/.github/workflows/source-git-automation-on-demand.yml b/.github/workflows/source-git-automation-on-demand.yml +index bf2ea2260c..3f3da959c4 100644 +--- a/.github/workflows/source-git-automation-on-demand.yml ++++ b/.github/workflows/source-git-automation-on-demand.yml +@@ -59,12 +59,11 @@ jobs: + matrix: + pr-number: ${{ inputs.pr-number == 0 && fromJSON(needs.gather-pull-requests.outputs.pr-numbers) || fromJSON(needs.gather-pull-requests.outputs.pr-numbers-manual) }} + +- permissions: +- contents: write +- statuses: write +- checks: write +- issues: write +- pull-requests: write ++ permissions: write-all ++ # contents: write ++ # statuses: write ++ # checks: write ++ # pull-requests: write + + steps: + - name: Repository checkout +diff --git a/.github/workflows/source-git-automation.yml b/.github/workflows/source-git-automation.yml +index d71664efa0..17135b590f 100644 +--- a/.github/workflows/source-git-automation.yml ++++ b/.github/workflows/source-git-automation.yml +@@ -92,7 +92,6 @@ jobs: + permissions: + contents: write + checks: write +- issues: write + pull-requests: write + + steps: diff --git a/SOURCES/0381-ci-lint-exclude-.in-files-from-ShellCheck-lint.patch b/SOURCES/0381-ci-lint-exclude-.in-files-from-ShellCheck-lint.patch new file mode 100644 index 0000000..2eb8f5c --- /dev/null +++ b/SOURCES/0381-ci-lint-exclude-.in-files-from-ShellCheck-lint.patch @@ -0,0 +1,33 @@ +From 594d2eb17d4548313eddf4e13ac8c734b268ae93 Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Mon, 7 Aug 2023 15:11:00 +0200 +Subject: [PATCH] ci(lint): exclude `.in` files from ShellCheck lint + +Exclude all `.in` files because they may contain unsupported syntax, and +they have to be preprocessed first. For example: + +```sh +Error: SHELLCHECK_WARNING: +./src/rpm/systemd-update-helper.in:130:37: warning[SC1083]: This { is literal. Check expression (missing ;/\n?) or quote it. +``` + +Related to: https://github.com/systemd/systemd/pull/28521 + +(cherry picked from commit 97eb82682126e7f3ee956a025078ea2b801955cb) + +Related: RHEL-1086 +--- + .github/workflows/differential-shellcheck.yml | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/.github/workflows/differential-shellcheck.yml b/.github/workflows/differential-shellcheck.yml +index 3662126304..0d3eee48e2 100644 +--- a/.github/workflows/differential-shellcheck.yml ++++ b/.github/workflows/differential-shellcheck.yml +@@ -32,4 +32,6 @@ jobs: + - name: Differential ShellCheck + uses: redhat-plumbers-in-action/differential-shellcheck@v4 + with: ++ # exclude all `.in` files because they may contain unsupported syntax, and they have to be preprocessed first ++ exclude-path: '**/*.in' + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/SOURCES/0382-udev-raise-RLIMIT_NOFILE-as-high-as-we-can.patch b/SOURCES/0382-udev-raise-RLIMIT_NOFILE-as-high-as-we-can.patch new file mode 100644 index 0000000..46b993f --- /dev/null +++ b/SOURCES/0382-udev-raise-RLIMIT_NOFILE-as-high-as-we-can.patch @@ -0,0 +1,43 @@ +From 5221edaee281175e3a8ba3e676ba5622085eb1ef Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 26 Sep 2023 09:52:05 +0200 +Subject: [PATCH] udev: raise RLIMIT_NOFILE as high as we can + +We might need a lot of fds on large systems, hence raise RLIMIT_NOFILE +to what the service manager allows us, which is quite a lot these days. + +udev already sets FORK_RLIMIT_NOFILE_SAFE when forking of chilren, thus +ensuring that forked off processes get their RLIMIT_NOFILE soft limit +reset to 1K for compat with crappy old select(). + +Replaces: #29298 +Fixes: #28583 +(cherry picked from commit 1617424ce76d797d081dd6cb1082b954c4d2bf38) + +Resolves: RHEL-11040 +--- + src/udev/udevd.c | 4 ++++ + 1 file changed, 4 insertions(+) + +diff --git a/src/udev/udevd.c b/src/udev/udevd.c +index e3a2742733..ccc3c0eece 100644 +--- a/src/udev/udevd.c ++++ b/src/udev/udevd.c +@@ -55,6 +55,7 @@ + #include "pretty-print.h" + #include "proc-cmdline.h" + #include "process-util.h" ++#include "rlimit-util.h" + #include "selinux-util.h" + #include "signal-util.h" + #include "socket-util.h" +@@ -2040,6 +2041,9 @@ int run_udevd(int argc, char *argv[]) { + if (r < 0) + return r; + ++ /* Make sure we can have plenty fds (for example for pidfds) */ ++ (void) rlimit_nofile_bump(-1); ++ + r = RET_NERRNO(mkdir("/run/udev", 0755)); + if (r < 0 && r != -EEXIST) + return log_error_errno(r, "Failed to create /run/udev: %m"); diff --git a/SOURCES/0383-udev-net-allow-new-link-name-as-an-altname-before-re.patch b/SOURCES/0383-udev-net-allow-new-link-name-as-an-altname-before-re.patch new file mode 100644 index 0000000..ace4e34 --- /dev/null +++ b/SOURCES/0383-udev-net-allow-new-link-name-as-an-altname-before-re.patch @@ -0,0 +1,37 @@ +From 57d5e48a572b98d6ab978072daddac2f7faf8dc8 Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Wed, 2 Nov 2022 11:05:01 -0400 +Subject: [PATCH] udev/net: allow new link name as an altname before renaming + happens + +When configuring a link's alternative names, the link's new name to-be +is not allowed to be included because interface renaming will fail if +the new name is already present as an alternative name. However, +rtnl_set_link_name will delete the conflicting alternative name before +renaming the device, if necessary. + +Allow the new link name to be set as an alternative name before the +device is renamed. This means that if the rename is later skipped (i.e. +because the link is already up), then the name can at least still be +present as an alternative name. + +(cherry picked from commit d0b31efc1ab7f6826ad834cf6b9e371bf73776aa) + +Related: RHEL-5988 +--- + src/udev/net/link-config.c | 2 -- + 1 file changed, 2 deletions(-) + +diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c +index e408725b08..5d28526527 100644 +--- a/src/udev/net/link-config.c ++++ b/src/udev/net/link-config.c +@@ -841,8 +841,6 @@ static int link_apply_alternative_names(Link *link, sd_netlink **rtnl) { + } + } + +- if (link->new_name) +- strv_remove(altnames, link->new_name); + strv_remove(altnames, link->ifname); + + r = rtnl_get_link_alternative_names(rtnl, link->ifindex, ¤t_altnames); diff --git a/SOURCES/0384-sd-netlink-do-not-swap-old-name-and-alternative-name.patch b/SOURCES/0384-sd-netlink-do-not-swap-old-name-and-alternative-name.patch new file mode 100644 index 0000000..9ecaa8d --- /dev/null +++ b/SOURCES/0384-sd-netlink-do-not-swap-old-name-and-alternative-name.patch @@ -0,0 +1,64 @@ +From ded04e17443f1e9a99705d39ae7dde72eb24ef34 Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Fri, 2 Dec 2022 15:26:18 -0500 +Subject: [PATCH] sd-netlink: do not swap old name and alternative name + +Commit 434a348380 ("netlink: do not fail when new interface name is +already used as an alternative name") added logic to set the old +interface name as an alternative name, but only when the new name is +currently an alternative name. This is not the desired outcome in most +cases, and the important part of this commit was to delete the new name +from the list of alternative names if necessary. + +(cherry picked from commit 080afbb57c4b2d592c5cf77ab10c6e0be74f0732) + +Related: RHEL-5988 +--- + src/libsystemd/sd-netlink/netlink-util.c | 13 ------------- + 1 file changed, 13 deletions(-) + +diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c +index 12cdc99ff2..6b4c25fe5a 100644 +--- a/src/libsystemd/sd-netlink/netlink-util.c ++++ b/src/libsystemd/sd-netlink/netlink-util.c +@@ -3,7 +3,6 @@ + #include "sd-netlink.h" + + #include "fd-util.h" +-#include "format-util.h" + #include "io-util.h" + #include "memory-util.h" + #include "netlink-internal.h" +@@ -15,7 +14,6 @@ + int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + _cleanup_strv_free_ char **alternative_names = NULL; +- char old_name[IF_NAMESIZE] = {}; + int r; + + assert(rtnl); +@@ -35,10 +33,6 @@ int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + if (r < 0) + return log_debug_errno(r, "Failed to remove '%s' from alternative names on network interface %i: %m", + name, ifindex); +- +- r = format_ifname(ifindex, old_name); +- if (r < 0) +- return log_debug_errno(r, "Failed to get current name of network interface %i: %m", ifindex); + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); +@@ -53,13 +47,6 @@ int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + if (r < 0) + return r; + +- if (!isempty(old_name)) { +- r = rtnl_set_link_alternative_names(rtnl, ifindex, STRV_MAKE(old_name)); +- if (r < 0) +- log_debug_errno(r, "Failed to set '%s' as an alternative name on network interface %i, ignoring: %m", +- old_name, ifindex); +- } +- + return 0; + } + diff --git a/SOURCES/0385-sd-netlink-restore-altname-on-error-in-rtnl_set_link.patch b/SOURCES/0385-sd-netlink-restore-altname-on-error-in-rtnl_set_link.patch new file mode 100644 index 0000000..2b924de --- /dev/null +++ b/SOURCES/0385-sd-netlink-restore-altname-on-error-in-rtnl_set_link.patch @@ -0,0 +1,66 @@ +From 9b6a3b192ba0f22ce99aa5c48c6c7143d12dddba Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Wed, 2 Nov 2022 05:36:14 -0400 +Subject: [PATCH] sd-netlink: restore altname on error in rtnl_set_link_name + +If a current alternative name is to be used to rename a network +interface, the alternative name must be removed first. If interface +renaming fails, restore the alternative name that was deleted if +necessary. + +(cherry picked from commit 4d600667f8af2985850b03a46357e068d3fb8570) + +Related: RHEL-5988 +--- + src/libsystemd/sd-netlink/netlink-util.c | 19 ++++++++++++++++--- + 1 file changed, 16 insertions(+), 3 deletions(-) + +diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c +index 6b4c25fe5a..cfcf2578d6 100644 +--- a/src/libsystemd/sd-netlink/netlink-util.c ++++ b/src/libsystemd/sd-netlink/netlink-util.c +@@ -14,6 +14,7 @@ + int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + _cleanup_strv_free_ char **alternative_names = NULL; ++ bool altname_deleted = false; + int r; + + assert(rtnl); +@@ -33,21 +34,33 @@ int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + if (r < 0) + return log_debug_errno(r, "Failed to remove '%s' from alternative names on network interface %i: %m", + name, ifindex); ++ ++ altname_deleted = true; + } + + r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); + if (r < 0) +- return r; ++ goto fail; + + r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); + if (r < 0) +- return r; ++ goto fail; + + r = sd_netlink_call(*rtnl, message, 0, NULL); + if (r < 0) +- return r; ++ goto fail; + + return 0; ++ ++fail: ++ if (altname_deleted) { ++ int q = rtnl_set_link_alternative_names(rtnl, ifindex, STRV_MAKE(name)); ++ if (q < 0) ++ log_debug_errno(q, "Failed to restore '%s' as an alternative name on network interface %i, ignoring: %m", ++ name, ifindex); ++ } ++ ++ return r; + } + + int rtnl_set_link_properties( diff --git a/SOURCES/0386-udev-attempt-device-rename-even-if-interface-is-up.patch b/SOURCES/0386-udev-attempt-device-rename-even-if-interface-is-up.patch new file mode 100644 index 0000000..a6b1875 --- /dev/null +++ b/SOURCES/0386-udev-attempt-device-rename-even-if-interface-is-up.patch @@ -0,0 +1,65 @@ +From ca122b3f1e00ba6a70e7575266502579108c4b47 Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Fri, 2 Dec 2022 15:35:25 -0500 +Subject: [PATCH] udev: attempt device rename even if interface is up + +Currently rename_netif() will not attempt to rename a device if it is +already up, because the kernel will return -EBUSY unless live renaming +is allowed on the device. This restriction will be removed in a future +kernel version [1]. + +To cover both cases, always attempt to rename the interface and return 0 +if we get -EBUSY. + +[1] https://git.kernel.org/pub/scm/linux/kernel/git/netdev/net-next.git/commit/?id=bd039b5ea2a9 + +(cherry picked from commit 53584e7b61373c26635b906eb64e98fbd3fd3ba4) + +Related: RHEL-5988 +--- + src/udev/udev-event.c | 18 ++++++------------ + 1 file changed, 6 insertions(+), 12 deletions(-) + +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index b3d92d5150..08d69cf1f0 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -862,7 +862,6 @@ int udev_event_spawn( + static int rename_netif(UdevEvent *event) { + const char *oldname; + sd_device *dev; +- unsigned flags; + int ifindex, r; + + assert(event); +@@ -896,17 +895,7 @@ static int rename_netif(UdevEvent *event) { + return 0; + } + +- r = rtnl_get_link_info(&event->rtnl, ifindex, NULL, &flags, NULL, NULL, NULL); +- if (r < 0) +- return log_device_warning_errno(dev, r, "Failed to get link flags: %m"); +- +- if (FLAGS_SET(flags, IFF_UP)) { +- log_device_info(dev, "Network interface '%s' is already up, refusing to rename to '%s'.", +- oldname, event->name); +- return 0; +- } +- +- /* Set ID_RENAMING boolean property here, and drop it in the corresponding move uevent later. */ ++ /* Set ID_RENAMING boolean property here. It will be dropped when the corresponding move uevent is processed. */ + r = device_add_property(dev, "ID_RENAMING", "1"); + if (r < 0) + return log_device_warning_errno(dev, r, "Failed to add 'ID_RENAMING' property: %m"); +@@ -927,6 +916,11 @@ static int rename_netif(UdevEvent *event) { + return log_device_debug_errno(event->dev_db_clone, r, "Failed to update database under /run/udev/data/: %m"); + + r = rtnl_set_link_name(&event->rtnl, ifindex, event->name); ++ if (r == -EBUSY) { ++ log_device_info(dev, "Network interface '%s' is already up, cannot rename to '%s'.", ++ oldname, event->name); ++ return 0; ++ } + if (r < 0) + return log_device_error_errno(dev, r, "Failed to rename network interface %i from '%s' to '%s': %m", + ifindex, oldname, event->name); diff --git a/SOURCES/0387-sd-netlink-add-a-test-for-rtnl_set_link_name.patch b/SOURCES/0387-sd-netlink-add-a-test-for-rtnl_set_link_name.patch new file mode 100644 index 0000000..c9ccbd9 --- /dev/null +++ b/SOURCES/0387-sd-netlink-add-a-test-for-rtnl_set_link_name.patch @@ -0,0 +1,74 @@ +From 32188058ad3b9a8bfc555215982145a128adfc44 Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Tue, 22 Nov 2022 17:01:47 -0500 +Subject: [PATCH] sd-netlink: add a test for rtnl_set_link_name() + +Add a test that verifies a deleted alternative name is restored on error +in rtnl_set_link_name(). + +(cherry picked from commit b338a8bb402a3ab241a617e096b21ae6a7b7badf) + +Related: RHEL-5988 +--- + src/libsystemd/sd-netlink/test-netlink.c | 27 ++++++++++++++++++++++++ + 1 file changed, 27 insertions(+) + +diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c +index 3f74ecc068..2d93f9eba0 100644 +--- a/src/libsystemd/sd-netlink/test-netlink.c ++++ b/src/libsystemd/sd-netlink/test-netlink.c +@@ -8,6 +8,7 @@ + #include + #include + #include ++#include + + #include "sd-netlink.h" + +@@ -16,6 +17,7 @@ + #include "macro.h" + #include "netlink-genl.h" + #include "netlink-internal.h" ++#include "netlink-util.h" + #include "socket-util.h" + #include "stdio-util.h" + #include "string-util.h" +@@ -667,6 +669,30 @@ static void test_genl(void) { + } + } + ++static void test_rtnl_set_link_name(sd_netlink *rtnl, int ifindex) { ++ _cleanup_strv_free_ char **alternative_names = NULL; ++ int r; ++ ++ log_debug("/* %s */", __func__); ++ ++ if (geteuid() != 0) ++ return (void) log_tests_skipped("not root"); ++ ++ /* Test that the new name (which is currently an alternative name) is ++ * restored as an alternative name on error. Create an error by using ++ * an invalid device name, namely one that exceeds IFNAMSIZ ++ * (alternative names can exceed IFNAMSIZ, but not regular names). */ ++ r = rtnl_set_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")); ++ if (r == -EPERM) ++ return (void) log_tests_skipped("missing required capabilities"); ++ ++ assert_se(r >= 0); ++ assert_se(rtnl_set_link_name(&rtnl, ifindex, "testlongalternativename") == -EINVAL); ++ assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); ++ assert_se(strv_contains(alternative_names, "testlongalternativename")); ++ assert_se(rtnl_delete_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")) >= 0); ++} ++ + int main(void) { + sd_netlink *rtnl; + sd_netlink_message *m; +@@ -698,6 +724,7 @@ int main(void) { + test_pipe(if_loopback); + test_event_loop(if_loopback); + test_link_configure(rtnl, if_loopback); ++ test_rtnl_set_link_name(rtnl, if_loopback); + + test_get_addresses(rtnl); + test_message_link_bridge(rtnl); diff --git a/SOURCES/0388-test-network-add-a-test-for-renaming-device-to-curre.patch b/SOURCES/0388-test-network-add-a-test-for-renaming-device-to-curre.patch new file mode 100644 index 0000000..9026e93 --- /dev/null +++ b/SOURCES/0388-test-network-add-a-test-for-renaming-device-to-curre.patch @@ -0,0 +1,50 @@ +From 6e095bdbd88ddbe289210720e7a55b62fa593ab8 Mon Sep 17 00:00:00 2001 +From: Nick Rosbrook +Date: Wed, 7 Dec 2022 12:28:28 -0500 +Subject: [PATCH] test-network: add a test for renaming device to current + altname + +(cherry picked from commit f68f644a167af3452be853b631fa9144c6716c28) + +Related: RHEL-5988 +--- + .../test-network/conf/12-dummy-rename-to-altname.link | 7 +++++++ + test/test-network/systemd-networkd-tests.py | 11 +++++++++++ + 2 files changed, 18 insertions(+) + create mode 100644 test/test-network/conf/12-dummy-rename-to-altname.link + +diff --git a/test/test-network/conf/12-dummy-rename-to-altname.link b/test/test-network/conf/12-dummy-rename-to-altname.link +new file mode 100644 +index 0000000000..bef4bf3dc5 +--- /dev/null ++++ b/test/test-network/conf/12-dummy-rename-to-altname.link +@@ -0,0 +1,7 @@ ++# SPDX-License-Identifier: LGPL-2.1-or-later ++[Match] ++OriginalName=dummy98 ++ ++[Link] ++Name=dummyalt ++AlternativeName=dummyalt hogehogehogehogehogehoge +diff --git a/test/test-network/systemd-networkd-tests.py b/test/test-network/systemd-networkd-tests.py +index 87710ef3fb..696618790a 100755 +--- a/test/test-network/systemd-networkd-tests.py ++++ b/test/test-network/systemd-networkd-tests.py +@@ -936,6 +936,17 @@ class NetworkctlTests(unittest.TestCase, Utilities): + output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummy98', env=env) + self.assertRegex(output, 'hogehogehogehogehogehoge') + ++ @expectedFailureIfAlternativeNameIsNotAvailable() ++ def test_rename_to_altname(self): ++ copy_network_unit('26-netdev-link-local-addressing-yes.network', ++ '12-dummy.netdev', '12-dummy-rename-to-altname.link') ++ start_networkd() ++ self.wait_online(['dummyalt:degraded']) ++ ++ output = check_output(*networkctl_cmd, '-n', '0', 'status', 'dummyalt', env=env) ++ self.assertIn('hogehogehogehogehogehoge', output) ++ self.assertNotIn('dummy98', output) ++ + def test_reconfigure(self): + copy_network_unit('25-address-static.network', '12-dummy.netdev') + start_networkd() diff --git a/SOURCES/0389-udev-align-table.patch b/SOURCES/0389-udev-align-table.patch new file mode 100644 index 0000000..535e1e4 --- /dev/null +++ b/SOURCES/0389-udev-align-table.patch @@ -0,0 +1,58 @@ +From d808bd97790dd8a38d844c827d2d9dbcb700d8c0 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 11:20:35 +0900 +Subject: [PATCH] udev: align table + +(cherry picked from commit bb1234d1d6b7b14424093a917890bb4013b4ff3e) + +Related: RHEL-5988 +--- + src/udev/udev-event.c | 34 +++++++++++++++++----------------- + 1 file changed, 17 insertions(+), 17 deletions(-) + +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index 08d69cf1f0..3ac12d9b52 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -119,24 +119,24 @@ struct subst_map_entry { + }; + + static const struct subst_map_entry map[] = { +- { .name = "devnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, +- { .name = "tempnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, /* deprecated */ +- { .name = "attr", .fmt = 's', .type = FORMAT_SUBST_ATTR }, +- { .name = "sysfs", .fmt = 's', .type = FORMAT_SUBST_ATTR }, /* deprecated */ +- { .name = "env", .fmt = 'E', .type = FORMAT_SUBST_ENV }, +- { .name = "kernel", .fmt = 'k', .type = FORMAT_SUBST_KERNEL }, ++ { .name = "devnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, ++ { .name = "tempnode", .fmt = 'N', .type = FORMAT_SUBST_DEVNODE }, /* deprecated */ ++ { .name = "attr", .fmt = 's', .type = FORMAT_SUBST_ATTR }, ++ { .name = "sysfs", .fmt = 's', .type = FORMAT_SUBST_ATTR }, /* deprecated */ ++ { .name = "env", .fmt = 'E', .type = FORMAT_SUBST_ENV }, ++ { .name = "kernel", .fmt = 'k', .type = FORMAT_SUBST_KERNEL }, + { .name = "number", .fmt = 'n', .type = FORMAT_SUBST_KERNEL_NUMBER }, +- { .name = "driver", .fmt = 'd', .type = FORMAT_SUBST_DRIVER }, +- { .name = "devpath", .fmt = 'p', .type = FORMAT_SUBST_DEVPATH }, +- { .name = "id", .fmt = 'b', .type = FORMAT_SUBST_ID }, +- { .name = "major", .fmt = 'M', .type = FORMAT_SUBST_MAJOR }, +- { .name = "minor", .fmt = 'm', .type = FORMAT_SUBST_MINOR }, +- { .name = "result", .fmt = 'c', .type = FORMAT_SUBST_RESULT }, +- { .name = "parent", .fmt = 'P', .type = FORMAT_SUBST_PARENT }, +- { .name = "name", .fmt = 'D', .type = FORMAT_SUBST_NAME }, +- { .name = "links", .fmt = 'L', .type = FORMAT_SUBST_LINKS }, +- { .name = "root", .fmt = 'r', .type = FORMAT_SUBST_ROOT }, +- { .name = "sys", .fmt = 'S', .type = FORMAT_SUBST_SYS }, ++ { .name = "driver", .fmt = 'd', .type = FORMAT_SUBST_DRIVER }, ++ { .name = "devpath", .fmt = 'p', .type = FORMAT_SUBST_DEVPATH }, ++ { .name = "id", .fmt = 'b', .type = FORMAT_SUBST_ID }, ++ { .name = "major", .fmt = 'M', .type = FORMAT_SUBST_MAJOR }, ++ { .name = "minor", .fmt = 'm', .type = FORMAT_SUBST_MINOR }, ++ { .name = "result", .fmt = 'c', .type = FORMAT_SUBST_RESULT }, ++ { .name = "parent", .fmt = 'P', .type = FORMAT_SUBST_PARENT }, ++ { .name = "name", .fmt = 'D', .type = FORMAT_SUBST_NAME }, ++ { .name = "links", .fmt = 'L', .type = FORMAT_SUBST_LINKS }, ++ { .name = "root", .fmt = 'r', .type = FORMAT_SUBST_ROOT }, ++ { .name = "sys", .fmt = 'S', .type = FORMAT_SUBST_SYS }, + }; + + static const char *format_type_to_string(FormatSubstitutionType t) { diff --git a/SOURCES/0390-sd-device-make-device_set_syspath-clear-sysname-and-.patch b/SOURCES/0390-sd-device-make-device_set_syspath-clear-sysname-and-.patch new file mode 100644 index 0000000..6834db5 --- /dev/null +++ b/SOURCES/0390-sd-device-make-device_set_syspath-clear-sysname-and-.patch @@ -0,0 +1,47 @@ +From 3b4d91e7ab44738f3773a3bfd4a6c5fb9bbc7322 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 14:00:09 +0900 +Subject: [PATCH] sd-device: make device_set_syspath() clear sysname and sysnum + +Otherwise, when a new syspath is assigned to the sd-device object, +sd_device_get_sysname() or _sysnum() will provide an outdated device +name or number. + +(cherry picked from commit 9a26098e90116fdb5fcffa03485b375ee0c82b6a) + +Related: RHEL-5988 +--- + src/libsystemd/sd-device/device-private.c | 4 ---- + src/libsystemd/sd-device/sd-device.c | 4 ++++ + 2 files changed, 4 insertions(+), 4 deletions(-) + +diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c +index bc7a838608..2c1d922ea3 100644 +--- a/src/libsystemd/sd-device/device-private.c ++++ b/src/libsystemd/sd-device/device-private.c +@@ -646,10 +646,6 @@ int device_rename(sd_device *device, const char *name) { + if (r < 0) + return r; + +- /* Here, only clear the sysname and sysnum. They will be set when requested. */ +- device->sysnum = NULL; +- device->sysname = mfree(device->sysname); +- + r = sd_device_get_property_value(device, "INTERFACE", &interface); + if (r == -ENOENT) + return 0; +diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c +index f2e142457b..c822a0b2f0 100644 +--- a/src/libsystemd/sd-device/sd-device.c ++++ b/src/libsystemd/sd-device/sd-device.c +@@ -250,6 +250,10 @@ int device_set_syspath(sd_device *device, const char *_syspath, bool verify) { + + free_and_replace(device->syspath, syspath); + device->devpath = devpath; ++ ++ /* Unset sysname and sysnum, they will be assigned when requested. */ ++ device->sysnum = NULL; ++ device->sysname = mfree(device->sysname); + return 0; + } + diff --git a/SOURCES/0391-sd-device-do-not-directly-access-entry-in-sd-device-.patch b/SOURCES/0391-sd-device-do-not-directly-access-entry-in-sd-device-.patch new file mode 100644 index 0000000..989573c --- /dev/null +++ b/SOURCES/0391-sd-device-do-not-directly-access-entry-in-sd-device-.patch @@ -0,0 +1,62 @@ +From 2c6ea8a97986c58954603b587875a52b043e4d9b Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 14:07:16 +0900 +Subject: [PATCH] sd-device: do not directly access entry in sd-device object + +No functional change, just refactoring. + +(cherry picked from commit 1de6a49721957a85a4934ddbdf88d535774597b1) + +Related: RHEL-5988 +--- + src/libsystemd/sd-device/device-private.c | 14 +++++++++----- + 1 file changed, 9 insertions(+), 5 deletions(-) + +diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c +index 2c1d922ea3..9cec037237 100644 +--- a/src/libsystemd/sd-device/device-private.c ++++ b/src/libsystemd/sd-device/device-private.c +@@ -621,7 +621,7 @@ int device_get_devlink_priority(sd_device *device, int *ret) { + + int device_rename(sd_device *device, const char *name) { + _cleanup_free_ char *new_syspath = NULL; +- const char *interface; ++ const char *s; + int r; + + assert(device); +@@ -630,7 +630,11 @@ int device_rename(sd_device *device, const char *name) { + if (!filename_is_valid(name)) + return -EINVAL; + +- r = path_extract_directory(device->syspath, &new_syspath); ++ r = sd_device_get_syspath(device, &s); ++ if (r < 0) ++ return r; ++ ++ r = path_extract_directory(s, &new_syspath); + if (r < 0) + return r; + +@@ -642,18 +646,18 @@ int device_rename(sd_device *device, const char *name) { + + /* At the time this is called, the renamed device may not exist yet. Hence, we cannot validate + * the new syspath. */ +- r = device_set_syspath(device, new_syspath, false); ++ r = device_set_syspath(device, new_syspath, /* verify = */ false); + if (r < 0) + return r; + +- r = sd_device_get_property_value(device, "INTERFACE", &interface); ++ r = sd_device_get_property_value(device, "INTERFACE", &s); + if (r == -ENOENT) + return 0; + if (r < 0) + return r; + + /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */ +- r = device_add_property_internal(device, "INTERFACE_OLD", interface); ++ r = device_add_property_internal(device, "INTERFACE_OLD", s); + if (r < 0) + return r; + diff --git a/SOURCES/0392-udev-move-device_rename-from-device-private.c.patch b/SOURCES/0392-udev-move-device_rename-from-device-private.c.patch new file mode 100644 index 0000000..75fb2ff --- /dev/null +++ b/SOURCES/0392-udev-move-device_rename-from-device-private.c.patch @@ -0,0 +1,148 @@ +From 7f183125fbec97bd6e4c0b3ac792b0e0c23132e0 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 15:00:30 +0900 +Subject: [PATCH] udev: move device_rename() from device-private.c + +The function is used only by udevd. + +(cherry picked from commit ff88b949531e70639c507f74da875a7de2adf543) + +Related: RHEL-5988 +--- + src/libsystemd/sd-device/device-private.c | 45 ---------------------- + src/libsystemd/sd-device/device-private.h | 1 - + src/udev/udev-event.c | 46 +++++++++++++++++++++++ + 3 files changed, 46 insertions(+), 46 deletions(-) + +diff --git a/src/libsystemd/sd-device/device-private.c b/src/libsystemd/sd-device/device-private.c +index 9cec037237..36b0da4f12 100644 +--- a/src/libsystemd/sd-device/device-private.c ++++ b/src/libsystemd/sd-device/device-private.c +@@ -619,51 +619,6 @@ int device_get_devlink_priority(sd_device *device, int *ret) { + return 0; + } + +-int device_rename(sd_device *device, const char *name) { +- _cleanup_free_ char *new_syspath = NULL; +- const char *s; +- int r; +- +- assert(device); +- assert(name); +- +- if (!filename_is_valid(name)) +- return -EINVAL; +- +- r = sd_device_get_syspath(device, &s); +- if (r < 0) +- return r; +- +- r = path_extract_directory(s, &new_syspath); +- if (r < 0) +- return r; +- +- if (!path_extend(&new_syspath, name)) +- return -ENOMEM; +- +- if (!path_is_safe(new_syspath)) +- return -EINVAL; +- +- /* At the time this is called, the renamed device may not exist yet. Hence, we cannot validate +- * the new syspath. */ +- r = device_set_syspath(device, new_syspath, /* verify = */ false); +- if (r < 0) +- return r; +- +- r = sd_device_get_property_value(device, "INTERFACE", &s); +- if (r == -ENOENT) +- return 0; +- if (r < 0) +- return r; +- +- /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */ +- r = device_add_property_internal(device, "INTERFACE_OLD", s); +- if (r < 0) +- return r; +- +- return device_add_property_internal(device, "INTERFACE", name); +-} +- + static int device_shallow_clone(sd_device *device, sd_device **ret) { + _cleanup_(sd_device_unrefp) sd_device *dest = NULL; + const char *val = NULL; +diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h +index a59f130aff..e57b74ba24 100644 +--- a/src/libsystemd/sd-device/device-private.h ++++ b/src/libsystemd/sd-device/device-private.h +@@ -53,7 +53,6 @@ int device_properties_prepare(sd_device *device); + int device_get_properties_nulstr(sd_device *device, const char **ret_nulstr, size_t *ret_len); + int device_get_properties_strv(sd_device *device, char ***ret); + +-int device_rename(sd_device *device, const char *name); + int device_clone_with_db(sd_device *device, sd_device **ret); + + int device_tag_index(sd_device *dev, sd_device *dev_old, bool add); +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index 3ac12d9b52..1dc05f863d 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -12,6 +12,7 @@ + #include "sd-event.h" + + #include "alloc-util.h" ++#include "device-internal.h" + #include "device-private.h" + #include "device-util.h" + #include "fd-util.h" +@@ -859,6 +860,51 @@ int udev_event_spawn( + return r; /* 0 for success, and positive if the program failed */ + } + ++static int device_rename(sd_device *device, const char *name) { ++ _cleanup_free_ char *new_syspath = NULL; ++ const char *s; ++ int r; ++ ++ assert(device); ++ assert(name); ++ ++ if (!filename_is_valid(name)) ++ return -EINVAL; ++ ++ r = sd_device_get_syspath(device, &s); ++ if (r < 0) ++ return r; ++ ++ r = path_extract_directory(s, &new_syspath); ++ if (r < 0) ++ return r; ++ ++ if (!path_extend(&new_syspath, name)) ++ return -ENOMEM; ++ ++ if (!path_is_safe(new_syspath)) ++ return -EINVAL; ++ ++ /* At the time this is called, the renamed device may not exist yet. Hence, we cannot validate ++ * the new syspath. */ ++ r = device_set_syspath(device, new_syspath, /* verify = */ false); ++ if (r < 0) ++ return r; ++ ++ r = sd_device_get_property_value(device, "INTERFACE", &s); ++ if (r == -ENOENT) ++ return 0; ++ if (r < 0) ++ return r; ++ ++ /* like DEVPATH_OLD, INTERFACE_OLD is not saved to the db, but only stays around for the current event */ ++ r = device_add_property_internal(device, "INTERFACE_OLD", s); ++ if (r < 0) ++ return r; ++ ++ return device_add_property_internal(device, "INTERFACE", name); ++} ++ + static int rename_netif(UdevEvent *event) { + const char *oldname; + sd_device *dev; diff --git a/SOURCES/0393-udev-restore-syspath-and-properties-on-failure.patch b/SOURCES/0393-udev-restore-syspath-and-properties-on-failure.patch new file mode 100644 index 0000000..dcba5f2 --- /dev/null +++ b/SOURCES/0393-udev-restore-syspath-and-properties-on-failure.patch @@ -0,0 +1,155 @@ +From 1f4bc8c496d2a310ffa3e7174af40f7e596cd2d1 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 14:58:58 +0900 +Subject: [PATCH] udev: restore syspath and properties on failure + +Otherwise, invalid sysname or properties may be broadcast to udev +listeners. + +(cherry picked from commit 210033847c340c43dd6835520f21f8b23ba29579) + +Related: RHEL-5988 +--- + src/udev/udev-event.c | 93 +++++++++++++++++++++++++++++-------------- + 1 file changed, 64 insertions(+), 29 deletions(-) + +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index 1dc05f863d..fab454ae37 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -906,7 +906,8 @@ static int device_rename(sd_device *device, const char *name) { + } + + static int rename_netif(UdevEvent *event) { +- const char *oldname; ++ _cleanup_free_ char *old_syspath = NULL, *old_sysname = NULL; ++ const char *s; + sd_device *dev; + int ifindex, r; + +@@ -917,15 +918,6 @@ static int rename_netif(UdevEvent *event) { + + dev = ASSERT_PTR(event->dev); + +- /* Read sysname from cloned sd-device object, otherwise use-after-free is triggered, as the +- * main object will be renamed and dev->sysname will be freed in device_rename(). */ +- r = sd_device_get_sysname(event->dev_db_clone, &oldname); +- if (r < 0) +- return log_device_error_errno(dev, r, "Failed to get sysname: %m"); +- +- if (streq(event->name, oldname)) +- return 0; /* The interface name is already requested name. */ +- + if (!device_for_action(dev, SD_DEVICE_ADD)) + return 0; /* Rename the interface only when it is added. */ + +@@ -933,7 +925,7 @@ static int rename_netif(UdevEvent *event) { + if (r == -ENOENT) + return 0; /* Device is not a network interface. */ + if (r < 0) +- return log_device_error_errno(dev, r, "Failed to get ifindex: %m"); ++ return log_device_warning_errno(dev, r, "Failed to get ifindex: %m"); + + if (naming_scheme_has(NAMING_REPLACE_STRICTLY) && + !ifname_valid(event->name)) { +@@ -941,39 +933,82 @@ static int rename_netif(UdevEvent *event) { + return 0; + } + +- /* Set ID_RENAMING boolean property here. It will be dropped when the corresponding move uevent is processed. */ +- r = device_add_property(dev, "ID_RENAMING", "1"); ++ r = sd_device_get_sysname(dev, &s); + if (r < 0) +- return log_device_warning_errno(dev, r, "Failed to add 'ID_RENAMING' property: %m"); ++ return log_device_warning_errno(dev, r, "Failed to get sysname: %m"); + +- r = device_rename(dev, event->name); ++ if (streq(event->name, s)) ++ return 0; /* The interface name is already requested name. */ ++ ++ old_sysname = strdup(s); ++ if (!old_sysname) ++ return -ENOMEM; ++ ++ r = sd_device_get_syspath(dev, &s); + if (r < 0) +- return log_device_warning_errno(dev, r, "Failed to update properties with new name '%s': %m", event->name); ++ return log_device_warning_errno(dev, r, "Failed to get syspath: %m"); ++ ++ old_syspath = strdup(s); ++ if (!old_syspath) ++ return -ENOMEM; ++ ++ r = device_rename(dev, event->name); ++ if (r < 0) { ++ log_device_warning_errno(dev, r, "Failed to update properties with new name '%s': %m", event->name); ++ goto revert; ++ } ++ ++ /* Set ID_RENAMING boolean property here. It will be dropped when the corresponding move uevent is processed. */ ++ r = device_add_property(dev, "ID_RENAMING", "1"); ++ if (r < 0) { ++ log_device_warning_errno(dev, r, "Failed to add 'ID_RENAMING' property: %m"); ++ goto revert; ++ } + + /* Also set ID_RENAMING boolean property to cloned sd_device object and save it to database + * before calling rtnl_set_link_name(). Otherwise, clients (e.g., systemd-networkd) may receive + * RTM_NEWLINK netlink message before the database is updated. */ + r = device_add_property(event->dev_db_clone, "ID_RENAMING", "1"); +- if (r < 0) +- return log_device_warning_errno(event->dev_db_clone, r, "Failed to add 'ID_RENAMING' property: %m"); ++ if (r < 0) { ++ log_device_warning_errno(event->dev_db_clone, r, "Failed to add 'ID_RENAMING' property: %m"); ++ goto revert; ++ } + + r = device_update_db(event->dev_db_clone); +- if (r < 0) +- return log_device_debug_errno(event->dev_db_clone, r, "Failed to update database under /run/udev/data/: %m"); ++ if (r < 0) { ++ log_device_debug_errno(event->dev_db_clone, r, "Failed to update database under /run/udev/data/: %m"); ++ goto revert; ++ } + + r = rtnl_set_link_name(&event->rtnl, ifindex, event->name); +- if (r == -EBUSY) { +- log_device_info(dev, "Network interface '%s' is already up, cannot rename to '%s'.", +- oldname, event->name); +- return 0; ++ if (r < 0) { ++ if (r == -EBUSY) { ++ log_device_info(dev, "Network interface '%s' is already up, cannot rename to '%s'.", ++ old_sysname, event->name); ++ r = 0; ++ } else ++ log_device_error_errno(dev, r, "Failed to rename network interface %i from '%s' to '%s': %m", ++ ifindex, old_sysname, event->name); ++ goto revert; + } +- if (r < 0) +- return log_device_error_errno(dev, r, "Failed to rename network interface %i from '%s' to '%s': %m", +- ifindex, oldname, event->name); +- +- log_device_debug(dev, "Network interface %i is renamed from '%s' to '%s'", ifindex, oldname, event->name); + ++ log_device_debug(dev, "Network interface %i is renamed from '%s' to '%s'", ifindex, old_sysname, event->name); + return 1; ++ ++revert: ++ /* Restore 'dev_db_clone' */ ++ (void) device_add_property(event->dev_db_clone, "ID_RENAMING", NULL); ++ (void) device_update_db(event->dev_db_clone); ++ ++ /* Restore 'dev' */ ++ (void) device_set_syspath(dev, old_syspath, /* verify = */ false); ++ if (sd_device_get_property_value(dev, "INTERFACE_OLD", &s) >= 0) { ++ (void) device_add_property_internal(dev, "INTERFACE", s); ++ (void) device_add_property_internal(dev, "INTERFACE_OLD", NULL); ++ } ++ (void) device_add_property(dev, "ID_RENAMING", NULL); ++ ++ return r; + } + + static int update_devnode(UdevEvent *event) { diff --git a/SOURCES/0394-sd-device-introduce-device_get_property_int.patch b/SOURCES/0394-sd-device-introduce-device_get_property_int.patch new file mode 100644 index 0000000..dbd2aab --- /dev/null +++ b/SOURCES/0394-sd-device-introduce-device_get_property_int.patch @@ -0,0 +1,56 @@ +From 284d6f9171ba819bcccb6a2df7c3012ba8483a0c Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 16:44:11 +0900 +Subject: [PATCH] sd-device: introduce device_get_property_int() + +(cherry picked from commit eedfef0f0d2654fcde2a3b694e62518d688af827) + +Related: RHEL-5988 +--- + src/libsystemd/sd-device/device-private.h | 1 + + src/libsystemd/sd-device/sd-device.c | 20 ++++++++++++++++++++ + 2 files changed, 21 insertions(+) + +diff --git a/src/libsystemd/sd-device/device-private.h b/src/libsystemd/sd-device/device-private.h +index e57b74ba24..d9a519a4d9 100644 +--- a/src/libsystemd/sd-device/device-private.h ++++ b/src/libsystemd/sd-device/device-private.h +@@ -18,6 +18,7 @@ int device_new_from_strv(sd_device **ret, char **strv); + int device_opendir(sd_device *device, const char *subdir, DIR **ret); + + int device_get_property_bool(sd_device *device, const char *key); ++int device_get_property_int(sd_device *device, const char *key, int *ret); + int device_get_sysattr_int(sd_device *device, const char *sysattr, int *ret_value); + int device_get_sysattr_unsigned(sd_device *device, const char *sysattr, unsigned *ret_value); + int device_get_sysattr_bool(sd_device *device, const char *sysattr); +diff --git a/src/libsystemd/sd-device/sd-device.c b/src/libsystemd/sd-device/sd-device.c +index c822a0b2f0..7ee67b4641 100644 +--- a/src/libsystemd/sd-device/sd-device.c ++++ b/src/libsystemd/sd-device/sd-device.c +@@ -2186,6 +2186,26 @@ int device_get_property_bool(sd_device *device, const char *key) { + return parse_boolean(value); + } + ++int device_get_property_int(sd_device *device, const char *key, int *ret) { ++ const char *value; ++ int r, v; ++ ++ assert(device); ++ assert(key); ++ ++ r = sd_device_get_property_value(device, key, &value); ++ if (r < 0) ++ return r; ++ ++ r = safe_atoi(value, &v); ++ if (r < 0) ++ return r; ++ ++ if (ret) ++ *ret = v; ++ return 0; ++} ++ + _public_ int sd_device_get_trigger_uuid(sd_device *device, sd_id128_t *ret) { + const char *s; + sd_id128_t id; diff --git a/SOURCES/0395-core-device-downgrade-log-level-for-ignored-errors.patch b/SOURCES/0395-core-device-downgrade-log-level-for-ignored-errors.patch new file mode 100644 index 0000000..d2b1f7b --- /dev/null +++ b/SOURCES/0395-core-device-downgrade-log-level-for-ignored-errors.patch @@ -0,0 +1,32 @@ +From 42a11f89c8836493847a69906ef2765e2e984dbf Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 16:11:04 +0900 +Subject: [PATCH] core/device: downgrade log level for ignored errors + +(cherry picked from commit 58b0a3e5112a27daa181383458f68955eb081551) + +Related: RHEL-5988 +--- + src/core/device.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/core/device.c b/src/core/device.c +index 224fc90835..09b7d56e1e 100644 +--- a/src/core/device.c ++++ b/src/core/device.c +@@ -1095,13 +1095,13 @@ static int device_dispatch_io(sd_device_monitor *monitor, sd_device *dev, void * + + r = sd_device_get_syspath(dev, &sysfs); + if (r < 0) { +- log_device_error_errno(dev, r, "Failed to get device syspath, ignoring: %m"); ++ log_device_warning_errno(dev, r, "Failed to get device syspath, ignoring: %m"); + return 0; + } + + r = sd_device_get_action(dev, &action); + if (r < 0) { +- log_device_error_errno(dev, r, "Failed to get udev action, ignoring: %m"); ++ log_device_warning_errno(dev, r, "Failed to get udev action, ignoring: %m"); + return 0; + } + diff --git a/SOURCES/0396-core-device-ignore-failed-uevents.patch b/SOURCES/0396-core-device-ignore-failed-uevents.patch new file mode 100644 index 0000000..e2464a3 --- /dev/null +++ b/SOURCES/0396-core-device-ignore-failed-uevents.patch @@ -0,0 +1,45 @@ +From b59fda96b0e24b93dcdb061da24c42a924ae0b20 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 16:11:52 +0900 +Subject: [PATCH] core/device: ignore failed uevents + +When udevd failed to process the device, SYSTEMD_ALIAS or any other +properties may contain invalid values. Let's refuse to handle the uevent. + +(cherry picked from commit e9336d6ac346df38d96c91ba0447b3c76ee6697b) + +Related: RHEL-5988 +--- + src/core/device.c | 19 +++++++++++++++++++ + 1 file changed, 19 insertions(+) + +diff --git a/src/core/device.c b/src/core/device.c +index 09b7d56e1e..f007bdfd9b 100644 +--- a/src/core/device.c ++++ b/src/core/device.c +@@ -1108,6 +1108,25 @@ static int device_dispatch_io(sd_device_monitor *monitor, sd_device *dev, void * + if (action == SD_DEVICE_MOVE) + device_remove_old_on_move(m, dev); + ++ /* When udevd failed to process the device, SYSTEMD_ALIAS or any other properties may contain invalid ++ * values. Let's refuse to handle the uevent. */ ++ if (sd_device_get_property_value(dev, "UDEV_WORKER_FAILED", NULL) >= 0) { ++ int v; ++ ++ if (device_get_property_int(dev, "UDEV_WORKER_ERRNO", &v) >= 0) ++ log_device_warning_errno(dev, v, "systemd-udevd failed to process the device, ignoring: %m"); ++ else if (device_get_property_int(dev, "UDEV_WORKER_EXIT_STATUS", &v) >= 0) ++ log_device_warning(dev, "systemd-udevd failed to process the device with exit status %i, ignoring.", v); ++ else if (device_get_property_int(dev, "UDEV_WORKER_SIGNAL", &v) >= 0) { ++ const char *s; ++ (void) sd_device_get_property_value(dev, "UDEV_WORKER_SIGNAL_NAME", &s); ++ log_device_warning(dev, "systemd-udevd failed to process the device with signal %i(%s), ignoring.", v, strna(s)); ++ } else ++ log_device_warning(dev, "systemd-udevd failed to process the device with unknown result, ignoring."); ++ ++ return 0; ++ } ++ + /* A change event can signal that a device is becoming ready, in particular if the device is using + * the SYSTEMD_READY logic in udev so we need to reach the else block of the following if, even for + * change events */ diff --git a/SOURCES/0397-test-add-tests-for-failure-in-renaming-network-inter.patch b/SOURCES/0397-test-add-tests-for-failure-in-renaming-network-inter.patch new file mode 100644 index 0000000..9cc2e65 --- /dev/null +++ b/SOURCES/0397-test-add-tests-for-failure-in-renaming-network-inter.patch @@ -0,0 +1,99 @@ +From 9e9f53612dc2356796cffb25826008944aede0e3 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 16:02:09 +0900 +Subject: [PATCH] test: add tests for failure in renaming network interface + +(cherry picked from commit 2d0d75b279924934c4c8e9acbc48456b01b71f00) + +Related: RHEL-5988 +--- + test/units/testsuite-17.02.sh | 78 +++++++++++++++++++++++++++++++++++ + 1 file changed, 78 insertions(+) + +diff --git a/test/units/testsuite-17.02.sh b/test/units/testsuite-17.02.sh +index 7abbce7747..ed3b39d074 100755 +--- a/test/units/testsuite-17.02.sh ++++ b/test/units/testsuite-17.02.sh +@@ -102,4 +102,82 @@ timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /s + # cleanup + ip link del hoge + ++teardown_netif_renaming_conflict() { ++ set +ex ++ ++ if [[ -n "$KILL_PID" ]]; then ++ kill "$KILL_PID" ++ fi ++ ++ rm -rf "$TMPDIR" ++ ++ rm -f /run/udev/rules.d/50-testsuite.rules ++ udevadm control --reload --timeout=30 ++ ++ ip link del hoge ++ ip link del foobar ++} ++ ++test_netif_renaming_conflict() { ++ local since found= ++ ++ trap teardown_netif_renaming_conflict RETURN ++ ++ cat >/run/udev/rules.d/50-testsuite.rules <"$TMPDIR"/monitor.txt & ++ KILL_PID="$!" ++ ++ # make sure that 'udevadm monitor' actually monitor uevents ++ sleep 1 ++ ++ since="$(date '+%H:%M:%S')" ++ ++ # add another interface which will conflict with an existing interface ++ ip link add foobar type dummy ++ ++ for _ in {1..40}; do ++ if ( ++ grep -q 'ACTION=add' "$TMPDIR"/monitor.txt ++ grep -q 'DEVPATH=/devices/virtual/net/foobar' "$TMPDIR"/monitor.txt ++ grep -q 'SUBSYSTEM=net' "$TMPDIR"/monitor.txt ++ grep -q 'INTERFACE=foobar' "$TMPDIR"/monitor.txt ++ grep -q 'ID_NET_DRIVER=dummy' "$TMPDIR"/monitor.txt ++ grep -q 'ID_NET_NAME=foobar' "$TMPDIR"/monitor.txt ++ # Even when network interface renaming is failed, SYSTEMD_ALIAS with the conflicting name will be broadcast. ++ grep -q 'SYSTEMD_ALIAS=/sys/subsystem/net/devices/hoge' "$TMPDIR"/monitor.txt ++ grep -q 'UDEV_WORKER_FAILED=1' "$TMPDIR"/monitor.txt ++ grep -q 'UDEV_WORKER_ERRNO=17' "$TMPDIR"/monitor.txt ++ grep -q 'UDEV_WORKER_ERRNO_NAME=EEXIST' "$TMPDIR"/monitor.txt ++ ); then ++ cat "$TMPDIR"/monitor.txt ++ found=1 ++ break ++ fi ++ sleep .5 ++ done ++ test -n "$found" ++ ++ timeout 30 bash -c "while ! journalctl _PID=1 _COMM=systemd --since $since | grep -q 'foobar: systemd-udevd failed to process the device, ignoring: File exists'; do sleep 1; done" ++ # check if the invalid SYSTEMD_ALIAS property for the interface foobar is ignored by PID1 ++ assert_eq "$(systemctl show --property=SysFSPath --value /sys/subsystem/net/devices/hoge)" "/sys/devices/virtual/net/hoge" ++} ++ ++test_netif_renaming_conflict ++ + exit 0 diff --git a/SOURCES/0398-test-modernize-test-netlink.c.patch b/SOURCES/0398-test-modernize-test-netlink.c.patch new file mode 100644 index 0000000..0592d24 --- /dev/null +++ b/SOURCES/0398-test-modernize-test-netlink.c.patch @@ -0,0 +1,623 @@ +From a3c14074e6cd91053ffafb0eb4b16054564e4239 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 20:33:35 +0900 +Subject: [PATCH] test: modernize test-netlink.c + +(cherry picked from commit eafff21da2978bfa4c5c4171a595abaeb1d170dc) + +Related: RHEL-5988 +--- + src/libsystemd/sd-netlink/test-netlink.c | 362 ++++++++--------------- + 1 file changed, 116 insertions(+), 246 deletions(-) + +diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c +index 2d93f9eba0..f740035639 100644 +--- a/src/libsystemd/sd-netlink/test-netlink.c ++++ b/src/libsystemd/sd-netlink/test-netlink.c +@@ -24,11 +24,12 @@ + #include "strv.h" + #include "tests.h" + +-static void test_message_link_bridge(sd_netlink *rtnl) { ++TEST(message_newlink_bridge) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; + uint32_t cost; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_NEWLINK, 1) >= 0); + assert_se(sd_rtnl_message_link_set_family(message, AF_BRIDGE) >= 0); +@@ -44,99 +45,81 @@ static void test_message_link_bridge(sd_netlink *rtnl) { + assert_se(sd_netlink_message_exit_container(message) >= 0); + } + +-static void test_link_configure(sd_netlink *rtnl, int ifindex) { ++TEST(message_getlink) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; +- uint32_t mtu_out; +- const char *name_out; +- struct ether_addr mac_out; +- +- log_debug("/* %s */", __func__); +- +- /* we'd really like to test NEWLINK, but let's not mess with the running kernel */ +- assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_GETLINK, ifindex) >= 0); +- +- assert_se(sd_netlink_call(rtnl, message, 0, &reply) == 1); +- +- assert_se(sd_netlink_message_read_string(reply, IFLA_IFNAME, &name_out) >= 0); +- assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, &mac_out) >= 0); +- assert_se(sd_netlink_message_read_u32(reply, IFLA_MTU, &mtu_out) >= 0); +-} +- +-static void test_link_get(sd_netlink *rtnl, int ifindex) { +- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; +- const char *str_data; ++ int ifindex; + uint8_t u8_data; ++ uint16_t u16_data; + uint32_t u32_data; ++ const char *str_data; + struct ether_addr eth_data; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); ++ ifindex = (int) if_nametoindex("lo"); + +- assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); +- assert_se(m); ++ /* we'd really like to test NEWLINK, but let's not mess with the running kernel */ ++ assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_GETLINK, ifindex) >= 0); ++ assert_se(sd_netlink_call(rtnl, message, 0, &reply) == 1); + +- assert_se(sd_netlink_call(rtnl, m, 0, &r) == 1); ++ /* u8 */ ++ assert_se(sd_netlink_message_read_u8(reply, IFLA_CARRIER, &u8_data) >= 0); ++ assert_se(sd_netlink_message_read_u8(reply, IFLA_OPERSTATE, &u8_data) >= 0); ++ assert_se(sd_netlink_message_read_u8(reply, IFLA_LINKMODE, &u8_data) >= 0); + +- assert_se(sd_netlink_message_read_string(r, IFLA_IFNAME, &str_data) == 0); ++ /* u16 */ ++ assert_se(sd_netlink_message_get_type(reply, &u16_data) >= 0); ++ assert_se(u16_data == RTM_NEWLINK); + +- assert_se(sd_netlink_message_read_u8(r, IFLA_CARRIER, &u8_data) == 0); +- assert_se(sd_netlink_message_read_u8(r, IFLA_OPERSTATE, &u8_data) == 0); +- assert_se(sd_netlink_message_read_u8(r, IFLA_LINKMODE, &u8_data) == 0); ++ /* u32 */ ++ assert_se(sd_netlink_message_read_u32(reply, IFLA_MTU, &u32_data) >= 0); ++ assert_se(sd_netlink_message_read_u32(reply, IFLA_GROUP, &u32_data) >= 0); ++ assert_se(sd_netlink_message_read_u32(reply, IFLA_TXQLEN, &u32_data) >= 0); ++ assert_se(sd_netlink_message_read_u32(reply, IFLA_NUM_TX_QUEUES, &u32_data) >= 0); ++ assert_se(sd_netlink_message_read_u32(reply, IFLA_NUM_RX_QUEUES, &u32_data) >= 0); + +- assert_se(sd_netlink_message_read_u32(r, IFLA_MTU, &u32_data) == 0); +- assert_se(sd_netlink_message_read_u32(r, IFLA_GROUP, &u32_data) == 0); +- assert_se(sd_netlink_message_read_u32(r, IFLA_TXQLEN, &u32_data) == 0); +- assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_TX_QUEUES, &u32_data) == 0); +- assert_se(sd_netlink_message_read_u32(r, IFLA_NUM_RX_QUEUES, &u32_data) == 0); ++ /* string */ ++ assert_se(sd_netlink_message_read_string(reply, IFLA_IFNAME, &str_data) >= 0); + +- assert_se(sd_netlink_message_read_ether_addr(r, IFLA_ADDRESS, ð_data) == 0); ++ /* ether_addr */ ++ assert_se(sd_netlink_message_read_ether_addr(reply, IFLA_ADDRESS, ð_data) >= 0); + } + +-static void test_address_get(sd_netlink *rtnl, int ifindex) { +- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; ++TEST(message_address) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; ++ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; ++ int ifindex; + struct in_addr in_data; + struct ifa_cacheinfo cache; + const char *label; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); ++ ifindex = (int) if_nametoindex("lo"); + +- assert_se(sd_rtnl_message_new_addr(rtnl, &m, RTM_GETADDR, ifindex, AF_INET) >= 0); +- assert_se(m); +- assert_se(sd_netlink_message_set_request_dump(m, true) >= 0); +- assert_se(sd_netlink_call(rtnl, m, -1, &r) == 1); ++ assert_se(sd_rtnl_message_new_addr(rtnl, &message, RTM_GETADDR, ifindex, AF_INET) >= 0); ++ assert_se(sd_netlink_message_set_request_dump(message, true) >= 0); ++ assert_se(sd_netlink_call(rtnl, message, 0, &reply) == 1); + +- assert_se(sd_netlink_message_read_in_addr(r, IFA_LOCAL, &in_data) == 0); +- assert_se(sd_netlink_message_read_in_addr(r, IFA_ADDRESS, &in_data) == 0); +- assert_se(sd_netlink_message_read_string(r, IFA_LABEL, &label) == 0); +- assert_se(sd_netlink_message_read_cache_info(r, IFA_CACHEINFO, &cache) == 0); ++ assert_se(sd_netlink_message_read_in_addr(reply, IFA_LOCAL, &in_data) >= 0); ++ assert_se(sd_netlink_message_read_in_addr(reply, IFA_ADDRESS, &in_data) >= 0); ++ assert_se(sd_netlink_message_read_string(reply, IFA_LABEL, &label) >= 0); ++ assert_se(sd_netlink_message_read_cache_info(reply, IFA_CACHEINFO, &cache) == 0); + } + +-static void test_route(sd_netlink *rtnl) { ++TEST(message_route) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL; + struct in_addr addr, addr_data; + uint32_t index = 2, u32_data; +- int r; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); + +- r = sd_rtnl_message_new_route(rtnl, &req, RTM_NEWROUTE, AF_INET, RTPROT_STATIC); +- if (r < 0) { +- log_error_errno(r, "Could not create RTM_NEWROUTE message: %m"); +- return; +- } ++ assert_se(sd_rtnl_message_new_route(rtnl, &req, RTM_NEWROUTE, AF_INET, RTPROT_STATIC) >= 0); + + addr.s_addr = htobe32(INADDR_LOOPBACK); + +- r = sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &addr); +- if (r < 0) { +- log_error_errno(r, "Could not append RTA_GATEWAY attribute: %m"); +- return; +- } +- +- r = sd_netlink_message_append_u32(req, RTA_OIF, index); +- if (r < 0) { +- log_error_errno(r, "Could not append RTA_OIF attribute: %m"); +- return; +- } ++ assert_se(sd_netlink_message_append_in_addr(req, RTA_GATEWAY, &addr) >= 0); ++ assert_se(sd_netlink_message_append_u32(req, RTA_OIF, index) >= 0); + + assert_se(sd_netlink_message_rewind(req, rtnl) >= 0); + +@@ -149,135 +132,94 @@ static void test_route(sd_netlink *rtnl) { + assert_se((req = sd_netlink_message_unref(req)) == NULL); + } + +-static void test_multiple(void) { +- sd_netlink *rtnl1, *rtnl2; +- +- log_debug("/* %s */", __func__); +- +- assert_se(sd_netlink_open(&rtnl1) >= 0); +- assert_se(sd_netlink_open(&rtnl2) >= 0); +- +- rtnl1 = sd_netlink_unref(rtnl1); +- rtnl2 = sd_netlink_unref(rtnl2); +-} +- + static int link_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) { +- char *ifname = userdata; + const char *data; + + assert_se(rtnl); + assert_se(m); +- assert_se(userdata); + +- log_info("%s: got link info about %s", __func__, ifname); +- free(ifname); ++ assert_se(streq_ptr(userdata, "foo")); + + assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &data) >= 0); + assert_se(streq(data, "lo")); + ++ log_info("%s: got link info about %s", __func__, data); + return 1; + } + +-static void test_event_loop(int ifindex) { ++TEST(netlink_event_loop) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; +- char *ifname; +- +- log_debug("/* %s */", __func__); +- +- ifname = strdup("lo2"); +- assert_se(ifname); ++ _cleanup_free_ char *userdata = NULL; ++ int ifindex; + + assert_se(sd_netlink_open(&rtnl) >= 0); +- assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); ++ ifindex = (int) if_nametoindex("lo"); + +- assert_se(sd_netlink_call_async(rtnl, NULL, m, link_handler, NULL, ifname, 0, NULL) >= 0); ++ assert_se(userdata = strdup("foo")); + + assert_se(sd_event_default(&event) >= 0); +- + assert_se(sd_netlink_attach_event(rtnl, event, 0) >= 0); + ++ assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); ++ assert_se(sd_netlink_call_async(rtnl, NULL, m, link_handler, NULL, userdata, 0, NULL) >= 0); ++ + assert_se(sd_event_run(event, 0) >= 0); + + assert_se(sd_netlink_detach_event(rtnl) >= 0); +- + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + } + + static void test_async_destroy(void *userdata) { + } + +-static void test_async(int ifindex) { ++TEST(netlink_call_async) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; +- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; ++ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL; + _cleanup_(sd_netlink_slot_unrefp) sd_netlink_slot *slot = NULL; ++ _cleanup_free_ char *userdata = NULL; + sd_netlink_destroy_t destroy_callback; + const char *description; +- char *ifname; +- +- log_debug("/* %s */", __func__); +- +- ifname = strdup("lo"); +- assert_se(ifname); ++ int ifindex; + + assert_se(sd_netlink_open(&rtnl) >= 0); ++ ifindex = (int) if_nametoindex("lo"); + +- assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); ++ assert_se(userdata = strdup("foo")); + +- assert_se(sd_netlink_call_async(rtnl, &slot, m, link_handler, test_async_destroy, ifname, 0, "hogehoge") >= 0); ++ assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); ++ assert_se(sd_netlink_call_async(rtnl, &slot, m, link_handler, test_async_destroy, userdata, 0, "hogehoge") >= 0); + + assert_se(sd_netlink_slot_get_netlink(slot) == rtnl); +- assert_se(sd_netlink_slot_get_userdata(slot) == ifname); +- assert_se(sd_netlink_slot_get_destroy_callback(slot, &destroy_callback) == 1); +- assert_se(destroy_callback == test_async_destroy); +- assert_se(sd_netlink_slot_get_floating(slot) == 0); +- assert_se(sd_netlink_slot_get_description(slot, &description) == 1); +- assert_se(streq(description, "hogehoge")); +- +- assert_se(sd_netlink_wait(rtnl, 0) >= 0); +- assert_se(sd_netlink_process(rtnl, &r) >= 0); +- +- assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +-} + +-static void test_slot_set(int ifindex) { +- _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; +- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; +- _cleanup_(sd_netlink_slot_unrefp) sd_netlink_slot *slot = NULL; +- sd_netlink_destroy_t destroy_callback; +- const char *description; +- char *ifname; +- +- log_debug("/* %s */", __func__); +- +- ifname = strdup("lo"); +- assert_se(ifname); +- +- assert_se(sd_netlink_open(&rtnl) >= 0); +- +- assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); +- +- assert_se(sd_netlink_call_async(rtnl, &slot, m, link_handler, NULL, NULL, 0, NULL) >= 0); ++ assert_se(sd_netlink_slot_get_userdata(slot) == userdata); ++ assert_se(sd_netlink_slot_set_userdata(slot, NULL) == userdata); ++ assert_se(sd_netlink_slot_get_userdata(slot) == NULL); ++ assert_se(sd_netlink_slot_set_userdata(slot, userdata) == NULL); ++ assert_se(sd_netlink_slot_get_userdata(slot) == userdata); + +- assert_se(sd_netlink_slot_get_netlink(slot) == rtnl); +- assert_se(!sd_netlink_slot_get_userdata(slot)); +- assert_se(!sd_netlink_slot_set_userdata(slot, ifname)); +- assert_se(sd_netlink_slot_get_userdata(slot) == ifname); +- assert_se(sd_netlink_slot_get_destroy_callback(slot, NULL) == 0); ++ assert_se(sd_netlink_slot_get_destroy_callback(slot, &destroy_callback) == 1); ++ assert_se(destroy_callback == test_async_destroy); ++ assert_se(sd_netlink_slot_set_destroy_callback(slot, NULL) >= 0); ++ assert_se(sd_netlink_slot_get_destroy_callback(slot, &destroy_callback) == 0); ++ assert_se(destroy_callback == NULL); + assert_se(sd_netlink_slot_set_destroy_callback(slot, test_async_destroy) >= 0); + assert_se(sd_netlink_slot_get_destroy_callback(slot, &destroy_callback) == 1); + assert_se(destroy_callback == test_async_destroy); ++ + assert_se(sd_netlink_slot_get_floating(slot) == 0); + assert_se(sd_netlink_slot_set_floating(slot, 1) == 1); + assert_se(sd_netlink_slot_get_floating(slot) == 1); +- assert_se(sd_netlink_slot_get_description(slot, NULL) == 0); +- assert_se(sd_netlink_slot_set_description(slot, "hogehoge") >= 0); ++ + assert_se(sd_netlink_slot_get_description(slot, &description) == 1); + assert_se(streq(description, "hogehoge")); ++ assert_se(sd_netlink_slot_set_description(slot, NULL) >= 0); ++ assert_se(sd_netlink_slot_get_description(slot, &description) == 0); ++ assert_se(description == NULL); + + assert_se(sd_netlink_wait(rtnl, 0) >= 0); +- assert_se(sd_netlink_process(rtnl, &r) >= 0); ++ assert_se(sd_netlink_process(rtnl, &reply) >= 0); + + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + } +@@ -322,23 +264,21 @@ static void test_async_object_destroy(void *userdata) { + test_async_object_unref(t); + } + +-static void test_async_destroy_callback(int ifindex) { ++TEST(async_destroy_callback) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; +- _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *r = NULL; ++ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL, *reply = NULL; + _cleanup_(test_async_object_unrefp) struct test_async_object *t = NULL; + _cleanup_(sd_netlink_slot_unrefp) sd_netlink_slot *slot = NULL; +- char *ifname; ++ int ifindex; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); ++ ifindex = (int) if_nametoindex("lo"); + + assert_se(t = new(struct test_async_object, 1)); +- assert_se(ifname = strdup("lo")); + *t = (struct test_async_object) { + .n_ref = 1, +- .ifname = ifname, + }; +- +- assert_se(sd_netlink_open(&rtnl) >= 0); ++ assert_se(t->ifname = strdup("lo")); + + /* destroy callback is called after processing message */ + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, ifindex) >= 0); +@@ -349,7 +289,7 @@ static void test_async_destroy_callback(int ifindex) { + assert_se(t->n_ref == 2); + + assert_se(sd_netlink_wait(rtnl, 0) >= 0); +- assert_se(sd_netlink_process(rtnl, &r) == 1); ++ assert_se(sd_netlink_process(rtnl, &reply) == 1); + assert_se(t->n_ref == 1); + + assert_se(!sd_netlink_message_unref(m)); +@@ -394,14 +334,13 @@ static int pipe_handler(sd_netlink *rtnl, sd_netlink_message *m, void *userdata) + return 1; + } + +-static void test_pipe(int ifindex) { ++TEST(pipe) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m1 = NULL, *m2 = NULL; +- int counter = 0; +- +- log_debug("/* %s */", __func__); ++ int ifindex, counter = 0; + + assert_se(sd_netlink_open(&rtnl) >= 0); ++ ifindex = (int) if_nametoindex("lo"); + + assert_se(sd_rtnl_message_new_link(rtnl, &m1, RTM_GETLINK, ifindex) >= 0); + assert_se(sd_rtnl_message_new_link(rtnl, &m2, RTM_GETLINK, ifindex) >= 0); +@@ -420,13 +359,14 @@ static void test_pipe(int ifindex) { + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + } + +-static void test_container(sd_netlink *rtnl) { ++TEST(message_container) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + uint16_t u16_data; + uint32_t u32_data; + const char *string_data; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINK, 0) >= 0); + +@@ -434,9 +374,7 @@ static void test_container(sd_netlink *rtnl) { + assert_se(sd_netlink_message_open_container_union(m, IFLA_INFO_DATA, "vlan") >= 0); + assert_se(sd_netlink_message_append_u16(m, IFLA_VLAN_ID, 100) >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); +- assert_se(sd_netlink_message_append_string(m, IFLA_INFO_KIND, "vlan") >= 0); + assert_se(sd_netlink_message_close_container(m) >= 0); +- assert_se(sd_netlink_message_close_container(m) == -EINVAL); + + assert_se(sd_netlink_message_rewind(m, rtnl) >= 0); + +@@ -453,16 +391,12 @@ static void test_container(sd_netlink *rtnl) { + assert_se(sd_netlink_message_exit_container(m) >= 0); + + assert_se(sd_netlink_message_read_u32(m, IFLA_LINKINFO, &u32_data) < 0); +- +- assert_se(sd_netlink_message_exit_container(m) == -EINVAL); + } + +-static void test_match(void) { ++TEST(sd_netlink_add_match) { + _cleanup_(sd_netlink_slot_unrefp) sd_netlink_slot *s1 = NULL, *s2 = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + +- log_debug("/* %s */", __func__); +- + assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_netlink_add_match(rtnl, &s1, RTM_NEWLINK, link_handler, NULL, NULL, NULL) >= 0); +@@ -475,17 +409,17 @@ static void test_match(void) { + assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); + } + +-static void test_get_addresses(sd_netlink *rtnl) { ++TEST(dump_addresses) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *req = NULL, *reply = NULL; +- sd_netlink_message *m; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_addr(rtnl, &req, RTM_GETADDR, 0, AF_UNSPEC) >= 0); + assert_se(sd_netlink_message_set_request_dump(req, true) >= 0); + assert_se(sd_netlink_call(rtnl, req, 0, &reply) >= 0); + +- for (m = reply; m; m = sd_netlink_message_next(m)) { ++ for (sd_netlink_message *m = reply; m; m = sd_netlink_message_next(m)) { + uint16_t type; + unsigned char scope, flags; + int family, ifindex; +@@ -505,21 +439,20 @@ static void test_get_addresses(sd_netlink *rtnl) { + } + } + +-static void test_message(sd_netlink *rtnl) { ++TEST(sd_netlink_message_get_errno) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(message_new_synthetic_error(rtnl, -ETIMEDOUT, 1, &m) >= 0); + assert_se(sd_netlink_message_get_errno(m) == -ETIMEDOUT); + } + +-static void test_array(void) { ++TEST(message_array) { + _cleanup_(sd_netlink_unrefp) sd_netlink *genl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + +- log_debug("/* %s */", __func__); +- + assert_se(sd_genl_socket_open(&genl) >= 0); + assert_se(sd_genl_message_new(genl, CTRL_GENL_NAME, CTRL_CMD_GETFAMILY, &m) >= 0); + +@@ -557,12 +490,13 @@ static void test_array(void) { + assert_se(sd_netlink_message_exit_container(m) >= 0); + } + +-static void test_strv(sd_netlink *rtnl) { ++TEST(message_strv) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; + _cleanup_strv_free_ char **names_in = NULL, **names_out; + const char *p; + +- log_debug("/* %s */", __func__); ++ assert_se(sd_netlink_open(&rtnl) >= 0); + + assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_NEWLINKPROP, 1) >= 0); + +@@ -624,7 +558,7 @@ static int genl_ctrl_match_callback(sd_netlink *genl, sd_netlink_message *m, voi + return 0; + } + +-static void test_genl(void) { ++TEST(genl) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_(sd_netlink_unrefp) sd_netlink *genl = NULL; + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *m = NULL; +@@ -632,8 +566,6 @@ static void test_genl(void) { + uint8_t cmd; + int r; + +- log_debug("/* %s */", __func__); +- + assert_se(sd_genl_socket_open(&genl) >= 0); + assert_se(sd_event_default(&event) >= 0); + assert_se(sd_netlink_attach_event(genl, event, 0) >= 0); +@@ -669,15 +601,17 @@ static void test_genl(void) { + } + } + +-static void test_rtnl_set_link_name(sd_netlink *rtnl, int ifindex) { ++TEST(rtnl_set_link_name) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; + _cleanup_strv_free_ char **alternative_names = NULL; +- int r; +- +- log_debug("/* %s */", __func__); ++ int ifindex, r; + + if (geteuid() != 0) + return (void) log_tests_skipped("not root"); + ++ assert_se(sd_netlink_open(&rtnl) >= 0); ++ ifindex = (int) if_nametoindex("lo"); ++ + /* Test that the new name (which is currently an alternative name) is + * restored as an alternative name on error. Create an error by using + * an invalid device name, namely one that exceeds IFNAMSIZ +@@ -693,68 +627,4 @@ static void test_rtnl_set_link_name(sd_netlink *rtnl, int ifindex) { + assert_se(rtnl_delete_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")) >= 0); + } + +-int main(void) { +- sd_netlink *rtnl; +- sd_netlink_message *m; +- sd_netlink_message *r; +- const char *string_data; +- int if_loopback; +- uint16_t type; +- +- test_setup_logging(LOG_DEBUG); +- +- test_match(); +- test_multiple(); +- +- assert_se(sd_netlink_open(&rtnl) >= 0); +- assert_se(rtnl); +- +- test_route(rtnl); +- test_message(rtnl); +- test_container(rtnl); +- test_array(); +- test_strv(rtnl); +- +- if_loopback = (int) if_nametoindex("lo"); +- assert_se(if_loopback > 0); +- +- test_async(if_loopback); +- test_slot_set(if_loopback); +- test_async_destroy_callback(if_loopback); +- test_pipe(if_loopback); +- test_event_loop(if_loopback); +- test_link_configure(rtnl, if_loopback); +- test_rtnl_set_link_name(rtnl, if_loopback); +- +- test_get_addresses(rtnl); +- test_message_link_bridge(rtnl); +- +- assert_se(sd_rtnl_message_new_link(rtnl, &m, RTM_GETLINK, if_loopback) >= 0); +- assert_se(m); +- +- assert_se(sd_netlink_message_get_type(m, &type) >= 0); +- assert_se(type == RTM_GETLINK); +- +- assert_se(sd_netlink_message_read_string(m, IFLA_IFNAME, &string_data) == -EPERM); +- +- assert_se(sd_netlink_call(rtnl, m, 0, &r) == 1); +- assert_se(sd_netlink_message_get_type(r, &type) >= 0); +- assert_se(type == RTM_NEWLINK); +- +- assert_se((r = sd_netlink_message_unref(r)) == NULL); +- +- assert_se(sd_netlink_call(rtnl, m, -1, &r) == -EPERM); +- assert_se((m = sd_netlink_message_unref(m)) == NULL); +- assert_se((r = sd_netlink_message_unref(r)) == NULL); +- +- test_link_get(rtnl, if_loopback); +- test_address_get(rtnl, if_loopback); +- +- assert_se((m = sd_netlink_message_unref(m)) == NULL); +- assert_se((r = sd_netlink_message_unref(r)) == NULL); +- assert_se((rtnl = sd_netlink_unref(rtnl)) == NULL); +- +- test_genl(); +- +- return EXIT_SUCCESS; +-} ++DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0399-test-netlink-use-dummy-interface-to-test-assigning-n.patch b/SOURCES/0399-test-netlink-use-dummy-interface-to-test-assigning-n.patch new file mode 100644 index 0000000..9df6628 --- /dev/null +++ b/SOURCES/0399-test-netlink-use-dummy-interface-to-test-assigning-n.patch @@ -0,0 +1,105 @@ +From 49fa9a23e444f864a4f06fb0c7b1f54ff0513206 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Mon, 9 Jan 2023 21:00:53 +0900 +Subject: [PATCH] test-netlink: use dummy interface to test assigning new + interface name + +Fixes #25981. + +(cherry picked from commit 5ccbe7fb197b01e0cf1f1ab523703274ef552555) + +Related: RHEL-5988 +--- + src/libsystemd/sd-netlink/test-netlink.c | 59 ++++++++++++++++++++++-- + 1 file changed, 55 insertions(+), 4 deletions(-) + +diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c +index f740035639..9ad8ecf320 100644 +--- a/src/libsystemd/sd-netlink/test-netlink.c ++++ b/src/libsystemd/sd-netlink/test-netlink.c +@@ -601,30 +601,81 @@ TEST(genl) { + } + } + ++static void remove_dummy_interfacep(int *ifindex) { ++ _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; ++ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; ++ ++ if (!ifindex || *ifindex <= 0) ++ return; ++ ++ assert_se(sd_netlink_open(&rtnl) >= 0); ++ ++ assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_DELLINK, *ifindex) >= 0); ++ assert_se(sd_netlink_call(rtnl, message, 0, NULL) == 1); ++} ++ + TEST(rtnl_set_link_name) { + _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; ++ _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL, *reply = NULL; ++ _cleanup_(remove_dummy_interfacep) int ifindex = 0; + _cleanup_strv_free_ char **alternative_names = NULL; +- int ifindex, r; ++ int r; + + if (geteuid() != 0) + return (void) log_tests_skipped("not root"); + + assert_se(sd_netlink_open(&rtnl) >= 0); +- ifindex = (int) if_nametoindex("lo"); ++ ++ assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_NEWLINK, 0) >= 0); ++ assert_se(sd_netlink_message_append_string(message, IFLA_IFNAME, "test-netlink") >= 0); ++ assert_se(sd_netlink_message_open_container(message, IFLA_LINKINFO) >= 0); ++ assert_se(sd_netlink_message_append_string(message, IFLA_INFO_KIND, "dummy") >= 0); ++ r = sd_netlink_call(rtnl, message, 0, &reply); ++ if (r == -EPERM) ++ return (void) log_tests_skipped("missing required capabilities"); ++ if (r == -EOPNOTSUPP) ++ return (void) log_tests_skipped("dummy network interface is not supported"); ++ assert_se(r >= 0); ++ ++ message = sd_netlink_message_unref(message); ++ reply = sd_netlink_message_unref(reply); ++ ++ assert_se(sd_rtnl_message_new_link(rtnl, &message, RTM_GETLINK, 0) >= 0); ++ assert_se(sd_netlink_message_append_string(message, IFLA_IFNAME, "test-netlink") >= 0); ++ assert_se(sd_netlink_call(rtnl, message, 0, &reply) == 1); ++ ++ assert_se(sd_rtnl_message_link_get_ifindex(reply, &ifindex) >= 0); ++ assert_se(ifindex > 0); + + /* Test that the new name (which is currently an alternative name) is + * restored as an alternative name on error. Create an error by using + * an invalid device name, namely one that exceeds IFNAMSIZ + * (alternative names can exceed IFNAMSIZ, but not regular names). */ +- r = rtnl_set_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")); ++ r = rtnl_set_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename", "test-shortname")); + if (r == -EPERM) + return (void) log_tests_skipped("missing required capabilities"); +- ++ if (r == -EOPNOTSUPP) ++ return (void) log_tests_skipped("alternative name is not supported"); + assert_se(r >= 0); ++ ++ assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); ++ assert_se(strv_contains(alternative_names, "testlongalternativename")); ++ assert_se(strv_contains(alternative_names, "test-shortname")); ++ + assert_se(rtnl_set_link_name(&rtnl, ifindex, "testlongalternativename") == -EINVAL); ++ assert_se(rtnl_set_link_name(&rtnl, ifindex, "test-shortname") >= 0); ++ ++ alternative_names = strv_free(alternative_names); + assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); + assert_se(strv_contains(alternative_names, "testlongalternativename")); ++ assert_se(!strv_contains(alternative_names, "test-shortname")); ++ + assert_se(rtnl_delete_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")) >= 0); ++ ++ alternative_names = strv_free(alternative_names); ++ assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); ++ assert_se(!strv_contains(alternative_names, "testlongalternativename")); ++ assert_se(!strv_contains(alternative_names, "test-shortname")); + } + + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0400-udev-use-SYNTHETIC_ERRNO-at-one-more-place.patch b/SOURCES/0400-udev-use-SYNTHETIC_ERRNO-at-one-more-place.patch new file mode 100644 index 0000000..88c6416 --- /dev/null +++ b/SOURCES/0400-udev-use-SYNTHETIC_ERRNO-at-one-more-place.patch @@ -0,0 +1,26 @@ +From 2deb458c5fd4ac318018b8464fa677dc4570ba61 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 16:34:31 +0900 +Subject: [PATCH] udev: use SYNTHETIC_ERRNO() at one more place + +(cherry picked from commit b3cfe5900108df81fbf547b297d51ac8c7359a9b) + +Related: RHEL-5988 +--- + src/udev/udevadm-test-builtin.c | 3 +-- + 1 file changed, 1 insertion(+), 2 deletions(-) + +diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c +index 81b633611e..5570eec789 100644 +--- a/src/udev/udevadm-test-builtin.c ++++ b/src/udev/udevadm-test-builtin.c +@@ -87,8 +87,7 @@ int builtin_main(int argc, char *argv[], void *userdata) { + + cmd = udev_builtin_lookup(arg_command); + if (cmd < 0) { +- log_error("Unknown command '%s'", arg_command); +- r = -EINVAL; ++ r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown command '%s'", arg_command); + goto finish; + } + diff --git a/SOURCES/0401-udev-make-udev_builtin_run-take-UdevEvent.patch b/SOURCES/0401-udev-make-udev_builtin_run-take-UdevEvent.patch new file mode 100644 index 0000000..26cb04d --- /dev/null +++ b/SOURCES/0401-udev-make-udev_builtin_run-take-UdevEvent.patch @@ -0,0 +1,351 @@ +From 09bc1c97130c4e646233ee3ea27ba03c226117d7 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 11:29:49 +0900 +Subject: [PATCH] udev: make udev_builtin_run() take UdevEvent* + +No functional change, preparation for later commits. + +(cherry picked from commit 5668f3a7cfccca704ea8e8bdc84ca7e17a5f101e) + +Related: RHEL-5988 +--- + src/udev/udev-builtin-blkid.c | 3 ++- + src/udev/udev-builtin-btrfs.c | 3 ++- + src/udev/udev-builtin-hwdb.c | 3 ++- + src/udev/udev-builtin-input_id.c | 6 ++---- + src/udev/udev-builtin-keyboard.c | 3 ++- + src/udev/udev-builtin-kmod.c | 5 ++--- + src/udev/udev-builtin-net_id.c | 3 ++- + src/udev/udev-builtin-net_setup_link.c | 7 ++++--- + src/udev/udev-builtin-path_id.c | 5 ++--- + src/udev/udev-builtin-uaccess.c | 3 ++- + src/udev/udev-builtin-usb_id.c | 5 ++--- + src/udev/udev-builtin.c | 7 ++++--- + src/udev/udev-builtin.h | 6 ++++-- + src/udev/udev-event.c | 2 +- + src/udev/udev-rules.c | 2 +- + src/udev/udevadm-test-builtin.c | 10 ++++++++-- + 16 files changed, 42 insertions(+), 31 deletions(-) + +diff --git a/src/udev/udev-builtin-blkid.c b/src/udev/udev-builtin-blkid.c +index 9f5646ffdd..63d1bd579d 100644 +--- a/src/udev/udev-builtin-blkid.c ++++ b/src/udev/udev-builtin-blkid.c +@@ -237,7 +237,8 @@ static int probe_superblocks(blkid_probe pr) { + return blkid_do_safeprobe(pr); + } + +-static int builtin_blkid(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_blkid(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + const char *devnode, *root_partition = NULL, *data, *name; + _cleanup_(blkid_free_probep) blkid_probe pr = NULL; + bool noraid = false, is_gpt = false; +diff --git a/src/udev/udev-builtin-btrfs.c b/src/udev/udev-builtin-btrfs.c +index 8cd627807f..b36eadb47a 100644 +--- a/src/udev/udev-builtin-btrfs.c ++++ b/src/udev/udev-builtin-btrfs.c +@@ -13,7 +13,8 @@ + #include "udev-builtin.h" + #include "util.h" + +-static int builtin_btrfs(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_btrfs(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + struct btrfs_ioctl_vol_args args = {}; + _cleanup_close_ int fd = -1; + int r; +diff --git a/src/udev/udev-builtin-hwdb.c b/src/udev/udev-builtin-hwdb.c +index 8d652e46fe..19e07e734f 100644 +--- a/src/udev/udev-builtin-hwdb.c ++++ b/src/udev/udev-builtin-hwdb.c +@@ -118,7 +118,7 @@ next: + return r; + } + +-static int builtin_hwdb(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_hwdb(UdevEvent *event, int argc, char *argv[], bool test) { + static const struct option options[] = { + { "filter", required_argument, NULL, 'f' }, + { "device", required_argument, NULL, 'd' }, +@@ -131,6 +131,7 @@ static int builtin_hwdb(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[ + const char *subsystem = NULL; + const char *prefix = NULL; + _cleanup_(sd_device_unrefp) sd_device *srcdev = NULL; ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + int r; + + if (!hwdb) +diff --git a/src/udev/udev-builtin-input_id.c b/src/udev/udev-builtin-input_id.c +index 0742120248..4322ce04b3 100644 +--- a/src/udev/udev-builtin-input_id.c ++++ b/src/udev/udev-builtin-input_id.c +@@ -356,8 +356,8 @@ static bool test_key(sd_device *dev, + return found; + } + +-static int builtin_input_id(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { +- sd_device *pdev; ++static int builtin_input_id(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *pdev, *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + unsigned long bitmask_ev[NBITS(EV_MAX)]; + unsigned long bitmask_abs[NBITS(ABS_MAX)]; + unsigned long bitmask_key[NBITS(KEY_MAX)]; +@@ -367,8 +367,6 @@ static int builtin_input_id(sd_device *dev, sd_netlink **rtnl, int argc, char *a + bool is_pointer; + bool is_key; + +- assert(dev); +- + /* walk up the parental chain until we find the real input device; the + * argument is very likely a subdevice of this, like eventN */ + for (pdev = dev; pdev; ) { +diff --git a/src/udev/udev-builtin-keyboard.c b/src/udev/udev-builtin-keyboard.c +index 6dd9eebd93..dac087a9e6 100644 +--- a/src/udev/udev-builtin-keyboard.c ++++ b/src/udev/udev-builtin-keyboard.c +@@ -159,7 +159,8 @@ static int set_trackpoint_sensitivity(sd_device *dev, const char *value) { + return 0; + } + +-static int builtin_keyboard(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_keyboard(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + unsigned release[1024]; + unsigned release_count = 0; + _cleanup_close_ int fd = -1; +diff --git a/src/udev/udev-builtin-kmod.c b/src/udev/udev-builtin-kmod.c +index eade042f35..3ab5c485f8 100644 +--- a/src/udev/udev-builtin-kmod.c ++++ b/src/udev/udev-builtin-kmod.c +@@ -22,11 +22,10 @@ _printf_(6,0) static void udev_kmod_log(void *data, int priority, const char *fi + log_internalv(priority, 0, file, line, fn, format, args); + } + +-static int builtin_kmod(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_kmod(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + int r; + +- assert(dev); +- + if (!ctx) + return 0; + +diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c +index c57568f8cb..cecf854b98 100644 +--- a/src/udev/udev-builtin-net_id.c ++++ b/src/udev/udev-builtin-net_id.c +@@ -1109,7 +1109,8 @@ static int get_link_info(sd_device *dev, LinkInfo *info) { + return 0; + } + +-static int builtin_net_id(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_net_id(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + const char *prefix; + NetNames names = {}; + LinkInfo info = { +diff --git a/src/udev/udev-builtin-net_setup_link.c b/src/udev/udev-builtin-net_setup_link.c +index ea7b1c5f60..4bf42cd492 100644 +--- a/src/udev/udev-builtin-net_setup_link.c ++++ b/src/udev/udev-builtin-net_setup_link.c +@@ -10,14 +10,15 @@ + + static LinkConfigContext *ctx = NULL; + +-static int builtin_net_setup_link(sd_device *dev, sd_netlink **rtnl, int argc, char **argv, bool test) { ++static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv, bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + _cleanup_(link_freep) Link *link = NULL; + int r; + + if (argc > 1) + return log_device_error_errno(dev, SYNTHETIC_ERRNO(EINVAL), "This program takes no arguments."); + +- r = link_new(ctx, rtnl, dev, &link); ++ r = link_new(ctx, &event->rtnl, dev, &link); + if (r == -ENODEV) { + log_device_debug_errno(dev, r, "Link vanished while getting information, ignoring."); + return 0; +@@ -38,7 +39,7 @@ static int builtin_net_setup_link(sd_device *dev, sd_netlink **rtnl, int argc, c + return log_device_error_errno(dev, r, "Failed to get link config: %m"); + } + +- r = link_apply_config(ctx, rtnl, link); ++ r = link_apply_config(ctx, &event->rtnl, link); + if (r == -ENODEV) + log_device_debug_errno(dev, r, "Link vanished while applying configuration, ignoring."); + else if (r < 0) +diff --git a/src/udev/udev-builtin-path_id.c b/src/udev/udev-builtin-path_id.c +index d58a3d5d60..6f4d7cbc5b 100644 +--- a/src/udev/udev-builtin-path_id.c ++++ b/src/udev/udev-builtin-path_id.c +@@ -581,15 +581,14 @@ static int find_real_nvme_parent(sd_device *dev, sd_device **ret) { + return 0; + } + +-static int builtin_path_id(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_path_id(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + _cleanup_(sd_device_unrefp) sd_device *dev_other_branch = NULL; + _cleanup_free_ char *path = NULL, *compat_path = NULL; + bool supported_transport = false, supported_parent = false; + const char *subsystem; + int r; + +- assert(dev); +- + /* walk up the chain of devices and compose path */ + for (sd_device *parent = dev; parent; ) { + const char *subsys, *sysname; +diff --git a/src/udev/udev-builtin-uaccess.c b/src/udev/udev-builtin-uaccess.c +index 6e73d99375..36c993cbb0 100644 +--- a/src/udev/udev-builtin-uaccess.c ++++ b/src/udev/udev-builtin-uaccess.c +@@ -16,7 +16,8 @@ + #include "log.h" + #include "udev-builtin.h" + +-static int builtin_uaccess(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_uaccess(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + const char *path = NULL, *seat; + bool changed_acl = false; + uid_t uid; +diff --git a/src/udev/udev-builtin-usb_id.c b/src/udev/udev-builtin-usb_id.c +index 847c2b8316..f3fc3cfdb3 100644 +--- a/src/udev/udev-builtin-usb_id.c ++++ b/src/udev/udev-builtin-usb_id.c +@@ -224,7 +224,8 @@ static int dev_if_packed_info(sd_device *dev, char *ifs_str, size_t len) { + * 6.) If the device supplies a serial number, this number + * is concatenated with the identification with an underscore '_'. + */ +-static int builtin_usb_id(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test) { ++static int builtin_usb_id(UdevEvent *event, int argc, char *argv[], bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + char vendor_str[64] = ""; + char vendor_str_enc[256]; + const char *vendor_id; +@@ -250,8 +251,6 @@ static int builtin_usb_id(sd_device *dev, sd_netlink **rtnl, int argc, char *arg + const char *syspath, *sysname, *devtype, *interface_syspath; + int r; + +- assert(dev); +- + r = sd_device_get_syspath(dev, &syspath); + if (r < 0) + return r; +diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c +index c98c6fa714..c84db8855c 100644 +--- a/src/udev/udev-builtin.c ++++ b/src/udev/udev-builtin.c +@@ -98,11 +98,12 @@ UdevBuiltinCommand udev_builtin_lookup(const char *command) { + return _UDEV_BUILTIN_INVALID; + } + +-int udev_builtin_run(sd_device *dev, sd_netlink **rtnl, UdevBuiltinCommand cmd, const char *command, bool test) { ++int udev_builtin_run(UdevEvent *event, UdevBuiltinCommand cmd, const char *command, bool test) { + _cleanup_strv_free_ char **argv = NULL; + int r; + +- assert(dev); ++ assert(event); ++ assert(event->dev); + assert(cmd >= 0 && cmd < _UDEV_BUILTIN_MAX); + assert(command); + +@@ -115,7 +116,7 @@ int udev_builtin_run(sd_device *dev, sd_netlink **rtnl, UdevBuiltinCommand cmd, + + /* we need '0' here to reset the internal state */ + optind = 0; +- return builtins[cmd]->cmd(dev, rtnl, strv_length(argv), argv, test); ++ return builtins[cmd]->cmd(event, strv_length(argv), argv, test); + } + + int udev_builtin_add_property(sd_device *dev, bool test, const char *key, const char *val) { +diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h +index bcfec03aae..919d51e798 100644 +--- a/src/udev/udev-builtin.h ++++ b/src/udev/udev-builtin.h +@@ -6,6 +6,8 @@ + #include "sd-device.h" + #include "sd-netlink.h" + ++#include "udev-event.h" ++ + typedef enum UdevBuiltinCommand { + #if HAVE_BLKID + UDEV_BUILTIN_BLKID, +@@ -30,7 +32,7 @@ typedef enum UdevBuiltinCommand { + + typedef struct UdevBuiltin { + const char *name; +- int (*cmd)(sd_device *dev, sd_netlink **rtnl, int argc, char *argv[], bool test); ++ int (*cmd)(UdevEvent *event, int argc, char *argv[], bool test); + const char *help; + int (*init)(void); + void (*exit)(void); +@@ -74,7 +76,7 @@ void udev_builtin_exit(void); + UdevBuiltinCommand udev_builtin_lookup(const char *command); + const char *udev_builtin_name(UdevBuiltinCommand cmd); + bool udev_builtin_run_once(UdevBuiltinCommand cmd); +-int udev_builtin_run(sd_device *dev, sd_netlink **rtnl, UdevBuiltinCommand cmd, const char *command, bool test); ++int udev_builtin_run(UdevEvent *event, UdevBuiltinCommand cmd, const char *command, bool test); + void udev_builtin_list(void); + bool udev_builtin_should_reload(void); + int udev_builtin_add_property(sd_device *dev, bool test, const char *key, const char *val); +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index fab454ae37..cf90d6f205 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -1200,7 +1200,7 @@ void udev_event_execute_run(UdevEvent *event, usec_t timeout_usec, int timeout_s + + if (builtin_cmd != _UDEV_BUILTIN_INVALID) { + log_device_debug(event->dev, "Running built-in command \"%s\"", command); +- r = udev_builtin_run(event->dev, &event->rtnl, builtin_cmd, command, false); ++ r = udev_builtin_run(event, builtin_cmd, command, false); + if (r < 0) + log_device_debug_errno(event->dev, r, "Failed to run built-in command \"%s\", ignoring: %m", command); + } else { +diff --git a/src/udev/udev-rules.c b/src/udev/udev-rules.c +index a8473041c3..9336ce1cd3 100644 +--- a/src/udev/udev-rules.c ++++ b/src/udev/udev-rules.c +@@ -1972,7 +1972,7 @@ static int udev_rule_apply_token_to_event( + + log_rule_debug(dev, rules, "Importing properties from results of builtin command '%s'", buf); + +- r = udev_builtin_run(dev, &event->rtnl, cmd, buf, false); ++ r = udev_builtin_run(event, cmd, buf, false); + if (r < 0) { + /* remember failure */ + log_rule_debug_errno(dev, rules, r, "Failed to run builtin '%s': %m", buf); +diff --git a/src/udev/udevadm-test-builtin.c b/src/udev/udevadm-test-builtin.c +index 5570eec789..5d1fafbd03 100644 +--- a/src/udev/udevadm-test-builtin.c ++++ b/src/udev/udevadm-test-builtin.c +@@ -72,7 +72,7 @@ static int parse_argv(int argc, char *argv[]) { + } + + int builtin_main(int argc, char *argv[], void *userdata) { +- _cleanup_(sd_netlink_unrefp) sd_netlink *rtnl = NULL; ++ _cleanup_(udev_event_freep) UdevEvent *event = NULL; + _cleanup_(sd_device_unrefp) sd_device *dev = NULL; + UdevBuiltinCommand cmd; + int r; +@@ -97,7 +97,13 @@ int builtin_main(int argc, char *argv[], void *userdata) { + goto finish; + } + +- r = udev_builtin_run(dev, &rtnl, cmd, arg_command, true); ++ event = udev_event_new(dev, 0, NULL, LOG_DEBUG); ++ if (!event) { ++ r = log_oom(); ++ goto finish; ++ } ++ ++ r = udev_builtin_run(event, cmd, arg_command, true); + if (r < 0) + log_debug_errno(r, "Builtin command '%s' fails: %m", arg_command); + diff --git a/SOURCES/0402-udev-net-verify-ID_NET_XYZ-before-trying-to-assign-i.patch b/SOURCES/0402-udev-net-verify-ID_NET_XYZ-before-trying-to-assign-i.patch new file mode 100644 index 0000000..4663dab --- /dev/null +++ b/SOURCES/0402-udev-net-verify-ID_NET_XYZ-before-trying-to-assign-i.patch @@ -0,0 +1,26 @@ +From 87a2e6ccd7989f2b271f557c6303a4eb412a03cb Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 11:46:11 +0900 +Subject: [PATCH] udev/net: verify ID_NET_XYZ before trying to assign it as an + alternative name + +(cherry picked from commit e65c6c1baa8ea905f7e5bad3b8486d509775ec6a) + +Related: RHEL-5988 +--- + src/udev/net/link-config.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c +index 5d28526527..4fcf373f8e 100644 +--- a/src/udev/net/link-config.c ++++ b/src/udev/net/link-config.c +@@ -834,7 +834,7 @@ static int link_apply_alternative_names(Link *link, sd_netlink **rtnl) { + default: + assert_not_reached(); + } +- if (!isempty(n)) { ++ if (ifname_valid_full(n, IFNAME_VALID_ALTERNATIVE)) { + r = strv_extend(&altnames, n); + if (r < 0) + return log_oom(); diff --git a/SOURCES/0403-udev-net-generate-new-network-interface-name-only-on.patch b/SOURCES/0403-udev-net-generate-new-network-interface-name-only-on.patch new file mode 100644 index 0000000..441a852 --- /dev/null +++ b/SOURCES/0403-udev-net-generate-new-network-interface-name-only-on.patch @@ -0,0 +1,29 @@ +From dd4c492721ed4be1b4c26cd937566dac2e97ba19 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 13:05:09 +0900 +Subject: [PATCH] udev/net: generate new network interface name only on add + uevent + +On other uevents, the name will be anyway ignored in rename_netif() in +udev-event.c. + +(cherry picked from commit cd941e6596adba6bb139c387ae596f26b35701f7) + +Related: RHEL-5988 +--- + src/udev/net/link-config.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c +index 4fcf373f8e..c9789bcb7c 100644 +--- a/src/udev/net/link-config.c ++++ b/src/udev/net/link-config.c +@@ -722,7 +722,7 @@ static int link_generate_new_name(Link *link) { + config = link->config; + device = link->device; + +- if (link->action == SD_DEVICE_MOVE) { ++ if (link->action != SD_DEVICE_ADD) { + log_link_debug(link, "Skipping to apply Name= and NamePolicy= on '%s' uevent.", + device_action_to_string(link->action)); + goto no_rename; diff --git a/SOURCES/0404-sd-netlink-make-rtnl_set_link_name-optionally-append.patch b/SOURCES/0404-sd-netlink-make-rtnl_set_link_name-optionally-append.patch new file mode 100644 index 0000000..934feb2 --- /dev/null +++ b/SOURCES/0404-sd-netlink-make-rtnl_set_link_name-optionally-append.patch @@ -0,0 +1,191 @@ +From ea5725b1e621c2733c28f818c3a58615a385337e Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 13:29:37 +0900 +Subject: [PATCH] sd-netlink: make rtnl_set_link_name() optionally append + alternative names + +(cherry picked from commit 81824455008070253c62bf5c27187028ba8e7e99) + +Related: RHEL-5988 +--- + src/libsystemd/sd-netlink/netlink-util.c | 89 ++++++++++++++++++------ + src/libsystemd/sd-netlink/netlink-util.h | 5 +- + src/libsystemd/sd-netlink/test-netlink.c | 6 +- + src/udev/udev-event.c | 2 +- + 4 files changed, 78 insertions(+), 24 deletions(-) + +diff --git a/src/libsystemd/sd-netlink/netlink-util.c b/src/libsystemd/sd-netlink/netlink-util.c +index cfcf2578d6..5438737b42 100644 +--- a/src/libsystemd/sd-netlink/netlink-util.c ++++ b/src/libsystemd/sd-netlink/netlink-util.c +@@ -11,44 +11,93 @@ + #include "process-util.h" + #include "strv.h" + +-int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { ++static int set_link_name(sd_netlink **rtnl, int ifindex, const char *name) { + _cleanup_(sd_netlink_message_unrefp) sd_netlink_message *message = NULL; +- _cleanup_strv_free_ char **alternative_names = NULL; +- bool altname_deleted = false; + int r; + + assert(rtnl); + assert(ifindex > 0); + assert(name); + +- if (!ifname_valid(name)) ++ /* Assign the requested name. */ ++ r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); ++ if (r < 0) ++ return r; ++ ++ r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); ++ if (r < 0) ++ return r; ++ ++ return sd_netlink_call(*rtnl, message, 0, NULL); ++} ++ ++int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name, char* const *alternative_names) { ++ _cleanup_strv_free_ char **original_altnames = NULL, **new_altnames = NULL; ++ bool altname_deleted = false; ++ int r; ++ ++ assert(rtnl); ++ assert(ifindex > 0); ++ ++ if (isempty(name) && strv_isempty(alternative_names)) ++ return 0; ++ ++ if (name && !ifname_valid(name)) + return -EINVAL; + +- r = rtnl_get_link_alternative_names(rtnl, ifindex, &alternative_names); ++ /* If the requested name is already assigned as an alternative name, then first drop it. */ ++ r = rtnl_get_link_alternative_names(rtnl, ifindex, &original_altnames); + if (r < 0) + log_debug_errno(r, "Failed to get alternative names on network interface %i, ignoring: %m", + ifindex); + +- if (strv_contains(alternative_names, name)) { +- r = rtnl_delete_link_alternative_names(rtnl, ifindex, STRV_MAKE(name)); +- if (r < 0) +- return log_debug_errno(r, "Failed to remove '%s' from alternative names on network interface %i: %m", +- name, ifindex); ++ if (name) { ++ if (strv_contains(original_altnames, name)) { ++ r = rtnl_delete_link_alternative_names(rtnl, ifindex, STRV_MAKE(name)); ++ if (r < 0) ++ return log_debug_errno(r, "Failed to remove '%s' from alternative names on network interface %i: %m", ++ name, ifindex); ++ ++ altname_deleted = true; ++ } + +- altname_deleted = true; ++ r = set_link_name(rtnl, ifindex, name); ++ if (r < 0) ++ goto fail; + } + +- r = sd_rtnl_message_new_link(*rtnl, &message, RTM_SETLINK, ifindex); +- if (r < 0) +- goto fail; ++ /* Filter out already assigned names from requested alternative names. Also, dedup the request. */ ++ STRV_FOREACH(a, alternative_names) { ++ if (streq_ptr(name, *a)) ++ continue; + +- r = sd_netlink_message_append_string(message, IFLA_IFNAME, name); +- if (r < 0) +- goto fail; ++ if (strv_contains(original_altnames, *a)) ++ continue; + +- r = sd_netlink_call(*rtnl, message, 0, NULL); +- if (r < 0) +- goto fail; ++ if (strv_contains(new_altnames, *a)) ++ continue; ++ ++ if (!ifname_valid_full(*a, IFNAME_VALID_ALTERNATIVE)) ++ continue; ++ ++ r = strv_extend(&new_altnames, *a); ++ if (r < 0) ++ return r; ++ } ++ ++ strv_sort(new_altnames); ++ ++ /* Finally, assign alternative names. */ ++ r = rtnl_set_link_alternative_names(rtnl, ifindex, new_altnames); ++ if (r == -EEXIST) /* Already assigned to another interface? */ ++ STRV_FOREACH(a, new_altnames) { ++ r = rtnl_set_link_alternative_names(rtnl, ifindex, STRV_MAKE(*a)); ++ if (r < 0) ++ log_debug_errno(r, "Failed to assign '%s' as an alternative name on network interface %i, ignoring: %m", ++ *a, ifindex); ++ } ++ else if (r < 0) ++ log_debug_errno(r, "Failed to assign alternative names on network interface %i, ignoring: %m", ifindex); + + return 0; + +diff --git a/src/libsystemd/sd-netlink/netlink-util.h b/src/libsystemd/sd-netlink/netlink-util.h +index d14392a018..888e28642d 100644 +--- a/src/libsystemd/sd-netlink/netlink-util.h ++++ b/src/libsystemd/sd-netlink/netlink-util.h +@@ -29,7 +29,10 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(MultipathRoute*, multipath_route_free); + + int multipath_route_dup(const MultipathRoute *m, MultipathRoute **ret); + +-int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name); ++int rtnl_set_link_name(sd_netlink **rtnl, int ifindex, const char *name, char* const* alternative_names); ++static inline int rtnl_append_link_alternative_names(sd_netlink **rtnl, int ifindex, char* const *alternative_names) { ++ return rtnl_set_link_name(rtnl, ifindex, NULL, alternative_names); ++} + int rtnl_set_link_properties( + sd_netlink **rtnl, + int ifindex, +diff --git a/src/libsystemd/sd-netlink/test-netlink.c b/src/libsystemd/sd-netlink/test-netlink.c +index 9ad8ecf320..43124b99ae 100644 +--- a/src/libsystemd/sd-netlink/test-netlink.c ++++ b/src/libsystemd/sd-netlink/test-netlink.c +@@ -662,12 +662,13 @@ TEST(rtnl_set_link_name) { + assert_se(strv_contains(alternative_names, "testlongalternativename")); + assert_se(strv_contains(alternative_names, "test-shortname")); + +- assert_se(rtnl_set_link_name(&rtnl, ifindex, "testlongalternativename") == -EINVAL); +- assert_se(rtnl_set_link_name(&rtnl, ifindex, "test-shortname") >= 0); ++ assert_se(rtnl_set_link_name(&rtnl, ifindex, "testlongalternativename", NULL) == -EINVAL); ++ assert_se(rtnl_set_link_name(&rtnl, ifindex, "test-shortname", STRV_MAKE("testlongalternativename", "test-shortname", "test-additional-name")) >= 0); + + alternative_names = strv_free(alternative_names); + assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); + assert_se(strv_contains(alternative_names, "testlongalternativename")); ++ assert_se(strv_contains(alternative_names, "test-additional-name")); + assert_se(!strv_contains(alternative_names, "test-shortname")); + + assert_se(rtnl_delete_link_alternative_names(&rtnl, ifindex, STRV_MAKE("testlongalternativename")) >= 0); +@@ -675,6 +676,7 @@ TEST(rtnl_set_link_name) { + alternative_names = strv_free(alternative_names); + assert_se(rtnl_get_link_alternative_names(&rtnl, ifindex, &alternative_names) >= 0); + assert_se(!strv_contains(alternative_names, "testlongalternativename")); ++ assert_se(strv_contains(alternative_names, "test-additional-name")); + assert_se(!strv_contains(alternative_names, "test-shortname")); + } + +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index cf90d6f205..2662806d61 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -980,7 +980,7 @@ static int rename_netif(UdevEvent *event) { + goto revert; + } + +- r = rtnl_set_link_name(&event->rtnl, ifindex, event->name); ++ r = rtnl_set_link_name(&event->rtnl, ifindex, event->name, NULL); + if (r < 0) { + if (r == -EBUSY) { + log_device_info(dev, "Network interface '%s' is already up, cannot rename to '%s'.", diff --git a/SOURCES/0405-udev-net-assign-alternative-names-only-on-add-uevent.patch b/SOURCES/0405-udev-net-assign-alternative-names-only-on-add-uevent.patch new file mode 100644 index 0000000..a7946d0 --- /dev/null +++ b/SOURCES/0405-udev-net-assign-alternative-names-only-on-add-uevent.patch @@ -0,0 +1,233 @@ +From 11f76dbf187708c3eda4a4daeb058f544ea28af5 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 12:28:23 +0900 +Subject: [PATCH] udev/net: assign alternative names only on add uevent + +Previously, we first assign alternative names to a network interface, +then later change its main name if requested. So, we could not assign +the name that currently assigned as the main name of an interface as an +alternative name. So, we retry to assign the previous main name as an +alternative name on later move uevent. + +However, that causes some confusing situation. E.g. if a .link file has +``` +Name=foo +AlternativeNames=foo baz +``` +then even if the interface is renamed by a user e.g. by invoking 'ip link' +command manually, the interface can be still referenced as 'foo', as the +name is now assigned as an alternative name. + +This makes the order of name assignment inverse: the main name is first +changed, and then the requested alternative names are assigned. And +udevd do not assign alternative names on move uevent. + +Replaces #27506. + +(cherry picked from commit 9094ae52caca0c19ff6abdbd95d17d8e401ea3b1) + +Resolves: RHEL-5988 +--- + src/udev/net/link-config.c | 37 ++++++++------------- + src/udev/net/link-config.h | 1 + + src/udev/udev-builtin-net_setup_link.c | 2 ++ + src/udev/udev-event.c | 45 ++++++++++++++++++++++---- + src/udev/udev-event.h | 1 + + 5 files changed, 55 insertions(+), 31 deletions(-) + +diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c +index c9789bcb7c..2d8c902fd3 100644 +--- a/src/udev/net/link-config.c ++++ b/src/udev/net/link-config.c +@@ -363,6 +363,7 @@ Link *link_free(Link *link) { + sd_device_unref(link->device); + free(link->kind); + free(link->driver); ++ strv_free(link->altnames); + return mfree(link); + } + +@@ -791,19 +792,22 @@ no_rename: + return 0; + } + +-static int link_apply_alternative_names(Link *link, sd_netlink **rtnl) { +- _cleanup_strv_free_ char **altnames = NULL, **current_altnames = NULL; ++static int link_generate_alternative_names(Link *link) { ++ _cleanup_strv_free_ char **altnames = NULL; + LinkConfig *config; + sd_device *device; + int r; + + assert(link); +- assert(link->config); +- assert(link->device); +- assert(rtnl); ++ config = ASSERT_PTR(link->config); ++ device = ASSERT_PTR(link->device); ++ assert(!link->altnames); + +- config = link->config; +- device = link->device; ++ if (link->action != SD_DEVICE_ADD) { ++ log_link_debug(link, "Skipping to apply AlternativeNames= and AlternativeNamesPolicy= on '%s' uevent.", ++ device_action_to_string(link->action)); ++ return 0; ++ } + + if (config->alternative_names) { + altnames = strv_copy(config->alternative_names); +@@ -841,22 +845,7 @@ static int link_apply_alternative_names(Link *link, sd_netlink **rtnl) { + } + } + +- strv_remove(altnames, link->ifname); +- +- r = rtnl_get_link_alternative_names(rtnl, link->ifindex, ¤t_altnames); +- if (r < 0) +- log_link_debug_errno(link, r, "Failed to get alternative names, ignoring: %m"); +- +- STRV_FOREACH(p, current_altnames) +- strv_remove(altnames, *p); +- +- strv_uniq(altnames); +- strv_sort(altnames); +- r = rtnl_set_link_alternative_names(rtnl, link->ifindex, altnames); +- if (r < 0) +- log_link_full_errno(link, r == -EOPNOTSUPP ? LOG_DEBUG : LOG_WARNING, r, +- "Could not set AlternativeName= or apply AlternativeNamesPolicy=, ignoring: %m"); +- ++ link->altnames = TAKE_PTR(altnames); + return 0; + } + +@@ -958,7 +947,7 @@ int link_apply_config(LinkConfigContext *ctx, sd_netlink **rtnl, Link *link) { + if (r < 0) + return r; + +- r = link_apply_alternative_names(link, rtnl); ++ r = link_generate_alternative_names(link); + if (r < 0) + return r; + +diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h +index ea9f560f45..874a391543 100644 +--- a/src/udev/net/link-config.h ++++ b/src/udev/net/link-config.h +@@ -27,6 +27,7 @@ typedef struct Link { + int ifindex; + const char *ifname; + const char *new_name; ++ char **altnames; + + LinkConfig *config; + sd_device *device; +diff --git a/src/udev/udev-builtin-net_setup_link.c b/src/udev/udev-builtin-net_setup_link.c +index 4bf42cd492..e964bf7bf4 100644 +--- a/src/udev/udev-builtin-net_setup_link.c ++++ b/src/udev/udev-builtin-net_setup_link.c +@@ -49,6 +49,8 @@ static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv, bool + if (link->new_name) + udev_builtin_add_property(dev, test, "ID_NET_NAME", link->new_name); + ++ event->altnames = TAKE_PTR(link->altnames); ++ + return 0; + } + +diff --git a/src/udev/udev-event.c b/src/udev/udev-event.c +index 2662806d61..3315d34eff 100644 +--- a/src/udev/udev-event.c ++++ b/src/udev/udev-event.c +@@ -88,6 +88,7 @@ UdevEvent *udev_event_free(UdevEvent *event) { + ordered_hashmap_free_free_free(event->seclabel_list); + free(event->program_result); + free(event->name); ++ strv_free(event->altnames); + + return mfree(event); + } +@@ -918,9 +919,6 @@ static int rename_netif(UdevEvent *event) { + + dev = ASSERT_PTR(event->dev); + +- if (!device_for_action(dev, SD_DEVICE_ADD)) +- return 0; /* Rename the interface only when it is added. */ +- + r = sd_device_get_ifindex(dev, &ifindex); + if (r == -ENOENT) + return 0; /* Device is not a network interface. */ +@@ -980,7 +978,7 @@ static int rename_netif(UdevEvent *event) { + goto revert; + } + +- r = rtnl_set_link_name(&event->rtnl, ifindex, event->name, NULL); ++ r = rtnl_set_link_name(&event->rtnl, ifindex, event->name, event->altnames); + if (r < 0) { + if (r == -EBUSY) { + log_device_info(dev, "Network interface '%s' is already up, cannot rename to '%s'.", +@@ -1011,6 +1009,35 @@ revert: + return r; + } + ++static int assign_altnames(UdevEvent *event) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); ++ int ifindex, r; ++ const char *s; ++ ++ if (strv_isempty(event->altnames)) ++ return 0; ++ ++ r = sd_device_get_ifindex(dev, &ifindex); ++ if (r == -ENOENT) ++ return 0; /* Device is not a network interface. */ ++ if (r < 0) ++ return log_device_warning_errno(dev, r, "Failed to get ifindex: %m"); ++ ++ r = sd_device_get_sysname(dev, &s); ++ if (r < 0) ++ return log_device_warning_errno(dev, r, "Failed to get sysname: %m"); ++ ++ /* Filter out the current interface name. */ ++ strv_remove(event->altnames, s); ++ ++ r = rtnl_append_link_alternative_names(&event->rtnl, ifindex, event->altnames); ++ if (r < 0) ++ log_device_full_errno(dev, r == -EOPNOTSUPP ? LOG_DEBUG : LOG_WARNING, r, ++ "Could not set AlternativeName= or apply AlternativeNamesPolicy=, ignoring: %m"); ++ ++ return 0; ++} ++ + static int update_devnode(UdevEvent *event) { + sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); + int r; +@@ -1163,9 +1190,13 @@ int udev_event_execute_rules( + + DEVICE_TRACE_POINT(rules_finished, dev); + +- r = rename_netif(event); +- if (r < 0) +- return r; ++ if (action == SD_DEVICE_ADD) { ++ r = rename_netif(event); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ (void) assign_altnames(event); ++ } + + r = update_devnode(event); + if (r < 0) +diff --git a/src/udev/udev-event.h b/src/udev/udev-event.h +index 74d065ce23..13bd85dcf7 100644 +--- a/src/udev/udev-event.h ++++ b/src/udev/udev-event.h +@@ -23,6 +23,7 @@ typedef struct UdevEvent { + sd_device *dev_parent; + sd_device *dev_db_clone; + char *name; ++ char **altnames; + char *program_result; + mode_t mode; + uid_t uid; diff --git a/SOURCES/0406-test-add-tests-for-renaming-network-interface.patch b/SOURCES/0406-test-add-tests-for-renaming-network-interface.patch new file mode 100644 index 0000000..071858d --- /dev/null +++ b/SOURCES/0406-test-add-tests-for-renaming-network-interface.patch @@ -0,0 +1,105 @@ +From dec129d192c7815bdfbb71c88a1d7cdc3092f11f Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 16 May 2023 16:28:54 +0900 +Subject: [PATCH] test: add tests for renaming network interface + +(cherry picked from commit 40b6b448bda5294582e685091123952fbcd43502) + +Related: RHEL-5988 +--- + test/units/testsuite-17.12.sh | 86 +++++++++++++++++++++++++++++++++++ + 1 file changed, 86 insertions(+) + create mode 100755 test/units/testsuite-17.12.sh + +diff --git a/test/units/testsuite-17.12.sh b/test/units/testsuite-17.12.sh +new file mode 100755 +index 0000000000..df74d356ee +--- /dev/null ++++ b/test/units/testsuite-17.12.sh +@@ -0,0 +1,86 @@ ++#!/usr/bin/env bash ++# SPDX-License-Identifier: LGPL-2.1-or-later ++set -ex ++set -o pipefail ++ ++# shellcheck source=test/units/assert.sh ++. "$(dirname "$0")"/assert.sh ++ ++create_link_file() { ++ name=${1?} ++ ++ mkdir -p /run/systemd/network/ ++ cat >/run/systemd/network/10-test.link < +Date: Wed, 22 Nov 2023 13:07:56 +0100 +Subject: [PATCH] Backport ukify from upstream + +This is based off v255-rc2. + +Resolves: RHEL-13199 +--- + man/rules/meson.build | 1 + + man/uki.conf.example | 14 + + man/ukify.xml | 685 +++++++ + meson.build | 25 + + meson_options.txt | 2 + + src/ukify/test/example.signing.crt.base64 | 23 + + src/ukify/test/example.signing.key.base64 | 30 + + .../test/example.tpm2-pcr-private.pem.base64 | 30 + + .../test/example.tpm2-pcr-private2.pem.base64 | 30 + + .../test/example.tpm2-pcr-public.pem.base64 | 8 + + .../test/example.tpm2-pcr-public2.pem.base64 | 8 + + src/ukify/test/meson.build | 20 + + src/ukify/test/test_ukify.py | 848 +++++++++ + src/ukify/ukify.py | 1660 +++++++++++++++++ + 14 files changed, 3384 insertions(+) + create mode 100644 man/uki.conf.example + create mode 100644 man/ukify.xml + create mode 100644 src/ukify/test/example.signing.crt.base64 + create mode 100644 src/ukify/test/example.signing.key.base64 + create mode 100644 src/ukify/test/example.tpm2-pcr-private.pem.base64 + create mode 100644 src/ukify/test/example.tpm2-pcr-private2.pem.base64 + create mode 100644 src/ukify/test/example.tpm2-pcr-public.pem.base64 + create mode 100644 src/ukify/test/example.tpm2-pcr-public2.pem.base64 + create mode 100644 src/ukify/test/meson.build + create mode 100755 src/ukify/test/test_ukify.py + create mode 100755 src/ukify/ukify.py + +diff --git a/man/rules/meson.build b/man/rules/meson.build +index bb7799036d..c7045840f2 100644 +--- a/man/rules/meson.build ++++ b/man/rules/meson.build +@@ -1189,6 +1189,7 @@ manpages = [ + ''], + ['udev_new', '3', ['udev_ref', 'udev_unref'], ''], + ['udevadm', '8', [], ''], ++ ['ukify', '1', [], 'ENABLE_UKIFY'], + ['user@.service', + '5', + ['systemd-user-runtime-dir', 'user-runtime-dir@.service'], +diff --git a/man/uki.conf.example b/man/uki.conf.example +new file mode 100644 +index 0000000000..84a9f77b8d +--- /dev/null ++++ b/man/uki.conf.example +@@ -0,0 +1,14 @@ ++[UKI] ++SecureBootPrivateKey=/etc/kernel/secure-boot.key.pem ++SecureBootCertificate=/etc/kernel/secure-boot.cert.pem ++ ++[PCRSignature:initrd] ++Phases=enter-initrd ++PCRPrivateKey=/etc/kernel/pcr-initrd.key.pem ++PCRPublicKey=/etc/kernel/pcr-initrd.pub.pem ++ ++[PCRSignature:system] ++Phases=enter-initrd:leave-initrd enter-initrd:leave-initrd:sysinit ++ enter-initrd:leave-initrd:sysinit:ready ++PCRPrivateKey=/etc/kernel/pcr-system.key.pem ++PCRPublicKey=/etc/kernel/pcr-system.pub.pem +diff --git a/man/ukify.xml b/man/ukify.xml +new file mode 100644 +index 0000000000..eff74ca150 +--- /dev/null ++++ b/man/ukify.xml +@@ -0,0 +1,685 @@ ++ ++ ++ ++ ++ ++ ++ ++ ukify ++ systemd ++ ++ ++ ++ ukify ++ 1 ++ ++ ++ ++ ukify ++ Combine components into a signed Unified Kernel Image for UEFI systems ++ ++ ++ ++ ++ ukify ++ OPTIONS ++ build ++ ++ ++ ++ ukify ++ OPTIONS ++ genkey ++ ++ ++ ++ ukify ++ OPTIONS ++ inspect ++ FILE ++ ++ ++ ++ ++ Description ++ ++ ukify is a tool whose primary purpose is to combine components (usually a ++ kernel, an initrd, and a UEFI boot stub) to create a ++ Unified Kernel Image (UKI) ++ — a PE binary that can be executed by the firmware to start the embedded linux kernel. ++ See systemd-stub7 ++ for details about the stub. ++ ++ ++ ++ Commands ++ ++ The following commands are understood: ++ ++ ++ <command>build</command> ++ ++ This command creates a Unified Kernel Image. The two primary options that should be specified for ++ the build verb are Linux=/, and ++ Initrd=/. Initrd= accepts multiple ++ whitespace-separated paths and can be specified multiple times. ++ ++ Additional sections will be inserted into the UKI, either automatically or only if a specific ++ option is provided. See the discussions of ++ Cmdline=/, ++ OSRelease=/, ++ DeviceTree=/, ++ Splash=/, ++ PCRPKey=/, ++ Uname=/, ++ SBAT=/, ++ and ++ below. ++ ++ ukify can also be used to assemble a PE binary that is not executable but ++ contains auxiliary data, for example additional kernel command line entries. ++ ++ If PCR signing keys are provided via the ++ PCRPrivateKey=/ and ++ PCRPublicKey=/ options, PCR values that will be seen ++ after booting with the given kernel, initrd, and other sections, will be calculated, signed, and embedded ++ in the UKI. ++ systemd-measure1 is ++ used to perform this calculation and signing. ++ ++ The calculation of PCR values is done for specific boot phase paths. Those can be specified with ++ the Phases=/ option. If not specified, the default provided ++ by systemd-measure is used. It is also possible to specify the ++ PCRPrivateKey=/, ++ PCRPublicKey=/, and ++ Phases=/ arguments more than once. Signatures will then be ++ performed with each of the specified keys. On the command line, when both and ++ are used, they must be specified the same number of times, and then ++ the n-th boot phase path set will be signed by the n-th key. This can be used to build different trust ++ policies for different phases of the boot. In the config file, PCRPrivateKey=, ++ PCRPublicKey=, and Phases= are grouped into separate sections, ++ describing separate boot phases. ++ ++ If a SecureBoot signing key is provided via the ++ SecureBootPrivateKey=/ option, the resulting ++ PE binary will be signed as a whole, allowing the resulting UKI to be trusted by SecureBoot. Also see the ++ discussion of automatic enrollment in ++ systemd-boot7. ++ ++ ++ If the stub and/or the kernel contain .sbat sections they will be merged in ++ the UKI so that revocation updates affecting either are considered when the UKI is loaded by Shim. For ++ more information on SBAT see ++ Shim documentation. ++ ++ ++ ++ ++ <command>genkey</command> ++ ++ This command creates the keys for PCR signing and the key and certificate used for SecureBoot ++ signing. The same configuration options that determine what keys and in which paths will be needed for ++ signing when build is used, here determine which keys will be created. See the ++ discussion of PCRPrivateKey=/, ++ PCRPublicKey=/, and ++ SecureBootPrivateKey=/ below. ++ ++ The output files must not exist. ++ ++ ++ ++ <command>inspect</command> ++ ++ Display information about the sections in a given binary or binaries. ++ If is given, all sections are shown. ++ Otherwise, if option is specified at least once, only those sections are shown. ++ Otherwise, well-known sections that are typically included in an UKI are shown. ++ For each section, its name, size, and sha256-digest is printed. ++ For text sections, the contents are printed. ++ ++ Also see the description of / and ++ . ++ ++ ++ ++ ++ Configuration settings ++ ++ Settings can appear in configuration files (the syntax with SomeSetting=value) and on the command line (the syntax ++ with ). For some command ++ line parameters, a single-letter shortcut is also allowed. In the configuration files, the setting must ++ be in the appropriate section, so the descriptions are grouped by section below. When the same setting ++ appears in the configuration file and on the command line, generally the command line setting has higher ++ priority and overwrites the config file setting completely. If some setting behaves differently, this is ++ described below. ++ ++ If no config file is provided via the option , ++ ukify will try to look for a default configuration file in the following paths in this ++ order: /run/systemd/ukify.conf, /etc/systemd/ukify.conf, ++ /usr/local/lib/systemd/ukify.conf, and /usr/lib/systemd/ukify.conf, ++ and then load the first one found. ukify will proceed normally if no configuration file ++ is specified and no default one is found. ++ ++ The LINUX and INITRD positional arguments, or ++ the equivalent Linux= and Initrd= settings, are optional. If more ++ than one initrd is specified, they will all be combined into a single PE section. This is useful to, for ++ example, prepend microcode before the actual initrd. ++ ++ The following options and settings are understood: ++ ++ ++ Command line-only options ++ ++ ++ ++ ++ ++ Load configuration from the given config file. In general, settings specified in ++ the config file have lower precedence than the settings specified via options. In cases where the ++ command line option does not fully override the config file setting are explicitly mentioned in the ++ descriptions of individual options. ++ ++ ++ ++ ++ ++ ++ ++ ++ Enable or disable a call to ++ systemd-measure1 ++ to print pre-calculated PCR values. Defaults to false. ++ ++ ++ ++ ++ ++ ++ ++ ++ For all verbs except inspect, the first syntax is used. ++ Specify an arbitrary additional section NAME. ++ The argument may be a literal string, or @ followed by a path name. ++ This option may be specified more than once. Any sections specified in this fashion will be ++ inserted (in order) before the .linux section which is always last. ++ ++ For the inspect verb, the second syntax is used. ++ The section NAME will be inspected (if found). ++ If the second argument is text, the contents will be printed. ++ If the third argument is given, the contents will be saved to file PATH. ++ ++ ++ Note that the name is used as-is, and if the section name should start with a dot, it must be ++ included in NAME. ++ ++ ++ ++ ++ ++ ++ ++ Specify one or more directories with helper tools. ukify will ++ look for helper tools in those directories first, and if not found, try to load them from ++ $PATH in the usual fashion. ++ ++ ++ ++ ++ ++ ++ ++ The output filename. If not specified, the name of the ++ LINUX argument, with the suffix .unsigned.efi or ++ .signed.efi will be used, depending on whether signing for SecureBoot was ++ performed. ++ ++ ++ ++ ++ ++ ++ ++ Print a summary of loaded config and exit. This is useful to check how the options ++ from the configuration file and the command line are combined. ++ ++ ++ ++ ++ ++ ++ ++ Print all sections (with inspect verb). ++ ++ ++ ++ ++ ++ ++ ++ Generate JSON output (with inspect verb). ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ [UKI] section ++ ++ ++ ++ Linux=LINUX ++ ++ ++ A path to the kernel binary. ++ ++ ++ ++ ++ ++ Initrd=INITRD... ++ ++ ++ Zero or more initrd paths. In the configuration file, items are separated by ++ whitespace. The initrds are combined in the order of specification, with the initrds specified in ++ the config file first. ++ ++ ++ ++ ++ ++ Cmdline=TEXT|@PATH ++ ++ ++ The kernel command line (the .cmdline section). The argument may ++ be a literal string, or @ followed by a path name. If not specified, no command ++ line will be embedded. ++ ++ ++ ++ ++ ++ OSRelease=TEXT|@PATH ++ ++ ++ The os-release description (the .osrel section). The argument ++ may be a literal string, or @ followed by a path name. If not specified, the ++ os-release5 file ++ will be picked up from the host system. ++ ++ ++ ++ ++ ++ DeviceTree=PATH ++ ++ ++ The devicetree description (the .dtb section). The argument is a ++ path to a compiled binary DeviceTree file. If not specified, the section will not be present. ++ ++ ++ ++ ++ ++ ++ Splash=PATH ++ ++ ++ A picture to display during boot (the .splash section). The ++ argument is a path to a BMP file. If not specified, the section will not be present. ++ ++ ++ ++ ++ ++ ++ PCRPKey=PATH ++ ++ ++ A path to a public key to embed in the .pcrpkey section. If not ++ specified, and there's exactly one ++ PCRPublicKey=/ argument, that key will be used. ++ Otherwise, the section will not be present. ++ ++ ++ ++ ++ ++ Uname=VERSION ++ ++ ++ Specify the kernel version (as in uname -r, the ++ .uname section). If not specified, an attempt will be made to extract the ++ version string from the kernel image. It is recommended to pass this explicitly if known, because ++ the extraction is based on heuristics and not very reliable. If not specified and extraction fails, ++ the section will not be present. ++ ++ ++ ++ ++ ++ PCRBanks=PATH ++ ++ ++ A comma or space-separated list of PCR banks to sign a policy for. If not present, ++ all known banks will be used (sha1, sha256, ++ sha384, sha512), which will fail if not supported by the ++ system. ++ ++ ++ ++ ++ ++ SecureBootSigningTool=SIGNER ++ ++ ++ Whether to use sbsign or pesign. ++ Depending on this choice, different parameters are required in order to sign an image. ++ Defaults to sbsign. ++ ++ ++ ++ ++ ++ SecureBootPrivateKey=SB_KEY ++ ++ ++ A path to a private key to use for signing of the resulting binary. If the ++ SigningEngine=/ option is used, this may also be ++ an engine-specific designation. This option is required by ++ SecureBootSigningTool=sbsign/. ++ ++ ++ ++ ++ ++ SecureBootCertificate=SB_CERT ++ ++ ++ A path to a certificate to use for signing of the resulting binary. If the ++ SigningEngine=/ option is used, this may also ++ be an engine-specific designation. This option is required by ++ SecureBootSigningTool=sbsign/. ++ ++ ++ ++ ++ ++ SecureBootCertificateDir=SB_PATH ++ ++ ++ A path to a nss certificate database directory to use for signing of the resulting binary. ++ Takes effect when SecureBootSigningTool=pesign/ is used. ++ Defaults to /etc/pki/pesign. ++ ++ ++ ++ ++ ++ SecureBootCertificateName=SB_CERTNAME ++ ++ ++ The name of the nss certificate database entry to use for signing of the resulting binary. ++ This option is required by SecureBootSigningTool=pesign/. ++ ++ ++ ++ ++ ++ SecureBootCertificateValidity=DAYS ++ ++ ++ Period of validity (in days) for a certificate created by ++ genkey. Defaults to 3650, i.e. 10 years. ++ ++ ++ ++ ++ ++ SigningEngine=ENGINE ++ ++ ++ An "engine" for signing of the resulting binary. This option is currently passed ++ verbatim to the option of ++ sbsign1. ++ ++ ++ ++ ++ ++ ++ SignKernel=BOOL ++ ++ ++ ++ Override the detection of whether to sign the Linux binary itself before it is ++ embedded in the combined image. If not specified, it will be signed if a SecureBoot signing key is ++ provided via the ++ SecureBootPrivateKey=/ option and the ++ binary has not already been signed. If ++ SignKernel=/ is true, and the binary has already ++ been signed, the signature will be appended anyway. ++ ++ ++ ++ ++ ++ SBAT=TEXT|@PATH ++ ++ ++ SBAT metadata associated with the UKI or addon. SBAT policies are useful to revoke ++ whole groups of UKIs or addons with a single, static policy update that does not take space in ++ DBX/MOKX. If not specified manually, a default metadata entry consisting of ++ uki,1,UKI,uki,1,https://www.freedesktop.org/software/systemd/man/systemd-stub.html ++ will be used, to ensure it is always possible to revoke UKIs and addons. For more information on ++ SBAT see Shim documentation. ++ ++ ++ ++ ++ ++ ++ ++ ++ [PCRSignature:<replaceable>NAME</replaceable>] section ++ ++ In the config file, those options are grouped by section. On the command line, they ++ must be specified in the same order. The sections specified in both sources are combined. ++ ++ ++ ++ ++ PCRPrivateKey=PATH ++ ++ ++ A private key to use for signing PCR policies. On the command line, this option may ++ be specified more than once, in which case multiple signatures will be made. ++ ++ ++ ++ ++ ++ PCRPublicKey=PATH ++ ++ ++ A public key to use for signing PCR policies. ++ ++ On the command line, this option may be specified more than once, similarly to the ++ option. If not present, the public keys will be extracted from ++ the private keys. On the command line, if present, this option must be specified the same number of ++ times as the option. ++ ++ ++ ++ ++ ++ Phases=LIST ++ ++ ++ A comma or space-separated list of colon-separated phase paths to sign a policy ++ for. Each set of boot phase paths will be signed with the corresponding private key. If not ++ present, the default of ++ systemd-measure1 ++ will be used. ++ ++ On the command line, when this argument is present, it must appear the same number of times as ++ the option. ++ ++ ++ ++ ++ ++ ++ ++ ++ Examples ++ ++ ++ Minimal invocation ++ ++ $ ukify build \ ++ --linux=/lib/modules/6.0.9-300.fc37.x86_64/vmlinuz \ ++ --initrd=/some/path/initramfs-6.0.9-300.fc37.x86_64.img \ ++ --cmdline='quiet rw' ++ ++ ++ This creates an unsigned UKI ./vmlinuz.unsigned.efi. ++ ++ ++ ++ All the bells and whistles ++ ++ $ ukify build \ ++ --linux=/lib/modules/6.0.9-300.fc37.x86_64/vmlinuz \ ++ --initrd=early_cpio \ ++ --initrd=/some/path/initramfs-6.0.9-300.fc37.x86_64.img \ ++ --sbat='sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md ++ uki.author.myimage,1,UKI for System,uki.author.myimage,1,https://www.freedesktop.org/software/systemd/man/systemd-stub.html' \ ++ --pcr-private-key=pcr-private-initrd-key.pem \ ++ --pcr-public-key=pcr-public-initrd-key.pem \ ++ --phases='enter-initrd' \ ++ --pcr-private-key=pcr-private-system-key.pem \ ++ --pcr-public-key=pcr-public-system-key.pem \ ++ --phases='enter-initrd:leave-initrd enter-initrd:leave-initrd:sysinit \ ++ enter-initrd:leave-initrd:sysinit:ready' \ ++ --pcr-banks=sha384,sha512 \ ++ --secureboot-private-key=sb.key \ ++ --secureboot-certificate=sb.cert \ ++ --sign-kernel \ ++ --cmdline='quiet rw rhgb' ++ ++ ++ This creates a signed UKI ./vmlinuz.signed.efi. ++ The initrd section contains two concatenated parts, early_cpio ++ and initramfs-6.0.9-300.fc37.x86_64.img. ++ The policy embedded in the .pcrsig section will be signed for the initrd (the ++ enter-initrd phase) with the key ++ pcr-private-initrd-key.pem, and for the main system (phases ++ leave-initrd, sysinit, ready) with the ++ key pcr-private-system-key.pem. The Linux binary and the resulting ++ combined image will be signed with the SecureBoot key sb.key. ++ ++ ++ ++ All the bells and whistles, via a config file ++ ++ This is the same as the previous example, but this time the configuration is stored in a ++ file: ++ ++ $ cat ukify.conf ++[UKI] ++Initrd=early_cpio ++Cmdline=quiet rw rhgb ++ ++SecureBootPrivateKey=sb.key ++SecureBootCertificate=sb.cert ++SignKernel=yes ++PCRBanks=sha384,sha512 ++ ++[PCRSignature:initrd] ++PCRPrivateKey=pcr-private-initrd-key.pem ++PCRPublicKey=pcr-public-initrd-key.pem ++Phases=enter-initrd ++ ++[PCRSignature:system] ++PCRPrivateKey=pcr-private-system-key.pem ++PCRPublicKey=pcr-public-system-key.pem ++Phases=enter-initrd:leave-initrd ++ enter-initrd:leave-initrd:sysinit ++ enter-initrd:leave-initrd:sysinit:ready ++ ++$ ukify -c ukify.conf build \ ++ --linux=/lib/modules/6.0.9-300.fc37.x86_64/vmlinuz \ ++ --initrd=/some/path/initramfs-6.0.9-300.fc37.x86_64.img ++ ++ ++ One "initrd" (early_cpio) is specified in the config file, and ++ the other initrd (initramfs-6.0.9-300.fc37.x86_64.img) is specified ++ on the command line. This may be useful for example when the first initrd contains microcode for the CPU ++ and does not need to be updated when the kernel version changes, unlike the actual initrd. ++ ++ ++ ++ Kernel command line auxiliary PE ++ ++ ukify build \ ++ --secureboot-private-key=sb.key \ ++ --secureboot-certificate=sb.cert \ ++ --cmdline='debug' \ ++ --sbat='sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md ++ uki.addon.author,1,UKI Addon for System,uki.addon.author,1,https://www.freedesktop.org/software/systemd/man/systemd-stub.html' ++ --output=debug.cmdline ++ ++ ++ This creates a signed PE binary that contains the additional kernel command line parameter ++ debug with SBAT metadata referring to the owner of the addon. ++ ++ ++ ++ Decide signing policy and create certificate and keys ++ ++ First, let's create an config file that specifies what signatures shall be made: ++ ++ # cat >/etc/kernel/uki.conf <<EOF ++EOF ++ ++ Next, we can generate the certificate and keys: ++ # ukify genkey --config=/etc/kernel/uki.conf ++Writing SecureBoot private key to /etc/kernel/secure-boot.key.pem ++Writing SecureBoot certificate to /etc/kernel/secure-boot.cert.pem ++Writing private key for PCR signing to /etc/kernel/pcr-initrd.key.pem ++Writing public key for PCR signing to /etc/kernel/pcr-initrd.pub.pem ++Writing private key for PCR signing to /etc/kernel/pcr-system.key.pem ++Writing public key for PCR signing to /etc/kernel/pcr-system.pub.pem ++ ++ ++ (Both operations need to be done as root to allow write access ++ to /etc/kernel/.) ++ ++ Subsequent invocations using the config file ++ (ukify build --config=/etc/kernel/uki.conf) ++ will use this certificate and key files. Note that the ++ kernel-install8 ++ plugin 60-ukify.install uses /etc/kernel/uki.conf ++ by default, so after this file has been created, installations of kernels that create a UKI on the ++ local machine using kernel-install will perform signing using this config. ++ ++ ++ ++ ++ See Also ++ ++ systemd1, ++ systemd-stub7, ++ systemd-boot7, ++ systemd-measure1, ++ systemd-pcrphase.service8 ++ ++ ++ ++ +diff --git a/meson.build b/meson.build +index 5b2e7ca172..b874c2f9b4 100644 +--- a/meson.build ++++ b/meson.build +@@ -1916,6 +1916,18 @@ subdir('src/boot/efi') + + ############################################################ + ++pymod = import('python') ++python = pymod.find_installation('python3', required : true, modules : ['jinja2']) ++python_39 = python.language_version().version_compare('>=3.9') ++ ++want_ukify = get_option('ukify') ++if want_ukify and not python_39 ++ error('ukify requires Python >= 3.9') ++endif ++conf.set10('ENABLE_UKIFY', want_ukify) ++ ++############################################################ ++ + generate_gperfs = find_program('tools/generate-gperfs.py') + make_autosuspend_rules_py = find_program('tools/make-autosuspend-rules.py') + make_directive_index_py = find_program('tools/make-directive-index.py') +@@ -2164,6 +2176,7 @@ subdir('src/test') + subdir('src/fuzz') + subdir('rules.d') + subdir('test') ++subdir('src/ukify/test') # needs to be last for test_env variable + + ############################################################ + +@@ -3841,6 +3854,18 @@ exe = custom_target( + install_dir : bindir) + public_programs += exe + ++ukify = custom_target( ++ 'ukify', ++ input : 'src/ukify/ukify.py', ++ output : 'ukify', ++ command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], ++ install : want_ukify, ++ install_mode : 'rwxr-xr-x', ++ install_dir : rootlibexecdir) ++if want_ukify ++ public_programs += ukify ++endif ++ + if want_tests != 'false' and want_kernel_install + test('test-kernel-install', + test_kernel_install_sh, +diff --git a/meson_options.txt b/meson_options.txt +index 814f340840..903e6681a4 100644 +--- a/meson_options.txt ++++ b/meson_options.txt +@@ -499,6 +499,8 @@ option('llvm-fuzz', type : 'boolean', value : false, + description : 'build against LLVM libFuzzer') + option('kernel-install', type: 'boolean', value: true, + description : 'install kernel-install and associated files') ++option('ukify', type : 'boolean', value : false, ++ description : 'install ukify') + option('analyze', type: 'boolean', value: true, + description : 'install systemd-analyze') + +diff --git a/src/ukify/test/example.signing.crt.base64 b/src/ukify/test/example.signing.crt.base64 +new file mode 100644 +index 0000000000..694d13b5a6 +--- /dev/null ++++ b/src/ukify/test/example.signing.crt.base64 +@@ -0,0 +1,23 @@ ++LS0tLS1CRUdJTiBDRVJUSUZJQ0FURS0tLS0tCk1JSURsVENDQW4yZ0F3SUJBZ0lVTzlqUWhhblhj ++b3ViOERzdXlMMWdZbksrR1lvd0RRWUpLb1pJaHZjTkFRRUwKQlFBd1dURUxNQWtHQTFVRUJoTUNX ++Rmd4RlRBVEJnTlZCQWNNREVSbFptRjFiSFFnUTJsMGVURWNNQm9HQTFVRQpDZ3dUUkdWbVlYVnNk ++Q0JEYjIxd1lXNTVJRXgwWkRFVk1CTUdBMVVFQXd3TWEyVjVJSE5wWjI1cGJtbG5NQ0FYCkRUSXlN ++VEF5T1RFM01qY3dNVm9ZRHpNd01qSXdNekF4TVRjeU56QXhXakJaTVFzd0NRWURWUVFHRXdKWVdE ++RVYKTUJNR0ExVUVCd3dNUkdWbVlYVnNkQ0JEYVhSNU1Sd3dHZ1lEVlFRS0RCTkVaV1poZFd4MElF ++TnZiWEJoYm5rZwpUSFJrTVJVd0V3WURWUVFEREF4clpYa2djMmxuYm1sdWFXY3dnZ0VpTUEwR0NT ++cUdTSWIzRFFFQkFRVUFBNElCCkR3QXdnZ0VLQW9JQkFRREtVeHR4Y0d1aGYvdUp1SXRjWEhvdW0v ++RE9RL1RJM3BzUWlaR0ZWRkJzbHBicU5wZDUKa2JDaUFMNmgrY1FYaGRjUmlOT1dBR0wyMFZ1T2Rv ++VTZrYzlkdklGQnFzKzc2NHhvWGY1UGd2SlhvQUxSUGxDZAp4YVdPQzFsOFFIRHpxZ09SdnREMWNI ++WFoveTkvZ1YxVU1GK1FlYm12aUhRN0U4eGw1T2h5MG1TQVZYRDhBTitsCjdpMUR6N0NuTzhrMVph ++alhqYXlpNWV1WEV0TnFSZXNuVktRRElTQ0t2STFueUxySWxHRU1GZmFuUmRLQWthZ3MKalJnTmVh ++T3N3aklHNjV6UzFVdjJTZXcxVFpIaFhtUmd5TzRVT0JySHZlSml2T2hObzU3UlRKd0M2K2lGY0FG ++aApSSnorVmM2QUlSSkI1ZWtJUmdCN3VDNEI5ZmwydXdZKytMODNBZ01CQUFHalV6QlJNQjBHQTFV ++ZERnUVdCQlFqCllIMnpzVFlPQU51MkcweXk1QkxlOHBvbWZUQWZCZ05WSFNNRUdEQVdnQlFqWUgy ++enNUWU9BTnUyRzB5eTVCTGUKOHBvbWZUQVBCZ05WSFJNQkFmOEVCVEFEQVFIL01BMEdDU3FHU0li ++M0RRRUJDd1VBQTRJQkFRQ2dxcmFXaE51dQptUmZPUjVxcURVcC83RkpIL1N6Zk1vaDBHL2lWRkhv ++OUpSS0tqMUZ2Q0VZc1NmeThYTmdaUDI5eS81Z0h4cmcrCjhwZWx6bWJLczdhUTRPK01TcmIzTm11 ++V1IzT0M0alBoNENrM09ZbDlhQy9iYlJqSWFvMDJ6K29XQWNZZS9xYTEKK2ZsemZWVEUwMHJ5V1RM ++K0FJdDFEZEVqaG01WXNtYlgvbWtacUV1TjBtSVhhRXhSVE9walczUWRNeVRQaURTdApvanQvQWMv ++R2RUWDd0QkhPTk44Z3djaC91V293aVNORERMUm1wM2VScnlOZ3RPKzBISUd5Qm16ZWNsM0VlVEo2 ++CnJzOGRWUFhqR1Z4dlZDb2tqQllrOWdxbkNGZEJCMGx4VXVNZldWdVkyRUgwSjI3aGh4SXNFc3ls ++VTNIR1EyK2MKN1JicVY4VTNSRzA4Ci0tLS0tRU5EIENFUlRJRklDQVRFLS0tLS0K +diff --git a/src/ukify/test/example.signing.key.base64 b/src/ukify/test/example.signing.key.base64 +new file mode 100644 +index 0000000000..88baedbcb6 +--- /dev/null ++++ b/src/ukify/test/example.signing.key.base64 +@@ -0,0 +1,30 @@ ++LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZB ++QVNDQktnd2dnU2tBZ0VBQW9JQkFRREtVeHR4Y0d1aGYvdUoKdUl0Y1hIb3VtL0RPUS9USTNwc1Fp ++WkdGVkZCc2xwYnFOcGQ1a2JDaUFMNmgrY1FYaGRjUmlOT1dBR0wyMFZ1Twpkb1U2a2M5ZHZJRkJx ++cys3NjR4b1hmNVBndkpYb0FMUlBsQ2R4YVdPQzFsOFFIRHpxZ09SdnREMWNIWFoveTkvCmdWMVVN ++RitRZWJtdmlIUTdFOHhsNU9oeTBtU0FWWEQ4QU4rbDdpMUR6N0NuTzhrMVphalhqYXlpNWV1WEV0 ++TnEKUmVzblZLUURJU0NLdkkxbnlMcklsR0VNRmZhblJkS0FrYWdzalJnTmVhT3N3aklHNjV6UzFV ++djJTZXcxVFpIaApYbVJneU80VU9Cckh2ZUppdk9oTm81N1JUSndDNitpRmNBRmhSSnorVmM2QUlS ++SkI1ZWtJUmdCN3VDNEI5ZmwyCnV3WSsrTDgzQWdNQkFBRUNnZ0VBQkhZQ28rU3JxdHJzaStQU3hz ++MlBNQm5tSEZZcFBvaVIrTEpmMEFYRTVEQUoKMGM0MFZzemNqU1hoRGljNHFLQWQxdGdpZWlzMkEy ++VW9WS0xPV3pVOTBqNUd4MURoMWEzaTRhWTQ1ajNuNUFDMgpMekRsakNVQWVucExsYzdCN3MxdjJM ++WFJXNmdJSVM5Y043NTlkVTYvdktyQ2FsbGkzcTZZRWlNUzhQMHNsQnZFCkZtdEc1elFsOVJjV0gr ++cHBqdzlIMTJSZ3BldUVJVEQ2cE0vd2xwcXZHRlUwcmZjM0NjMHhzaWdNTnh1Z1FJNGgKbnpjWDVs ++OEs0SHdvbmhOTG9TYkh6OU5BK3p3QkpuUlZVSWFaaEVjSThtaEVPWHRaRkpYc01aRnhjS2l3SHFS ++dApqUUVHOHJRa3lPLytXMmR5Z2czV1lNYXE1OWpUWVdIOUsrQmFyeEMzRVFLQmdRRFBNSFMycjgz ++ZUpRTTlreXpkCndDdnlmWGhQVlVtbVJnOGwyWng0aC9tci9mNUdDeW5SdzRzT2JuZGVQd29tZ1Iz ++cFBleFFGWlFFSExoZ1RGY3UKVk5uYXcrTzBFL1VnL01pRGswZDNXU0hVZXZPZnM1cEM2b3hYNjNT ++VENwNkVLT2VEZlpVMW9OeHRsZ0YyRVhjcgpmVlZpSzFKRGk3N2dtaENLcFNGcjBLK3gyUUtCZ1FE ++NS9VUC9hNU52clExdUhnKzR0SzJZSFhSK1lUOFREZG00Ck8xZmh5TU5lOHRYSkd5UUJjTktVTWg2 ++M2VyR1MwWlRWdGdkNHlGS3RuOGtLU2U4TmlacUl1aitVUVIyZ3pEQVAKQ2VXcXl2Y2pRNmovU1Yw ++WjVvKzlTNytiOStpWWx5RTg2bGZobHh5Z21aNnptYisxUUNteUtNVUdBNis5VmUvMgo1MHhDMXBB ++L2p3S0JnUUNEOHA4UnpVcDFZK3I1WnVaVzN0RGVJSXZqTWpTeVFNSGE0QWhuTm1tSjREcjBUcDIy ++CmFpci82TmY2WEhsUlpqOHZVSEZUMnpvbG1FalBneTZ1WWZsUCtocmtqeVU0ZWVRVTcxRy9Mek45 ++UjBRcCs4Nk4KT1NSaHhhQzdHRE0xaFh0VFlVSUtJa1RmUVgzeXZGTEJqcE0yN3RINEZHSmVWWitk ++UEdiWmE5REltUUtCZ1FENQpHTU5qeExiQnhhY25QYThldG5KdnE1SUR5RFRJY0xtc1dQMkZ6cjNX ++WTVSZzhybGE4aWZ5WVVxNE92cXNPRWZjCjk2ZlVVNUFHejd2TWs4VXZNUmtaK3JRVnJ4aXR2Q2g3 ++STdxRkIvOWdWVEFWU080TE8vR29oczBqeGRBd0ZBK2IKbWtyOVQ4ekh2cXNqZlNWSW51bXRTL0Nl ++d0plaHl2cjBoSjg1em9Fbnd3S0JnR1h6UXVDSjJDb3NHVVhEdnlHKwpyRzVBd3pUZGd0bHg4YTBK ++NTg1OWtZbVd0cW5WYUJmbFdrRmNUcHNEaGZ2ZWVDUkswc29VRlNPWkcranpsbWJrCkpRL09aVkZJ ++dG9MSVZCeE9qeWVXNlhUSkJXUzFRSkVHckkwY0tTbXNKcENtUXVPdUxMVnZYczV0U21CVmc5RXQK ++MjZzUkZwcjVWWmsrZlNRa3RhbkM4NGV1Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +diff --git a/src/ukify/test/example.tpm2-pcr-private.pem.base64 b/src/ukify/test/example.tpm2-pcr-private.pem.base64 +new file mode 100644 +index 0000000000..586b28ef9b +--- /dev/null ++++ b/src/ukify/test/example.tpm2-pcr-private.pem.base64 +@@ -0,0 +1,30 @@ ++LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2Z0lCQURBTkJna3Foa2lHOXcwQkFRRUZB ++QVNDQktnd2dnU2tBZ0VBQW9JQkFRQzVuOHFhbzVNZ1BJUVcKc0F5Y2R3dnB1bjdNNHlRSW9FL3I3 ++ekFGTG1hZlBXclo3d2JaaUIyTkY1MVdHOEo4bnlDQkI3M0RLcmZaeWs5cwphQXdXVW5RR2t0dGFv ++RXpXRzZSRTM3dXdQOUpVM09YdklTNTBhcy9KSHVHNlJPYmE2V0NOOFp2TTdkZGpvTDFKCkZlYnBS ++SXI1Vi82VStMTFhrUnRNYVczUnZ6T0xYeU1NT2QzOEcxZ0d0VlRHcm90ejVldFgrTUNVU2lOVGFE ++OVUKN1dEZXVsZXVpMlRnK1I3TGRoSXg3ZTQ5cEhRM3d6a1NxeFQ4SGpoU3ZURWpITWVSNjIwaUhF ++ZW9uYzdsMXVnagpzY1pwTktHdk13bXUvU2ptWFp6UkpOdjVOU0txcEVnQll2RnFkS3dUdlc4MWl6 ++SUFvN3paMkx6NDJYb25zSWJ2CjNrbGZqTG1mQWdNQkFBRUNnZ0VBQXozYm8yeTAzb3kvLzhkdVNQ ++TTVSWWtvdXJwQ3dGWFFYMzNyV0VQUnJmazgKR3ZjMkp1bGVIcjhwVTc0alhOcklqZ2hORTVIMDZQ ++eEQrOUFyV2Q1eHdVV2lTQWhobnlHWGNrNTM4Q0dGTWs4egpRc1JSRTk1anA0Ny9BU28vMzlYUWhs ++b1FUdmxlV0JLUUM2MHl2YU1oVEM1eHR6ZEtwRUlYK0hNazVGTlMrcDJVCmxtL3AzVE1YWDl1bmc5 ++Mk9pTzUzV1VreFpQN2cwTVJHbGJrNzhqc1dkdjFYY0tLRjhuVmU5WC9NR1lTYlVLNy8KM2NYazFR ++WTRUdVZaQlBFSE12RFRpWWwxbmdDd1ZuL2MyY3JQU3hJRFdFWlhEdm90SFUwQkNQZURVckxGa0F5 ++cQpDaloza3MzdEh4am42STkraEVNcUJDMzY1MHFjdDNkZ0RVV2loc2MzdVFLQmdRRG1mVTNKc29K ++QWFOdmxCbXgyClhzRDRqbXlXV1F2Z244cVNVNG03a2JKdmprcUJ6VnB0T0ZsYmk2ejZHOXR6ZHNX ++a0dJSjh3T0ZRb1hlM0dKOFIKSlVpeEFXTWZOM1JURGo5VjVXbzZJdE5EbzM1N3dNbVVYOW1qeThF ++YXp0RE1BckdSNGJva0Q5RjY3clhqSGdSMQpaZVcvSDlUWHFUV1l4VHl6UDB3ZDBQeUZ4d0tCZ1FE ++T0swWHVQS0o0WG00WmFCemN0OTdETXdDcFBSVmVvUWU3CmkzQjRJQ3orWFZ4cVM2amFTY2xNeEVm ++Nk5tM2tLNERDR1dwVkpXcm9qNjlMck1KWnQzTlI2VUJ5NzNqUVBSamsKRXk5N3YrR04yVGwwNjFw ++ZUxUM0dRS2RhT2VxWldpdElOcFc1dUxHL1poMGhoRUY5c1lSVTRtUFYwUWpla2kvdgp1bnVmcWx0 ++TmFRS0JnQTl6TE1pdFg0L0R0NkcxZVlYQnVqdXZDRlpYcDdVcDRPRklHajVwZU1XRGl6a0NNK0tJ ++CldXMEtndERORnp1NUpXeG5mQyt5bWlmV2V2alovS2Vna1N2VVJQbXR0TzF3VWd5RzhVVHVXcXo1 ++QTV4MkFzMGcKVTYxb0ZneWUrbDRDZkRha0k5OFE5R0RDS1kwTTBRMnhnK0g0MTBLUmhCYzJlV2dt ++Z1FxcW5KSzNBb0dCQU1rZgpnOWZXQlBVQndjdzlPYkxFR0tjNkVSSUlTZG1IbytCOE5kcXFJTnAv ++djFEZXdEazZ0QXFVakZiMlZCdTdxSjh4ClpmN3NRcS9ldzdaQ01WS09XUXgyVEc0VFdUdGo3dTFJ ++SGhGTjdiNlFRN0hnaXNiR3diV3VpdFBGSGl3OXYyMXgKK253MFJnb2VscHFFeDlMVG92R2Y3SjdB ++ampONlR4TkJTNnBGNlUzSkFvR0JBT0tnbHlRWDJpVG5oMXd4RG1TVQo4RXhoQVN3S09iNS8yRmx4 ++aUhtUHVjNTZpS0tHY0lkV1cxMUdtbzdJakNzSTNvRm9iRkFjKzBXZkMvQTdMNWlmCjNBYVNWcmh0 ++cThRRklRaUtyYUQ0YlRtRk9Famg5QVVtUHMrWnd1OE9lSXJBSWtwZDV3YmlhTEJLd0pRbVdtSFAK ++dUNBRTA3cXlSWXJ0c3QvcnVSSG5IdFA1Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +diff --git a/src/ukify/test/example.tpm2-pcr-private2.pem.base64 b/src/ukify/test/example.tpm2-pcr-private2.pem.base64 +new file mode 100644 +index 0000000000..d21a3d6043 +--- /dev/null ++++ b/src/ukify/test/example.tpm2-pcr-private2.pem.base64 +@@ -0,0 +1,30 @@ ++LS0tLS1CRUdJTiBQUklWQVRFIEtFWS0tLS0tCk1JSUV2QUlCQURBTkJna3Foa2lHOXcwQkFRRUZB ++QVNDQktZd2dnU2lBZ0VBQW9JQkFRQzJ2Nk1oZHg3a3VjUHIKbmtFNFIrY3FnV2Y5T3B1c2h2M2o3 ++SG50K08wdi84d2l2T1BFNTlLMHYvRWJOOG94TDZEWUNXU0JCRU4vREJ5MgpMUTYwbldSdHBZN2Ju ++bEcrcEtVeTRvSDRNZXZCR2JqZUhrak9LU3dNYVVWNGs4UmVSSjg4cVZ1U1MxSnVORW1NCmd5SERF ++NGFPNG5ndG5UUFZZdzUydVBIcG1rN0E4VFdXN2lLZE5JWWZWOCtuR1pENXIzRWllekRsUUNORG54 ++UkcKdm5uSFZ6VFhZR3RwY2xaeWlJclpVekpBNFFPZnRueXB5UDVrQS94NVM1MU9QeGFxWlA3eGtP ++S0NicUUvZmZvMApFTi9rTno0N0ZoUGUxbVBHUkZZWldHZXg0aWFPdHlLdHhnU1FYYkdlNEVoeVR4 ++SjJlT3U4QUVoVklTdjh6UU9nClNtbWx2UGQvQWdNQkFBRUNnZ0VBUUFpRERRRlR3bG96QTVhMmpK ++VnBNdlFYNzF0L1c2TUxTRGMrZS90cWhKU1IKUHlUSGZHR3NhMmdMLy9qNjhHUWJiRWRTUDRDeWM4 ++eFhMU0E1bEdESDVVR0svbm9KYzQ3MlVZK2JjYzl3SjMrdgpUcWoyNHNIN2JMZmdQMEVybjhwVXIy ++azZMRmNYSVlWUnRobm1sUmQ4NFFrS2loVVlxZTdsRFFWOXdsZ3V1eHpRCnBmVEtDTWk1bXJlYjIx ++OExHS0QrMUxjVmVYZjExamc3Z2JnMllLZ1dOQ2R3VmIyUzJ5V0hTTjBlT3hPd21kWXIKSUVCekpG ++eEc2MFJxSlJ1RzVIam9iemc2cy9ycUo1THFta3JhUWh6bHFPQVZLblpGOHppbG9vcDhXUXBQY3RN ++cwp0cHBjczhtYkFkWHpoSTVjN0U1VVpDM2NJcEd6SE4raDZLK0F3R3ZEeVFLQmdRRDRBOTdQM29v ++dGhoMHZHQmFWCnZWOXhHTm1YbW5TeUg0b29HcmJvaG1JWkkwVlhZdms5dWViSUJjbDZRMUx4WnN3 ++UFpRMVh5TUhpTjY1Z0E1emgKai9HZGcrdDlvcU5CZml0TUFrUTl1aWxvaXlKVWhYblk5REMvRitl ++ZksycEpNbHdkci9qWEttRHpkQUZBVDgyWQpWRmJ3MlpLVi9GNEJNMUtCdDBZN0RPTmlad0tCZ1FD ++OG9kZk0waytqL25VSzQ4TEV2MGtGbVNMdWdnTVlkM3hVCmZibmx0cUhFTVpJZU45OFVHK2hBWEdw ++dU1Ya0JPM2Mwcm5ZRDVXZkNBRzFxT1V2ZTZzdHd6N0VuK3hWdlkvcWEKU3ZTaDRzMzhnZlBIeXhR ++aGJvNWRwQTZUT3pwT0MyVi9rVXBVRUdJSmVVVllhQ05uWXNpUjRWUGVWL1lvR1htSwpQV29KbnAw ++REtRS0JnQlk3cXBheDJXczVVWlp1TDJBZkNOWkhwd0hySzdqb0VPZUZkWTRrdGRpUkM5OUlsUlZP ++CmUvekVZQXBnektldFVtK3kzRjVaTmVCRW81SWg0TWRyc3ZvdTRFWno5UFNqRGRpVGYzQ1ZKcThq ++Z2VGWDBkTjgKR0g2WTh2K1cwY0ZjRFZ2djhYdkFaYzZOUUt0Mk8vVUM0b1JXek1nN1JtWVBKcjlR ++SWJDYmVDclRBb0dBTjdZbApJbDFMSUVoYkVTaExzZ2c4N09aWnBzL0hVa2FYOWV4Y0p6aFZkcmlk ++UzBkOUgxZE90Uk9XYTQwNUMrQWdTUEx0CjhDQ2xFR3RINVlPZW9Pdi93Z1hWY05WN2N6YTRJVEhh ++SnFYeDZJNEpEZzB3bU44cU5RWHJPQmphRTRyU0kyY3AKNk1JZDhtWmEwTTJSQjB2cHFRdy8xUDl0 ++dUZJdHoySnNHd001cEdFQ2dZQVVnQVV3WENBcEtZVkZFRmxHNlBhYwpvdTBhdzdGNm1aMi9NNUcv ++ek9tMHFDYnNXdGFNU09TdUEvNmlVOXB0NDBaWUFONFUvd2ZxbncyVkVoRnA3dzFNCnpZWmJCRDBx ++ZVlkcDRmc1NuWXFMZmJBVmxQLzB6dmEzdkwwMlJFa25WalBVSnAvaGpKVWhBK21WN252VDZ5VjQK ++cTg4SWVvOEx3Q1c1c2Jtd2lyU3Btdz09Ci0tLS0tRU5EIFBSSVZBVEUgS0VZLS0tLS0K +diff --git a/src/ukify/test/example.tpm2-pcr-public.pem.base64 b/src/ukify/test/example.tpm2-pcr-public.pem.base64 +new file mode 100644 +index 0000000000..728a0f5362 +--- /dev/null ++++ b/src/ukify/test/example.tpm2-pcr-public.pem.base64 +@@ -0,0 +1,8 @@ ++LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR ++OEFNSUlCQ2dLQ0FRRUF1Wi9LbXFPVElEeUVGckFNbkhjTAo2YnArek9Na0NLQlA2Kzh3QlM1bW56 ++MXEyZThHMllnZGpSZWRWaHZDZko4Z2dRZTl3eXEzMmNwUGJHZ01GbEowCkJwTGJXcUJNMWh1a1JO ++KzdzRC9TVk56bDd5RXVkR3JQeVI3aHVrVG0ydWxnamZHYnpPM1hZNkM5U1JYbTZVU0sKK1ZmK2xQ ++aXkxNUViVEdsdDBiOHppMThqRERuZC9CdFlCclZVeHE2TGMrWHJWL2pBbEVvalUyZy9WTzFnM3Jw ++WApyb3RrNFBrZXkzWVNNZTN1UGFSME44TTVFcXNVL0I0NFVyMHhJeHpIa2V0dEloeEhxSjNPNWRi ++b0k3SEdhVFNoCnJ6TUpydjBvNWwyYzBTVGIrVFVpcXFSSUFXTHhhblNzRTcxdk5Zc3lBS084MmRp ++OCtObDZKN0NHNzk1Slg0eTUKbndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg== +diff --git a/src/ukify/test/example.tpm2-pcr-public2.pem.base64 b/src/ukify/test/example.tpm2-pcr-public2.pem.base64 +new file mode 100644 +index 0000000000..44bb3ee9ac +--- /dev/null ++++ b/src/ukify/test/example.tpm2-pcr-public2.pem.base64 +@@ -0,0 +1,8 @@ ++LS0tLS1CRUdJTiBQVUJMSUMgS0VZLS0tLS0KTUlJQklqQU5CZ2txaGtpRzl3MEJBUUVGQUFPQ0FR ++OEFNSUlCQ2dLQ0FRRUF0citqSVhjZTVMbkQ2NTVCT0VmbgpLb0ZuL1RxYnJJYjk0K3g1N2ZqdEwv ++L01JcnpqeE9mU3RML3hHemZLTVMrZzJBbGtnUVJEZnd3Y3RpME90SjFrCmJhV08yNTVSdnFTbE11 ++S0IrREhyd1JtNDNoNUl6aWtzREdsRmVKUEVYa1NmUEtsYmtrdFNialJKaklNaHd4T0cKanVKNExa ++MHoxV01PZHJqeDZacE93UEUxbHU0aW5UU0dIMWZQcHhtUSthOXhJbnN3NVVBalE1OFVScjU1eDFj ++MAoxMkJyYVhKV2NvaUsyVk15UU9FRG43WjhxY2orWkFQOGVVdWRUajhXcW1UKzhaRGlnbTZoUDMz ++Nk5CRGY1RGMrCk94WVQzdFpqeGtSV0dWaG5zZUltanJjaXJjWUVrRjJ4bnVCSWNrOFNkbmpydkFC ++SVZTRXIvTTBEb0VwcHBiejMKZndJREFRQUIKLS0tLS1FTkQgUFVCTElDIEtFWS0tLS0tCg== +diff --git a/src/ukify/test/meson.build b/src/ukify/test/meson.build +new file mode 100644 +index 0000000000..5870ccc06c +--- /dev/null ++++ b/src/ukify/test/meson.build +@@ -0,0 +1,20 @@ ++# SPDX-License-Identifier: LGPL-2.1-or-later ++ ++if want_ukify and want_tests != 'false' ++ have_pytest_flakes = pymod.find_installation( ++ 'python3', ++ required : false, ++ modules : ['pytest_flakes'], ++ ).found() ++ ++ args = ['-v'] ++ if have_pytest_flakes ++ args += ['--flakes'] ++ endif ++ ++ test('test-ukify', ++ files('test_ukify.py'), ++ args: args, ++ env : test_env, ++ suite : 'ukify') ++endif +diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py +new file mode 100755 +index 0000000000..b12c09d4bf +--- /dev/null ++++ b/src/ukify/test/test_ukify.py +@@ -0,0 +1,848 @@ ++#!/usr/bin/env python3 ++# SPDX-License-Identifier: LGPL-2.1-or-later ++ ++# pylint: disable=unused-import,import-outside-toplevel,useless-else-on-loop ++# pylint: disable=consider-using-with,wrong-import-position,unspecified-encoding ++# pylint: disable=protected-access,redefined-outer-name ++ ++import base64 ++import json ++import os ++import pathlib ++import re ++import shutil ++import subprocess ++import sys ++import tempfile ++import textwrap ++ ++try: ++ import pytest ++except ImportError as e: ++ print(str(e), file=sys.stderr) ++ sys.exit(77) ++ ++try: ++ # pyflakes: noqa ++ import pefile # noqa ++except ImportError as e: ++ print(str(e), file=sys.stderr) ++ sys.exit(77) ++ ++# We import ukify.py, which is a template file. But only __version__ is ++# substituted, which we don't care about here. Having the .py suffix makes it ++# easier to import the file. ++sys.path.append(os.path.dirname(__file__) + '/..') ++import ukify ++ ++build_root = os.getenv('PROJECT_BUILD_ROOT') ++arg_tools = ['--tools', build_root] if build_root else [] ++ ++def systemd_measure(): ++ opts = ukify.create_parser().parse_args(arg_tools) ++ return ukify.find_tool('systemd-measure', opts=opts) ++ ++def test_guess_efi_arch(): ++ arch = ukify.guess_efi_arch() ++ assert arch in ukify.EFI_ARCHES ++ ++def test_shell_join(): ++ assert ukify.shell_join(['a', 'b', ' ']) == "a b ' '" ++ ++def test_round_up(): ++ assert ukify.round_up(0) == 0 ++ assert ukify.round_up(4095) == 4096 ++ assert ukify.round_up(4096) == 4096 ++ assert ukify.round_up(4097) == 8192 ++ ++def test_namespace_creation(): ++ ns = ukify.create_parser().parse_args(()) ++ assert ns.linux is None ++ assert ns.initrd is None ++ ++def test_config_example(): ++ ex = ukify.config_example() ++ assert '[UKI]' in ex ++ assert 'Splash = BMP' in ex ++ ++def test_apply_config(tmp_path): ++ config = tmp_path / 'config1.conf' ++ config.write_text(textwrap.dedent( ++ f''' ++ [UKI] ++ Linux = LINUX ++ Initrd = initrd1 initrd2 ++ initrd3 ++ Cmdline = 1 2 3 4 5 ++ 6 7 8 ++ OSRelease = @some/path1 ++ DeviceTree = some/path2 ++ Splash = some/path3 ++ Uname = 1.2.3 ++ EFIArch=arm ++ Stub = some/path4 ++ PCRBanks = sha512,sha1 ++ SigningEngine = engine1 ++ SecureBootPrivateKey = some/path5 ++ SecureBootCertificate = some/path6 ++ SignKernel = no ++ ++ [PCRSignature:NAME] ++ PCRPrivateKey = some/path7 ++ PCRPublicKey = some/path8 ++ Phases = {':'.join(ukify.KNOWN_PHASES)} ++ ''')) ++ ++ ns = ukify.create_parser().parse_args(['build']) ++ ns.linux = None ++ ns.initrd = [] ++ ukify.apply_config(ns, config) ++ ++ assert ns.linux == pathlib.Path('LINUX') ++ assert ns.initrd == [pathlib.Path('initrd1'), ++ pathlib.Path('initrd2'), ++ pathlib.Path('initrd3')] ++ assert ns.cmdline == '1 2 3 4 5\n6 7 8' ++ assert ns.os_release == '@some/path1' ++ assert ns.devicetree == pathlib.Path('some/path2') ++ assert ns.splash == pathlib.Path('some/path3') ++ assert ns.efi_arch == 'arm' ++ assert ns.stub == pathlib.Path('some/path4') ++ assert ns.pcr_banks == ['sha512', 'sha1'] ++ assert ns.signing_engine == 'engine1' ++ assert ns.sb_key == 'some/path5' ++ assert ns.sb_cert == 'some/path6' ++ assert ns.sign_kernel is False ++ ++ assert ns._groups == ['NAME'] ++ assert ns.pcr_private_keys == [pathlib.Path('some/path7')] ++ assert ns.pcr_public_keys == [pathlib.Path('some/path8')] ++ assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] ++ ++ ukify.finalize_options(ns) ++ ++ assert ns.linux == pathlib.Path('LINUX') ++ assert ns.initrd == [pathlib.Path('initrd1'), ++ pathlib.Path('initrd2'), ++ pathlib.Path('initrd3')] ++ assert ns.cmdline == '1 2 3 4 5 6 7 8' ++ assert ns.os_release == pathlib.Path('some/path1') ++ assert ns.devicetree == pathlib.Path('some/path2') ++ assert ns.splash == pathlib.Path('some/path3') ++ assert ns.efi_arch == 'arm' ++ assert ns.stub == pathlib.Path('some/path4') ++ assert ns.pcr_banks == ['sha512', 'sha1'] ++ assert ns.signing_engine == 'engine1' ++ assert ns.sb_key == 'some/path5' ++ assert ns.sb_cert == 'some/path6' ++ assert ns.sign_kernel is False ++ ++ assert ns._groups == ['NAME'] ++ assert ns.pcr_private_keys == [pathlib.Path('some/path7')] ++ assert ns.pcr_public_keys == [pathlib.Path('some/path8')] ++ assert ns.phase_path_groups == [['enter-initrd:leave-initrd:sysinit:ready:shutdown:final']] ++ ++def test_parse_args_minimal(): ++ with pytest.raises(ValueError): ++ ukify.parse_args([]) ++ ++ opts = ukify.parse_args('arg1 arg2'.split()) ++ assert opts.linux == pathlib.Path('arg1') ++ assert opts.initrd == [pathlib.Path('arg2')] ++ assert opts.os_release in (pathlib.Path('/etc/os-release'), ++ pathlib.Path('/usr/lib/os-release')) ++ ++def test_parse_args_many_deprecated(): ++ opts = ukify.parse_args( ++ ['/ARG1', '///ARG2', '/ARG3 WITH SPACE', ++ '--cmdline=a b c', ++ '--os-release=K1=V1\nK2=V2', ++ '--devicetree=DDDDTTTT', ++ '--splash=splash', ++ '--pcrpkey=PATH', ++ '--uname=1.2.3', ++ '--stub=STUBPATH', ++ '--pcr-private-key=PKEY1', ++ '--pcr-public-key=PKEY2', ++ '--pcr-banks=SHA1,SHA256', ++ '--signing-engine=ENGINE', ++ '--secureboot-private-key=SBKEY', ++ '--secureboot-certificate=SBCERT', ++ '--sign-kernel', ++ '--no-sign-kernel', ++ '--tools=TOOLZ///', ++ '--output=OUTPUT', ++ '--measure', ++ '--no-measure', ++ ]) ++ assert opts.linux == pathlib.Path('/ARG1') ++ assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] ++ assert opts.cmdline == 'a b c' ++ assert opts.os_release == 'K1=V1\nK2=V2' ++ assert opts.devicetree == pathlib.Path('DDDDTTTT') ++ assert opts.splash == pathlib.Path('splash') ++ assert opts.pcrpkey == pathlib.Path('PATH') ++ assert opts.uname == '1.2.3' ++ assert opts.stub == pathlib.Path('STUBPATH') ++ assert opts.pcr_private_keys == [pathlib.Path('PKEY1')] ++ assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] ++ assert opts.pcr_banks == ['SHA1', 'SHA256'] ++ assert opts.signing_engine == 'ENGINE' ++ assert opts.sb_key == 'SBKEY' ++ assert opts.sb_cert == 'SBCERT' ++ assert opts.sign_kernel is False ++ assert opts.tools == [pathlib.Path('TOOLZ/')] ++ assert opts.output == pathlib.Path('OUTPUT') ++ assert opts.measure is False ++ ++def test_parse_args_many(): ++ opts = ukify.parse_args( ++ ['build', ++ '--linux=/ARG1', ++ '--initrd=///ARG2', ++ '--initrd=/ARG3 WITH SPACE', ++ '--cmdline=a b c', ++ '--os-release=K1=V1\nK2=V2', ++ '--devicetree=DDDDTTTT', ++ '--splash=splash', ++ '--pcrpkey=PATH', ++ '--uname=1.2.3', ++ '--stub=STUBPATH', ++ '--pcr-private-key=PKEY1', ++ '--pcr-public-key=PKEY2', ++ '--pcr-banks=SHA1,SHA256', ++ '--signing-engine=ENGINE', ++ '--secureboot-private-key=SBKEY', ++ '--secureboot-certificate=SBCERT', ++ '--sign-kernel', ++ '--no-sign-kernel', ++ '--tools=TOOLZ///', ++ '--output=OUTPUT', ++ '--measure', ++ '--no-measure', ++ ]) ++ assert opts.linux == pathlib.Path('/ARG1') ++ assert opts.initrd == [pathlib.Path('/ARG2'), pathlib.Path('/ARG3 WITH SPACE')] ++ assert opts.cmdline == 'a b c' ++ assert opts.os_release == 'K1=V1\nK2=V2' ++ assert opts.devicetree == pathlib.Path('DDDDTTTT') ++ assert opts.splash == pathlib.Path('splash') ++ assert opts.pcrpkey == pathlib.Path('PATH') ++ assert opts.uname == '1.2.3' ++ assert opts.stub == pathlib.Path('STUBPATH') ++ assert opts.pcr_private_keys == [pathlib.Path('PKEY1')] ++ assert opts.pcr_public_keys == [pathlib.Path('PKEY2')] ++ assert opts.pcr_banks == ['SHA1', 'SHA256'] ++ assert opts.signing_engine == 'ENGINE' ++ assert opts.sb_key == 'SBKEY' ++ assert opts.sb_cert == 'SBCERT' ++ assert opts.sign_kernel is False ++ assert opts.tools == [pathlib.Path('TOOLZ/')] ++ assert opts.output == pathlib.Path('OUTPUT') ++ assert opts.measure is False ++ ++def test_parse_sections(): ++ opts = ukify.parse_args( ++ ['build', ++ '--linux=/ARG1', ++ '--initrd=/ARG2', ++ '--section=test:TESTTESTTEST', ++ '--section=test2:@FILE', ++ ]) ++ ++ assert opts.linux == pathlib.Path('/ARG1') ++ assert opts.initrd == [pathlib.Path('/ARG2')] ++ assert len(opts.sections) == 2 ++ ++ assert opts.sections[0].name == 'test' ++ assert isinstance(opts.sections[0].content, pathlib.Path) ++ assert opts.sections[0].tmpfile ++ assert opts.sections[0].measure is False ++ ++ assert opts.sections[1].name == 'test2' ++ assert opts.sections[1].content == pathlib.Path('FILE') ++ assert opts.sections[1].tmpfile is None ++ assert opts.sections[1].measure is False ++ ++def test_config_priority(tmp_path): ++ config = tmp_path / 'config1.conf' ++ # config: use pesign and give certdir + certname ++ config.write_text(textwrap.dedent( ++ f''' ++ [UKI] ++ Linux = LINUX ++ Initrd = initrd1 initrd2 ++ initrd3 ++ Cmdline = 1 2 3 4 5 ++ 6 7 8 ++ OSRelease = @some/path1 ++ DeviceTree = some/path2 ++ Splash = some/path3 ++ Uname = 1.2.3 ++ EFIArch = arm ++ Stub = some/path4 ++ PCRBanks = sha512,sha1 ++ SigningEngine = engine1 ++ SecureBootSigningTool = pesign ++ SecureBootCertificateDir = some/path5 ++ SecureBootCertificateName = some/name1 ++ SignKernel = no ++ ++ [PCRSignature:NAME] ++ PCRPrivateKey = some/path7 ++ PCRPublicKey = some/path8 ++ Phases = {':'.join(ukify.KNOWN_PHASES)} ++ ''')) ++ ++ # args: use sbsign and give key + cert, should override pesign ++ opts = ukify.parse_args( ++ ['build', ++ '--linux=/ARG1', ++ '--initrd=///ARG2', ++ '--initrd=/ARG3 WITH SPACE', ++ '--cmdline= a b c ', ++ '--os-release=K1=V1\nK2=V2', ++ '--devicetree=DDDDTTTT', ++ '--splash=splash', ++ '--pcrpkey=PATH', ++ '--uname=1.2.3', ++ '--stub=STUBPATH', ++ '--pcr-private-key=PKEY1', ++ '--pcr-public-key=PKEY2', ++ '--pcr-banks=SHA1,SHA256', ++ '--signing-engine=ENGINE', ++ '--signtool=sbsign', ++ '--secureboot-private-key=SBKEY', ++ '--secureboot-certificate=SBCERT', ++ '--sign-kernel', ++ '--no-sign-kernel', ++ '--tools=TOOLZ///', ++ '--output=OUTPUT', ++ '--measure', ++ ]) ++ ++ ukify.apply_config(opts, config) ++ ukify.finalize_options(opts) ++ ++ assert opts.linux == pathlib.Path('/ARG1') ++ assert opts.initrd == [pathlib.Path('initrd1'), ++ pathlib.Path('initrd2'), ++ pathlib.Path('initrd3'), ++ pathlib.Path('/ARG2'), ++ pathlib.Path('/ARG3 WITH SPACE')] ++ assert opts.cmdline == 'a b c' ++ assert opts.os_release == 'K1=V1\nK2=V2' ++ assert opts.devicetree == pathlib.Path('DDDDTTTT') ++ assert opts.splash == pathlib.Path('splash') ++ assert opts.pcrpkey == pathlib.Path('PATH') ++ assert opts.uname == '1.2.3' ++ assert opts.stub == pathlib.Path('STUBPATH') ++ assert opts.pcr_private_keys == [pathlib.Path('PKEY1'), ++ pathlib.Path('some/path7')] ++ assert opts.pcr_public_keys == [pathlib.Path('PKEY2'), ++ pathlib.Path('some/path8')] ++ assert opts.pcr_banks == ['SHA1', 'SHA256'] ++ assert opts.signing_engine == 'ENGINE' ++ assert opts.signtool == 'sbsign' # from args ++ assert opts.sb_key == 'SBKEY' # from args ++ assert opts.sb_cert == 'SBCERT' # from args ++ assert opts.sb_certdir == 'some/path5' # from config ++ assert opts.sb_cert_name == 'some/name1' # from config ++ assert opts.sign_kernel is False ++ assert opts.tools == [pathlib.Path('TOOLZ/')] ++ assert opts.output == pathlib.Path('OUTPUT') ++ assert opts.measure is True ++ ++def test_help(capsys): ++ with pytest.raises(SystemExit): ++ ukify.parse_args(['--help']) ++ out = capsys.readouterr() ++ assert '--section' in out.out ++ assert not out.err ++ ++def test_help_display(capsys): ++ with pytest.raises(SystemExit): ++ ukify.parse_args(['inspect', '--help']) ++ out = capsys.readouterr() ++ assert '--section' in out.out ++ assert not out.err ++ ++def test_help_error_deprecated(capsys): ++ with pytest.raises(SystemExit): ++ ukify.parse_args(['a', 'b', '--no-such-option']) ++ out = capsys.readouterr() ++ assert not out.out ++ assert '--no-such-option' in out.err ++ assert len(out.err.splitlines()) == 1 ++ ++def test_help_error(capsys): ++ with pytest.raises(SystemExit): ++ ukify.parse_args(['build', '--no-such-option']) ++ out = capsys.readouterr() ++ assert not out.out ++ assert '--no-such-option' in out.err ++ assert len(out.err.splitlines()) == 1 ++ ++@pytest.fixture(scope='session') ++def kernel_initrd(): ++ opts = ukify.create_parser().parse_args(arg_tools) ++ bootctl = ukify.find_tool('bootctl', opts=opts) ++ if bootctl is None: ++ return None ++ ++ try: ++ text = subprocess.check_output([bootctl, 'list', '--json=short'], ++ text=True) ++ except subprocess.CalledProcessError: ++ return None ++ ++ items = json.loads(text) ++ ++ for item in items: ++ try: ++ linux = f"{item['root']}{item['linux']}" ++ initrd = f"{item['root']}{item['initrd'][0].split(' ')[0]}" ++ except (KeyError, IndexError): ++ continue ++ return ['--linux', linux, '--initrd', initrd] ++ else: ++ return None ++ ++def test_check_splash(): ++ try: ++ # pyflakes: noqa ++ import PIL # noqa ++ except ImportError: ++ pytest.skip('PIL not available') ++ ++ with pytest.raises(OSError): ++ ukify.check_splash(os.devnull) ++ ++def test_basic_operation(kernel_initrd, tmpdir): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ ++ output = f'{tmpdir}/basic.efi' ++ opts = ukify.parse_args([ ++ 'build', ++ *kernel_initrd, ++ f'--output={output}', ++ ]) ++ try: ++ ukify.check_inputs(opts) ++ except OSError as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ # let's check that objdump likes the resulting file ++ subprocess.check_output(['objdump', '-h', output]) ++ ++def test_sections(kernel_initrd, tmpdir): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ ++ output = f'{tmpdir}/basic.efi' ++ opts = ukify.parse_args([ ++ 'build', ++ *kernel_initrd, ++ f'--output={output}', ++ '--uname=1.2.3', ++ '--cmdline=ARG1 ARG2 ARG3', ++ '--os-release=K1=V1\nK2=V2\n', ++ '--section=.test:CONTENTZ', ++ ]) ++ ++ try: ++ ukify.check_inputs(opts) ++ except OSError as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ # let's check that objdump likes the resulting file ++ dump = subprocess.check_output(['objdump', '-h', output], text=True) ++ ++ for sect in 'text osrel cmdline linux initrd uname test'.split(): ++ assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE) ++ ++def test_addon(tmpdir): ++ output = f'{tmpdir}/addon.efi' ++ args = [ ++ 'build', ++ f'--output={output}', ++ '--cmdline=ARG1 ARG2 ARG3', ++ """--sbat=sbat,1,foo ++foo,1 ++bar,2 ++""", ++ '--section=.test:CONTENTZ', ++ """--sbat=sbat,1,foo ++baz,3 ++""" ++ ] ++ if stub := os.getenv('EFI_ADDON'): ++ args += [f'--stub={stub}'] ++ expected_exceptions = () ++ else: ++ expected_exceptions = (FileNotFoundError,) ++ ++ opts = ukify.parse_args(args) ++ try: ++ ukify.check_inputs(opts) ++ except expected_exceptions as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ # let's check that objdump likes the resulting file ++ dump = subprocess.check_output(['objdump', '-h', output], text=True) ++ ++ for sect in 'text cmdline test sbat'.split(): ++ assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE) ++ ++ pe = pefile.PE(output, fast_load=True) ++ found = False ++ ++ for section in pe.sections: ++ if section.Name.rstrip(b"\x00").decode() == ".sbat": ++ assert found is False ++ split = section.get_data().rstrip(b"\x00").decode().splitlines() ++ assert split == ["sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md", "foo,1", "bar,2", "baz,3"] ++ found = True ++ ++ assert found is True ++ ++ ++def unbase64(filename): ++ tmp = tempfile.NamedTemporaryFile() ++ base64.decode(filename.open('rb'), tmp) ++ tmp.flush() ++ return tmp ++ ++ ++def test_uname_scraping(kernel_initrd): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ ++ assert kernel_initrd[0] == '--linux' ++ uname = ukify.Uname.scrape(kernel_initrd[1]) ++ assert re.match(r'\d+\.\d+\.\d+', uname) ++ ++def test_efi_signing_sbsign(kernel_initrd, tmpdir): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ if not shutil.which('sbsign'): ++ pytest.skip('sbsign not found') ++ ++ ourdir = pathlib.Path(__file__).parent ++ cert = unbase64(ourdir / 'example.signing.crt.base64') ++ key = unbase64(ourdir / 'example.signing.key.base64') ++ ++ output = f'{tmpdir}/signed.efi' ++ opts = ukify.parse_args([ ++ 'build', ++ *kernel_initrd, ++ f'--output={output}', ++ '--uname=1.2.3', ++ '--cmdline=ARG1 ARG2 ARG3', ++ f'--secureboot-certificate={cert.name}', ++ f'--secureboot-private-key={key.name}', ++ ]) ++ ++ try: ++ ukify.check_inputs(opts) ++ except OSError as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ if shutil.which('sbverify'): ++ # let's check that sbverify likes the resulting file ++ dump = subprocess.check_output([ ++ 'sbverify', ++ '--cert', cert.name, ++ output, ++ ], text=True) ++ ++ assert 'Signature verification OK' in dump ++ ++def test_efi_signing_pesign(kernel_initrd, tmpdir): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ if not shutil.which('pesign'): ++ pytest.skip('pesign not found') ++ ++ nss_db = f'{tmpdir}/nss_db' ++ name = 'Test_Secureboot' ++ author = 'systemd' ++ ++ subprocess.check_call(['mkdir', '-p', nss_db]) ++ cmd = f'certutil -N --empty-password -d {nss_db}'.split(' ') ++ subprocess.check_call(cmd) ++ cmd = f'efikeygen -d {nss_db} -S -k -c CN={author} -n {name}'.split(' ') ++ subprocess.check_call(cmd) ++ ++ output = f'{tmpdir}/signed.efi' ++ opts = ukify.parse_args([ ++ 'build', ++ *kernel_initrd, ++ f'--output={output}', ++ '--uname=1.2.3', ++ '--signtool=pesign', ++ '--cmdline=ARG1 ARG2 ARG3', ++ f'--secureboot-certificate-name={name}', ++ f'--secureboot-certificate-dir={nss_db}', ++ ]) ++ ++ try: ++ ukify.check_inputs(opts) ++ except OSError as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ # let's check that sbverify likes the resulting file ++ dump = subprocess.check_output([ ++ 'pesign', '-S', ++ '-i', output, ++ ], text=True) ++ ++ assert f"The signer's common name is {author}" in dump ++ ++def test_inspect(kernel_initrd, tmpdir, capsys): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ if not shutil.which('sbsign'): ++ pytest.skip('sbsign not found') ++ ++ ourdir = pathlib.Path(__file__).parent ++ cert = unbase64(ourdir / 'example.signing.crt.base64') ++ key = unbase64(ourdir / 'example.signing.key.base64') ++ ++ output = f'{tmpdir}/signed2.efi' ++ uname_arg='1.2.3' ++ osrel_arg='Linux' ++ cmdline_arg='ARG1 ARG2 ARG3' ++ opts = ukify.parse_args([ ++ 'build', ++ *kernel_initrd, ++ f'--cmdline={cmdline_arg}', ++ f'--os-release={osrel_arg}', ++ f'--uname={uname_arg}', ++ f'--output={output}', ++ f'--secureboot-certificate={cert.name}', ++ f'--secureboot-private-key={key.name}', ++ ]) ++ ++ ukify.check_inputs(opts) ++ ukify.make_uki(opts) ++ ++ opts = ukify.parse_args(['inspect', output]) ++ ukify.inspect_sections(opts) ++ ++ text = capsys.readouterr().out ++ ++ expected_osrel = f'.osrel:\n size: {len(osrel_arg)}' ++ assert expected_osrel in text ++ expected_cmdline = f'.cmdline:\n size: {len(cmdline_arg)}' ++ assert expected_cmdline in text ++ expected_uname = f'.uname:\n size: {len(uname_arg)}' ++ assert expected_uname in text ++ ++ expected_initrd = '.initrd:\n size:' ++ assert expected_initrd in text ++ expected_linux = '.linux:\n size:' ++ assert expected_linux in text ++ ++ ++def test_pcr_signing(kernel_initrd, tmpdir): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ if systemd_measure() is None: ++ pytest.skip('systemd-measure not found') ++ ++ ourdir = pathlib.Path(__file__).parent ++ pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64') ++ priv = unbase64(ourdir / 'example.tpm2-pcr-private.pem.base64') ++ ++ output = f'{tmpdir}/signed.efi' ++ args = [ ++ 'build', ++ *kernel_initrd, ++ f'--output={output}', ++ '--uname=1.2.3', ++ '--cmdline=ARG1 ARG2 ARG3', ++ '--os-release=ID=foobar\n', ++ '--pcr-banks=sha1', # use sha1 because it doesn't really matter ++ f'--pcr-private-key={priv.name}', ++ ] + arg_tools ++ ++ # If the public key is not explicitly specified, it is derived automatically. Let's make sure everything ++ # works as expected both when the public keys is specified explicitly and when it is derived from the ++ # private key. ++ for extra in ([f'--pcrpkey={pub.name}', f'--pcr-public-key={pub.name}'], []): ++ opts = ukify.parse_args(args + extra) ++ try: ++ ukify.check_inputs(opts) ++ except OSError as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ # let's check that objdump likes the resulting file ++ dump = subprocess.check_output(['objdump', '-h', output], text=True) ++ ++ for sect in 'text osrel cmdline linux initrd uname pcrsig'.split(): ++ assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE) ++ ++ # objcopy fails when called without an output argument (EPERM). ++ # It also fails when called with /dev/null (file truncated). ++ # It also fails when called with /dev/zero (because it reads the ++ # output file, infinitely in this case.) ++ # So let's just call it with a dummy output argument. ++ subprocess.check_call([ ++ 'objcopy', ++ *(f'--dump-section=.{n}={tmpdir}/out.{n}' for n in ( ++ 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline')), ++ output, ++ tmpdir / 'dummy', ++ ], ++ text=True) ++ ++ assert open(tmpdir / 'out.pcrpkey').read() == open(pub.name).read() ++ assert open(tmpdir / 'out.osrel').read() == 'ID=foobar\n' ++ assert open(tmpdir / 'out.uname').read() == '1.2.3' ++ assert open(tmpdir / 'out.cmdline').read() == 'ARG1 ARG2 ARG3' ++ sig = open(tmpdir / 'out.pcrsig').read() ++ sig = json.loads(sig) ++ assert list(sig.keys()) == ['sha1'] ++ assert len(sig['sha1']) == 4 # four items for four phases ++ ++def test_pcr_signing2(kernel_initrd, tmpdir): ++ if kernel_initrd is None: ++ pytest.skip('linux+initrd not found') ++ if systemd_measure() is None: ++ pytest.skip('systemd-measure not found') ++ ++ ourdir = pathlib.Path(__file__).parent ++ pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64') ++ priv = unbase64(ourdir / 'example.tpm2-pcr-private.pem.base64') ++ pub2 = unbase64(ourdir / 'example.tpm2-pcr-public2.pem.base64') ++ priv2 = unbase64(ourdir / 'example.tpm2-pcr-private2.pem.base64') ++ ++ # simulate a microcode file ++ with open(f'{tmpdir}/microcode', 'wb') as microcode: ++ microcode.write(b'1234567890') ++ ++ output = f'{tmpdir}/signed.efi' ++ assert kernel_initrd[0] == '--linux' ++ opts = ukify.parse_args([ ++ 'build', ++ *kernel_initrd[:2], ++ f'--initrd={microcode.name}', ++ *kernel_initrd[2:], ++ f'--output={output}', ++ '--uname=1.2.3', ++ '--cmdline=ARG1 ARG2 ARG3', ++ '--os-release=ID=foobar\n', ++ '--pcr-banks=sha1', ++ f'--pcrpkey={pub2.name}', ++ f'--pcr-public-key={pub.name}', ++ f'--pcr-private-key={priv.name}', ++ '--phases=enter-initrd enter-initrd:leave-initrd', ++ f'--pcr-public-key={pub2.name}', ++ f'--pcr-private-key={priv2.name}', ++ '--phases=sysinit ready shutdown final', # yes, those phase paths are not reachable ++ ] + arg_tools) ++ ++ try: ++ ukify.check_inputs(opts) ++ except OSError as e: ++ pytest.skip(str(e)) ++ ++ ukify.make_uki(opts) ++ ++ # let's check that objdump likes the resulting file ++ dump = subprocess.check_output(['objdump', '-h', output], text=True) ++ ++ for sect in 'text osrel cmdline linux initrd uname pcrsig'.split(): ++ assert re.search(fr'^\s*\d+\s+.{sect}\s+0', dump, re.MULTILINE) ++ ++ subprocess.check_call([ ++ 'objcopy', ++ *(f'--dump-section=.{n}={tmpdir}/out.{n}' for n in ( ++ 'pcrpkey', 'pcrsig', 'osrel', 'uname', 'cmdline', 'initrd')), ++ output, ++ tmpdir / 'dummy', ++ ], ++ text=True) ++ ++ assert open(tmpdir / 'out.pcrpkey').read() == open(pub2.name).read() ++ assert open(tmpdir / 'out.osrel').read() == 'ID=foobar\n' ++ assert open(tmpdir / 'out.uname').read() == '1.2.3' ++ assert open(tmpdir / 'out.cmdline').read() == 'ARG1 ARG2 ARG3' ++ assert open(tmpdir / 'out.initrd', 'rb').read(10) == b'1234567890' ++ ++ sig = open(tmpdir / 'out.pcrsig').read() ++ sig = json.loads(sig) ++ assert list(sig.keys()) == ['sha1'] ++ assert len(sig['sha1']) == 6 # six items for six phases paths ++ ++def test_key_cert_generation(tmpdir): ++ opts = ukify.parse_args([ ++ 'genkey', ++ f"--pcr-public-key={tmpdir / 'pcr1.pub.pem'}", ++ f"--pcr-private-key={tmpdir / 'pcr1.priv.pem'}", ++ '--phases=enter-initrd enter-initrd:leave-initrd', ++ f"--pcr-public-key={tmpdir / 'pcr2.pub.pem'}", ++ f"--pcr-private-key={tmpdir / 'pcr2.priv.pem'}", ++ '--phases=sysinit ready', ++ f"--secureboot-private-key={tmpdir / 'sb.priv.pem'}", ++ f"--secureboot-certificate={tmpdir / 'sb.cert.pem'}", ++ ]) ++ assert opts.verb == 'genkey' ++ ukify.check_cert_and_keys_nonexistent(opts) ++ ++ pytest.importorskip('cryptography') ++ ++ ukify.generate_keys(opts) ++ ++ if not shutil.which('openssl'): ++ return ++ ++ for key in (tmpdir / 'pcr1.priv.pem', ++ tmpdir / 'pcr2.priv.pem', ++ tmpdir / 'sb.priv.pem'): ++ out = subprocess.check_output([ ++ 'openssl', 'rsa', ++ '-in', key, ++ '-text', ++ '-noout', ++ ], text = True) ++ assert 'Private-Key' in out ++ assert '2048 bit' in out ++ ++ for pub in (tmpdir / 'pcr1.pub.pem', ++ tmpdir / 'pcr2.pub.pem'): ++ out = subprocess.check_output([ ++ 'openssl', 'rsa', ++ '-pubin', ++ '-in', pub, ++ '-text', ++ '-noout', ++ ], text = True) ++ assert 'Public-Key' in out ++ assert '2048 bit' in out ++ ++ out = subprocess.check_output([ ++ 'openssl', 'x509', ++ '-in', tmpdir / 'sb.cert.pem', ++ '-text', ++ '-noout', ++ ], text = True) ++ assert 'Certificate' in out ++ assert 'Issuer: CN = SecureBoot signing key on host' in out ++ ++if __name__ == '__main__': ++ sys.exit(pytest.main(sys.argv)) +diff --git a/src/ukify/ukify.py b/src/ukify/ukify.py +new file mode 100755 +index 0000000000..08f505a271 +--- /dev/null ++++ b/src/ukify/ukify.py +@@ -0,0 +1,1660 @@ ++#!/usr/bin/env python3 ++# SPDX-License-Identifier: LGPL-2.1-or-later ++# ++# This file is part of systemd. ++# ++# systemd is free software; you can redistribute it and/or modify it ++# under the terms of the GNU Lesser General Public License as published by ++# the Free Software Foundation; either version 2.1 of the License, or ++# (at your option) any later version. ++# ++# systemd is distributed in the hope that it will be useful, but ++# WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public License ++# along with systemd; If not, see . ++ ++# pylint: disable=import-outside-toplevel,consider-using-with,unused-argument ++# pylint: disable=unnecessary-lambda-assignment ++ ++import argparse ++import configparser ++import contextlib ++import collections ++import dataclasses ++import datetime ++import fnmatch ++import itertools ++import json ++import os ++import pathlib ++import pprint ++import pydoc ++import re ++import shlex ++import shutil ++import socket ++import subprocess ++import sys ++import tempfile ++import textwrap ++from hashlib import sha256 ++from typing import (Any, ++ Callable, ++ IO, ++ Optional, ++ Sequence, ++ Union) ++ ++import pefile # type: ignore ++ ++__version__ = '{{PROJECT_VERSION}} ({{GIT_VERSION}})' ++ ++EFI_ARCH_MAP = { ++ # host_arch glob : [efi_arch, 32_bit_efi_arch if mixed mode is supported] ++ 'x86_64' : ['x64', 'ia32'], ++ 'i[3456]86' : ['ia32'], ++ 'aarch64' : ['aa64'], ++ 'armv[45678]*l': ['arm'], ++ 'loongarch32' : ['loongarch32'], ++ 'loongarch64' : ['loongarch64'], ++ 'riscv32' : ['riscv32'], ++ 'riscv64' : ['riscv64'], ++} ++EFI_ARCHES: list[str] = sum(EFI_ARCH_MAP.values(), []) ++ ++# Default configuration directories and file name. ++# When the user does not specify one, the directories are searched in this order and the first file found is used. ++DEFAULT_CONFIG_DIRS = ['/run/systemd', '/etc/systemd', '/usr/local/lib/systemd', '/usr/lib/systemd'] ++DEFAULT_CONFIG_FILE = 'ukify.conf' ++ ++class Style: ++ bold = "\033[0;1;39m" if sys.stderr.isatty() else "" ++ gray = "\033[0;38;5;245m" if sys.stderr.isatty() else "" ++ red = "\033[31;1m" if sys.stderr.isatty() else "" ++ yellow = "\033[33;1m" if sys.stderr.isatty() else "" ++ reset = "\033[0m" if sys.stderr.isatty() else "" ++ ++ ++def guess_efi_arch(): ++ arch = os.uname().machine ++ ++ for glob, mapping in EFI_ARCH_MAP.items(): ++ if fnmatch.fnmatch(arch, glob): ++ efi_arch, *fallback = mapping ++ break ++ else: ++ raise ValueError(f'Unsupported architecture {arch}') ++ ++ # This makes sense only on some architectures, but it also probably doesn't ++ # hurt on others, so let's just apply the check everywhere. ++ if fallback: ++ fw_platform_size = pathlib.Path('/sys/firmware/efi/fw_platform_size') ++ try: ++ size = fw_platform_size.read_text().strip() ++ except FileNotFoundError: ++ pass ++ else: ++ if int(size) == 32: ++ efi_arch = fallback[0] ++ ++ # print(f'Host arch {arch!r}, EFI arch {efi_arch!r}') ++ return efi_arch ++ ++ ++def page(text: str, enabled: Optional[bool]) -> None: ++ if enabled: ++ # Initialize less options from $SYSTEMD_LESS or provide a suitable fallback. ++ os.environ['LESS'] = os.getenv('SYSTEMD_LESS', 'FRSXMK') ++ pydoc.pager(text) ++ else: ++ print(text) ++ ++ ++def shell_join(cmd): ++ # TODO: drop in favour of shlex.join once shlex.join supports pathlib.Path. ++ return ' '.join(shlex.quote(str(x)) for x in cmd) ++ ++ ++def round_up(x, blocksize=4096): ++ return (x + blocksize - 1) // blocksize * blocksize ++ ++ ++def try_import(modname, name=None): ++ try: ++ return __import__(modname) ++ except ImportError as e: ++ raise ValueError(f'Kernel is compressed with {name or modname}, but module unavailable') from e ++ ++ ++def maybe_decompress(filename): ++ """Decompress file if compressed. Return contents.""" ++ f = open(filename, 'rb') ++ start = f.read(4) ++ f.seek(0) ++ ++ if start.startswith(b'\x7fELF'): ++ # not compressed ++ return f.read() ++ ++ if start.startswith(b'MZ'): ++ # not compressed aarch64 and riscv64 ++ return f.read() ++ ++ if start.startswith(b'\x1f\x8b'): ++ gzip = try_import('gzip') ++ return gzip.open(f).read() ++ ++ if start.startswith(b'\x28\xb5\x2f\xfd'): ++ zstd = try_import('zstd') ++ return zstd.uncompress(f.read()) ++ ++ if start.startswith(b'\x02\x21\x4c\x18'): ++ lz4 = try_import('lz4.frame', 'lz4') ++ return lz4.frame.decompress(f.read()) ++ ++ if start.startswith(b'\x04\x22\x4d\x18'): ++ print('Newer lz4 stream format detected! This may not boot!') ++ lz4 = try_import('lz4.frame', 'lz4') ++ return lz4.frame.decompress(f.read()) ++ ++ if start.startswith(b'\x89LZO'): ++ # python3-lzo is not packaged for Fedora ++ raise NotImplementedError('lzo decompression not implemented') ++ ++ if start.startswith(b'BZh'): ++ bz2 = try_import('bz2', 'bzip2') ++ return bz2.open(f).read() ++ ++ if start.startswith(b'\x5d\x00\x00'): ++ lzma = try_import('lzma') ++ return lzma.open(f).read() ++ ++ raise NotImplementedError(f'unknown file format (starts with {start})') ++ ++ ++class Uname: ++ # This class is here purely as a namespace for the functions ++ ++ VERSION_PATTERN = r'(?P[a-z0-9._-]+) \([^ )]+\) (?:#.*)' ++ ++ NOTES_PATTERN = r'^\s+Linux\s+0x[0-9a-f]+\s+OPEN\n\s+description data: (?P[0-9a-f ]+)\s*$' ++ ++ # Linux version 6.0.8-300.fc37.ppc64le (mockbuild@buildvm-ppc64le-03.iad2.fedoraproject.org) ++ # (gcc (GCC) 12.2.1 20220819 (Red Hat 12.2.1-2), GNU ld version 2.38-24.fc37) ++ # #1 SMP Fri Nov 11 14:39:11 UTC 2022 ++ TEXT_PATTERN = rb'Linux version (?P\d\.\S+) \(' ++ ++ @classmethod ++ def scrape_x86(cls, filename, opts=None): ++ # Based on https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio/-/blob/master/functions#L136 ++ # and https://www.kernel.org/doc/html/latest/x86/boot.html#the-real-mode-kernel-header ++ with open(filename, 'rb') as f: ++ f.seek(0x202) ++ magic = f.read(4) ++ if magic != b'HdrS': ++ raise ValueError('Real-Mode Kernel Header magic not found') ++ f.seek(0x20E) ++ offset = f.read(1)[0] + f.read(1)[0]*256 # Pointer to kernel version string ++ f.seek(0x200 + offset) ++ text = f.read(128) ++ text = text.split(b'\0', maxsplit=1)[0] ++ text = text.decode() ++ ++ if not (m := re.match(cls.VERSION_PATTERN, text)): ++ raise ValueError(f'Cannot parse version-host-release uname string: {text!r}') ++ return m.group('version') ++ ++ @classmethod ++ def scrape_elf(cls, filename, opts=None): ++ readelf = find_tool('readelf', opts=opts) ++ ++ cmd = [ ++ readelf, ++ '--notes', ++ filename, ++ ] ++ ++ print('+', shell_join(cmd)) ++ try: ++ notes = subprocess.check_output(cmd, stderr=subprocess.PIPE, text=True) ++ except subprocess.CalledProcessError as e: ++ raise ValueError(e.stderr.strip()) from e ++ ++ if not (m := re.search(cls.NOTES_PATTERN, notes, re.MULTILINE)): ++ raise ValueError('Cannot find Linux version note') ++ ++ text = ''.join(chr(int(c, 16)) for c in m.group('version').split()) ++ return text.rstrip('\0') ++ ++ @classmethod ++ def scrape_generic(cls, filename, opts=None): ++ # import libarchive ++ # libarchive-c fails with ++ # ArchiveError: Unrecognized archive format (errno=84, retcode=-30, archive_p=94705420454656) ++ ++ # Based on https://gitlab.archlinux.org/archlinux/mkinitcpio/mkinitcpio/-/blob/master/functions#L209 ++ ++ text = maybe_decompress(filename) ++ if not (m := re.search(cls.TEXT_PATTERN, text)): ++ raise ValueError(f'Cannot find {cls.TEXT_PATTERN!r} in {filename}') ++ ++ return m.group('version').decode() ++ ++ @classmethod ++ def scrape(cls, filename, opts=None): ++ for func in (cls.scrape_x86, cls.scrape_elf, cls.scrape_generic): ++ try: ++ version = func(filename, opts=opts) ++ print(f'Found uname version: {version}') ++ return version ++ except ValueError as e: ++ print(str(e)) ++ return None ++ ++DEFAULT_SECTIONS_TO_SHOW = { ++ '.linux' : 'binary', ++ '.initrd' : 'binary', ++ '.splash' : 'binary', ++ '.dtb' : 'binary', ++ '.cmdline' : 'text', ++ '.osrel' : 'text', ++ '.uname' : 'text', ++ '.pcrpkey' : 'text', ++ '.pcrsig' : 'text', ++ '.sbat' : 'text', ++ '.sbom' : 'binary', ++} ++ ++@dataclasses.dataclass ++class Section: ++ name: str ++ content: Optional[pathlib.Path] ++ tmpfile: Optional[IO] = None ++ measure: bool = False ++ output_mode: Optional[str] = None ++ ++ @classmethod ++ def create(cls, name, contents, **kwargs): ++ if isinstance(contents, (str, bytes)): ++ mode = 'wt' if isinstance(contents, str) else 'wb' ++ tmp = tempfile.NamedTemporaryFile(mode=mode, prefix=f'tmp{name}') ++ tmp.write(contents) ++ tmp.flush() ++ contents = pathlib.Path(tmp.name) ++ else: ++ tmp = None ++ ++ return cls(name, contents, tmpfile=tmp, **kwargs) ++ ++ @classmethod ++ def parse_input(cls, s): ++ try: ++ name, contents, *rest = s.split(':') ++ except ValueError as e: ++ raise ValueError(f'Cannot parse section spec (name or contents missing): {s!r}') from e ++ if rest: ++ raise ValueError(f'Cannot parse section spec (extraneous parameters): {s!r}') ++ ++ if contents.startswith('@'): ++ contents = pathlib.Path(contents[1:]) ++ ++ sec = cls.create(name, contents) ++ sec.check_name() ++ return sec ++ ++ @classmethod ++ def parse_output(cls, s): ++ if not (m := re.match(r'([a-zA-Z0-9_.]+):(text|binary)(?:@(.+))?', s)): ++ raise ValueError(f'Cannot parse section spec: {s!r}') ++ ++ name, ttype, out = m.groups() ++ out = pathlib.Path(out) if out else None ++ ++ return cls.create(name, out, output_mode=ttype) ++ ++ def size(self): ++ return self.content.stat().st_size ++ ++ def check_name(self): ++ # PE section names with more than 8 characters are legal, but our stub does ++ # not support them. ++ if not self.name.isascii() or not self.name.isprintable(): ++ raise ValueError(f'Bad section name: {self.name!r}') ++ if len(self.name) > 8: ++ raise ValueError(f'Section name too long: {self.name!r}') ++ ++ ++@dataclasses.dataclass ++class UKI: ++ executable: list[Union[pathlib.Path, str]] ++ sections: list[Section] = dataclasses.field(default_factory=list, init=False) ++ ++ def add_section(self, section): ++ if section.name in [s.name for s in self.sections]: ++ raise ValueError(f'Duplicate section {section.name}') ++ ++ self.sections += [section] ++ ++ ++def parse_banks(s): ++ banks = re.split(r',|\s+', s) ++ # TODO: do some sanity checking here ++ return banks ++ ++ ++KNOWN_PHASES = ( ++ 'enter-initrd', ++ 'leave-initrd', ++ 'sysinit', ++ 'ready', ++ 'shutdown', ++ 'final', ++) ++ ++def parse_phase_paths(s): ++ # Split on commas or whitespace here. Commas might be hard to parse visually. ++ paths = re.split(r',|\s+', s) ++ ++ for path in paths: ++ for phase in path.split(':'): ++ if phase not in KNOWN_PHASES: ++ raise argparse.ArgumentTypeError(f'Unknown boot phase {phase!r} ({path=})') ++ ++ return paths ++ ++ ++def check_splash(filename): ++ if filename is None: ++ return ++ ++ # import is delayed, to avoid import when the splash image is not used ++ try: ++ from PIL import Image ++ except ImportError: ++ return ++ ++ img = Image.open(filename, formats=['BMP']) ++ print(f'Splash image {filename} is {img.width}×{img.height} pixels') ++ ++ ++def check_inputs(opts): ++ for name, value in vars(opts).items(): ++ if name in {'output', 'tools'}: ++ continue ++ ++ if isinstance(value, pathlib.Path): ++ # Open file to check that we can read it, or generate an exception ++ value.open().close() ++ elif isinstance(value, list): ++ for item in value: ++ if isinstance(item, pathlib.Path): ++ item.open().close() ++ ++ check_splash(opts.splash) ++ ++ ++def check_cert_and_keys_nonexistent(opts): ++ # Raise if any of the keys and certs are found on disk ++ paths = itertools.chain( ++ (opts.sb_key, opts.sb_cert), ++ *((priv_key, pub_key) ++ for priv_key, pub_key, _ in key_path_groups(opts))) ++ for path in paths: ++ if path and path.exists(): ++ raise ValueError(f'{path} is present') ++ ++ ++def find_tool(name, fallback=None, opts=None): ++ if opts and opts.tools: ++ for d in opts.tools: ++ tool = d / name ++ if tool.exists(): ++ return tool ++ ++ if shutil.which(name) is not None: ++ return name ++ ++ if fallback is None: ++ print(f"Tool {name} not installed!") ++ ++ return fallback ++ ++def combine_signatures(pcrsigs): ++ combined = collections.defaultdict(list) ++ for pcrsig in pcrsigs: ++ for bank, sigs in pcrsig.items(): ++ for sig in sigs: ++ if sig not in combined[bank]: ++ combined[bank] += [sig] ++ return json.dumps(combined) ++ ++ ++def key_path_groups(opts): ++ if not opts.pcr_private_keys: ++ return ++ ++ n_priv = len(opts.pcr_private_keys) ++ pub_keys = opts.pcr_public_keys or [None] * n_priv ++ pp_groups = opts.phase_path_groups or [None] * n_priv ++ ++ yield from zip(opts.pcr_private_keys, ++ pub_keys, ++ pp_groups) ++ ++ ++def call_systemd_measure(uki, linux, opts): ++ measure_tool = find_tool('systemd-measure', ++ '/usr/lib/systemd/systemd-measure', ++ opts=opts) ++ ++ banks = opts.pcr_banks or () ++ ++ # PCR measurement ++ ++ if opts.measure: ++ pp_groups = opts.phase_path_groups or [] ++ ++ cmd = [ ++ measure_tool, ++ 'calculate', ++ f'--linux={linux}', ++ *(f"--{s.name.removeprefix('.')}={s.content}" ++ for s in uki.sections ++ if s.measure), ++ *(f'--bank={bank}' ++ for bank in banks), ++ # For measurement, the keys are not relevant, so we can lump all the phase paths ++ # into one call to systemd-measure calculate. ++ *(f'--phase={phase_path}' ++ for phase_path in itertools.chain.from_iterable(pp_groups)), ++ ] ++ ++ print('+', shell_join(cmd)) ++ subprocess.check_call(cmd) ++ ++ # PCR signing ++ ++ if opts.pcr_private_keys: ++ pcrsigs = [] ++ ++ cmd = [ ++ measure_tool, ++ 'sign', ++ f'--linux={linux}', ++ *(f"--{s.name.removeprefix('.')}={s.content}" ++ for s in uki.sections ++ if s.measure), ++ *(f'--bank={bank}' ++ for bank in banks), ++ ] ++ ++ for priv_key, pub_key, group in key_path_groups(opts): ++ extra = [f'--private-key={priv_key}'] ++ if pub_key: ++ extra += [f'--public-key={pub_key}'] ++ extra += [f'--phase={phase_path}' for phase_path in group or ()] ++ ++ print('+', shell_join(cmd + extra)) ++ pcrsig = subprocess.check_output(cmd + extra, text=True) ++ pcrsig = json.loads(pcrsig) ++ pcrsigs += [pcrsig] ++ ++ combined = combine_signatures(pcrsigs) ++ uki.add_section(Section.create('.pcrsig', combined)) ++ ++ ++def join_initrds(initrds): ++ if not initrds: ++ return None ++ if len(initrds) == 1: ++ return initrds[0] ++ ++ seq = [] ++ for file in initrds: ++ initrd = file.read_bytes() ++ n = len(initrd) ++ padding = b'\0' * (round_up(n, 4) - n) # pad to 32 bit alignment ++ seq += [initrd, padding] ++ ++ return b''.join(seq) ++ ++ ++def pairwise(iterable): ++ a, b = itertools.tee(iterable) ++ next(b, None) ++ return zip(a, b) ++ ++ ++class PEError(Exception): ++ pass ++ ++ ++def pe_add_sections(uki: UKI, output: str): ++ pe = pefile.PE(uki.executable, fast_load=True) ++ ++ # Old stubs do not have the symbol/string table stripped, even though image files should not have one. ++ if symbol_table := pe.FILE_HEADER.PointerToSymbolTable: ++ symbol_table_size = 18 * pe.FILE_HEADER.NumberOfSymbols ++ if string_table_size := pe.get_dword_from_offset(symbol_table + symbol_table_size): ++ symbol_table_size += string_table_size ++ ++ # Let's be safe and only strip it if it's at the end of the file. ++ if symbol_table + symbol_table_size == len(pe.__data__): ++ pe.__data__ = pe.__data__[:symbol_table] ++ pe.FILE_HEADER.PointerToSymbolTable = 0 ++ pe.FILE_HEADER.NumberOfSymbols = 0 ++ pe.FILE_HEADER.IMAGE_FILE_LOCAL_SYMS_STRIPPED = True ++ ++ # Old stubs might have been stripped, leading to unaligned raw data values, so let's fix them up here. ++ # pylint thinks that Structure doesn't have various members that it has… ++ # pylint: disable=no-member ++ ++ for i, section in enumerate(pe.sections): ++ oldp = section.PointerToRawData ++ oldsz = section.SizeOfRawData ++ section.PointerToRawData = round_up(oldp, pe.OPTIONAL_HEADER.FileAlignment) ++ section.SizeOfRawData = round_up(oldsz, pe.OPTIONAL_HEADER.FileAlignment) ++ padp = section.PointerToRawData - oldp ++ padsz = section.SizeOfRawData - oldsz ++ ++ for later_section in pe.sections[i+1:]: ++ later_section.PointerToRawData += padp + padsz ++ ++ pe.__data__ = pe.__data__[:oldp] + bytes(padp) + pe.__data__[oldp:oldp+oldsz] + bytes(padsz) + pe.__data__[oldp+oldsz:] ++ ++ # We might not have any space to add new sections. Let's try our best to make some space by padding the ++ # SizeOfHeaders to a multiple of the file alignment. This is safe because the first section's data starts ++ # at a multiple of the file alignment, so all space before that is unused. ++ pe.OPTIONAL_HEADER.SizeOfHeaders = round_up(pe.OPTIONAL_HEADER.SizeOfHeaders, pe.OPTIONAL_HEADER.FileAlignment) ++ pe = pefile.PE(data=pe.write(), fast_load=True) ++ ++ warnings = pe.get_warnings() ++ if warnings: ++ raise PEError(f'pefile warnings treated as errors: {warnings}') ++ ++ security = pe.OPTIONAL_HEADER.DATA_DIRECTORY[pefile.DIRECTORY_ENTRY['IMAGE_DIRECTORY_ENTRY_SECURITY']] ++ if security.VirtualAddress != 0: ++ # We could strip the signatures, but why would anyone sign the stub? ++ raise PEError('Stub image is signed, refusing.') ++ ++ for section in uki.sections: ++ new_section = pefile.SectionStructure(pe.__IMAGE_SECTION_HEADER_format__, pe=pe) ++ new_section.__unpack__(b'\0' * new_section.sizeof()) ++ ++ offset = pe.sections[-1].get_file_offset() + new_section.sizeof() ++ if offset + new_section.sizeof() > pe.OPTIONAL_HEADER.SizeOfHeaders: ++ raise PEError(f'Not enough header space to add section {section.name}.') ++ ++ assert section.content ++ data = section.content.read_bytes() ++ ++ new_section.set_file_offset(offset) ++ new_section.Name = section.name.encode() ++ new_section.Misc_VirtualSize = len(data) ++ # Non-stripped stubs might still have an unaligned symbol table at the end, making their size ++ # unaligned, so we make sure to explicitly pad the pointer to new sections to an aligned offset. ++ new_section.PointerToRawData = round_up(len(pe.__data__), pe.OPTIONAL_HEADER.FileAlignment) ++ new_section.SizeOfRawData = round_up(len(data), pe.OPTIONAL_HEADER.FileAlignment) ++ new_section.VirtualAddress = round_up( ++ pe.sections[-1].VirtualAddress + pe.sections[-1].Misc_VirtualSize, ++ pe.OPTIONAL_HEADER.SectionAlignment, ++ ) ++ ++ new_section.IMAGE_SCN_MEM_READ = True ++ if section.name == '.linux': ++ # Old kernels that use EFI handover protocol will be executed inline. ++ new_section.IMAGE_SCN_CNT_CODE = True ++ else: ++ new_section.IMAGE_SCN_CNT_INITIALIZED_DATA = True ++ ++ # Special case, mostly for .sbat: the stub will already have a .sbat section, but we want to append ++ # the one from the kernel to it. It should be small enough to fit in the existing section, so just ++ # swap the data. ++ for i, s in enumerate(pe.sections): ++ if s.Name.rstrip(b"\x00").decode() == section.name: ++ if new_section.Misc_VirtualSize > s.SizeOfRawData: ++ raise PEError(f'Not enough space in existing section {section.name} to append new data.') ++ ++ padding = bytes(new_section.SizeOfRawData - new_section.Misc_VirtualSize) ++ pe.__data__ = pe.__data__[:s.PointerToRawData] + data + padding + pe.__data__[pe.sections[i+1].PointerToRawData:] ++ s.SizeOfRawData = new_section.SizeOfRawData ++ s.Misc_VirtualSize = new_section.Misc_VirtualSize ++ break ++ else: ++ pe.__data__ = pe.__data__[:] + bytes(new_section.PointerToRawData - len(pe.__data__)) + data + bytes(new_section.SizeOfRawData - len(data)) ++ ++ pe.FILE_HEADER.NumberOfSections += 1 ++ pe.OPTIONAL_HEADER.SizeOfInitializedData += new_section.Misc_VirtualSize ++ pe.__structures__.append(new_section) ++ pe.sections.append(new_section) ++ ++ pe.OPTIONAL_HEADER.CheckSum = 0 ++ pe.OPTIONAL_HEADER.SizeOfImage = round_up( ++ pe.sections[-1].VirtualAddress + pe.sections[-1].Misc_VirtualSize, ++ pe.OPTIONAL_HEADER.SectionAlignment, ++ ) ++ ++ pe.write(output) ++ ++def merge_sbat(input_pe: [pathlib.Path], input_text: [str]) -> str: ++ sbat = [] ++ ++ for f in input_pe: ++ try: ++ pe = pefile.PE(f, fast_load=True) ++ except pefile.PEFormatError: ++ print(f"{f} is not a valid PE file, not extracting SBAT section.") ++ continue ++ ++ for section in pe.sections: ++ if section.Name.rstrip(b"\x00").decode() == ".sbat": ++ split = section.get_data().rstrip(b"\x00").decode().splitlines() ++ if not split[0].startswith('sbat,'): ++ print(f"{f} does not contain a valid SBAT section, skipping.") ++ continue ++ # Filter out the sbat line, we'll add it back later, there needs to be only one and it ++ # needs to be first. ++ sbat += split[1:] ++ ++ for t in input_text: ++ if t.startswith('@'): ++ t = pathlib.Path(t[1:]).read_text() ++ split = t.splitlines() ++ if not split[0].startswith('sbat,'): ++ print(f"{t} does not contain a valid SBAT section, skipping.") ++ continue ++ sbat += split[1:] ++ ++ return 'sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md\n' + '\n'.join(sbat) + "\n\x00" ++ ++def signer_sign(cmd): ++ print('+', shell_join(cmd)) ++ subprocess.check_call(cmd) ++ ++def find_sbsign(opts=None): ++ return find_tool('sbsign', opts=opts) ++ ++def sbsign_sign(sbsign_tool, input_f, output_f, opts=None): ++ sign_invocation = [ ++ sbsign_tool, ++ '--key', opts.sb_key, ++ '--cert', opts.sb_cert, ++ input_f, ++ '--output', output_f, ++ ] ++ if opts.signing_engine is not None: ++ sign_invocation += ['--engine', opts.signing_engine] ++ signer_sign(sign_invocation) ++ ++def find_pesign(opts=None): ++ return find_tool('pesign', opts=opts) ++ ++def pesign_sign(pesign_tool, input_f, output_f, opts=None): ++ sign_invocation = [ ++ pesign_tool, '-s', '--force', ++ '-n', opts.sb_certdir, ++ '-c', opts.sb_cert_name, ++ '-i', input_f, ++ '-o', output_f, ++ ] ++ signer_sign(sign_invocation) ++ ++SBVERIFY = { ++ 'name': 'sbverify', ++ 'option': '--list', ++ 'output': 'No signature table present', ++} ++ ++PESIGCHECK = { ++ 'name': 'pesign', ++ 'option': '-i', ++ 'output': 'No signatures found.', ++ 'flags': '-S' ++} ++ ++def verify(tool, opts): ++ verify_tool = find_tool(tool['name'], opts=opts) ++ cmd = [ ++ verify_tool, ++ tool['option'], ++ opts.linux, ++ ] ++ if 'flags' in tool: ++ cmd.append(tool['flags']) ++ ++ print('+', shell_join(cmd)) ++ info = subprocess.check_output(cmd, text=True) ++ ++ return tool['output'] in info ++ ++def make_uki(opts): ++ # kernel payload signing ++ ++ sign_tool = None ++ sign_args_present = opts.sb_key or opts.sb_cert_name ++ sign_kernel = opts.sign_kernel ++ sign = None ++ linux = opts.linux ++ ++ if sign_args_present: ++ if opts.signtool == 'sbsign': ++ sign_tool = find_sbsign(opts=opts) ++ sign = sbsign_sign ++ verify_tool = SBVERIFY ++ else: ++ sign_tool = find_pesign(opts=opts) ++ sign = pesign_sign ++ verify_tool = PESIGCHECK ++ ++ if sign_tool is None: ++ raise ValueError(f'{opts.signtool}, required for signing, is not installed') ++ ++ if sign_kernel is None and opts.linux is not None: ++ # figure out if we should sign the kernel ++ sign_kernel = verify(verify_tool, opts) ++ ++ if sign_kernel: ++ linux_signed = tempfile.NamedTemporaryFile(prefix='linux-signed') ++ linux = pathlib.Path(linux_signed.name) ++ sign(sign_tool, opts.linux, linux, opts=opts) ++ ++ if opts.uname is None and opts.linux is not None: ++ print('Kernel version not specified, starting autodetection 😖.') ++ opts.uname = Uname.scrape(opts.linux, opts=opts) ++ ++ uki = UKI(opts.stub) ++ initrd = join_initrds(opts.initrd) ++ ++ pcrpkey = opts.pcrpkey ++ if pcrpkey is None: ++ if opts.pcr_public_keys and len(opts.pcr_public_keys) == 1: ++ pcrpkey = opts.pcr_public_keys[0] ++ elif opts.pcr_private_keys and len(opts.pcr_private_keys) == 1: ++ import cryptography.hazmat.primitives.serialization as serialization ++ privkey = serialization.load_pem_private_key(opts.pcr_private_keys[0].read_bytes(), password=None) ++ pcrpkey = privkey.public_key().public_bytes( ++ encoding=serialization.Encoding.PEM, ++ format=serialization.PublicFormat.SubjectPublicKeyInfo, ++ ) ++ ++ sections = [ ++ # name, content, measure? ++ ('.osrel', opts.os_release, True ), ++ ('.cmdline', opts.cmdline, True ), ++ ('.dtb', opts.devicetree, True ), ++ ('.uname', opts.uname, True ), ++ ('.splash', opts.splash, True ), ++ ('.pcrpkey', pcrpkey, True ), ++ ('.initrd', initrd, True ), ++ ++ # linux shall be last to leave breathing room for decompression. ++ # We'll add it later. ++ ] ++ ++ for name, content, measure in sections: ++ if content: ++ uki.add_section(Section.create(name, content, measure=measure)) ++ ++ # systemd-measure doesn't know about those extra sections ++ for section in opts.sections: ++ uki.add_section(section) ++ ++ if linux is not None: ++ # Merge the .sbat sections from stub, kernel and parameter, so that revocation can be done on either. ++ uki.add_section(Section.create('.sbat', merge_sbat([opts.stub, linux], opts.sbat), measure=True)) ++ else: ++ # Addons don't use the stub so we add SBAT manually ++ if not opts.sbat: ++ opts.sbat = ["""sbat,1,SBAT Version,sbat,1,https://github.com/rhboot/shim/blob/main/SBAT.md ++uki,1,UKI,uki,1,https://www.freedesktop.org/software/systemd/man/systemd-stub.html ++"""] ++ uki.add_section(Section.create('.sbat', merge_sbat([], opts.sbat), measure=False)) ++ ++ # PCR measurement and signing ++ ++ # We pass in the contents for .linux separately because we need them to do the measurement but can't add ++ # the section yet because we want .linux to be the last section. Make sure any other sections are added ++ # before this function is called. ++ call_systemd_measure(uki, linux, opts=opts) ++ ++ # UKI creation ++ ++ if linux is not None: ++ uki.add_section(Section.create('.linux', linux, measure=True)) ++ ++ if sign_args_present: ++ unsigned = tempfile.NamedTemporaryFile(prefix='uki') ++ unsigned_output = unsigned.name ++ else: ++ unsigned_output = opts.output ++ ++ pe_add_sections(uki, unsigned_output) ++ ++ # UKI signing ++ ++ if sign_args_present: ++ assert sign ++ sign(sign_tool, unsigned_output, opts.output, opts=opts) ++ ++ # We end up with no executable bits, let's reapply them ++ os.umask(umask := os.umask(0)) ++ os.chmod(opts.output, 0o777 & ~umask) ++ ++ print(f"Wrote {'signed' if sign_args_present else 'unsigned'} {opts.output}") ++ ++ ++ONE_DAY = datetime.timedelta(1, 0, 0) ++ ++ ++@contextlib.contextmanager ++def temporary_umask(mask: int): ++ # Drop bits from umask ++ old = os.umask(0) ++ os.umask(old | mask) ++ try: ++ yield ++ finally: ++ os.umask(old) ++ ++ ++def generate_key_cert_pair( ++ common_name: str, ++ valid_days: int, ++ keylength: int = 2048, ++) -> tuple[bytes]: ++ ++ from cryptography import x509 ++ from cryptography.hazmat.primitives import serialization, hashes ++ from cryptography.hazmat.primitives.asymmetric import rsa ++ ++ # We use a keylength of 2048 bits. That is what Microsoft documents as ++ # supported/expected: ++ # https://learn.microsoft.com/en-us/windows-hardware/manufacture/desktop/windows-secure-boot-key-creation-and-management-guidance?view=windows-11#12-public-key-cryptography ++ ++ now = datetime.datetime.utcnow() ++ ++ key = rsa.generate_private_key( ++ public_exponent=65537, ++ key_size=keylength, ++ ) ++ cert = x509.CertificateBuilder( ++ ).subject_name( ++ x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, common_name)]) ++ ).issuer_name( ++ x509.Name([x509.NameAttribute(x509.oid.NameOID.COMMON_NAME, common_name)]) ++ ).not_valid_before( ++ now, ++ ).not_valid_after( ++ now + ONE_DAY * valid_days ++ ).serial_number( ++ x509.random_serial_number() ++ ).public_key( ++ key.public_key() ++ ).add_extension( ++ x509.BasicConstraints(ca=False, path_length=None), ++ critical=True, ++ ).sign( ++ private_key=key, ++ algorithm=hashes.SHA256(), ++ ) ++ ++ cert_pem = cert.public_bytes( ++ encoding=serialization.Encoding.PEM, ++ ) ++ key_pem = key.private_bytes( ++ encoding=serialization.Encoding.PEM, ++ format=serialization.PrivateFormat.TraditionalOpenSSL, ++ encryption_algorithm=serialization.NoEncryption(), ++ ) ++ ++ return key_pem, cert_pem ++ ++ ++def generate_priv_pub_key_pair(keylength : int = 2048) -> tuple[bytes]: ++ from cryptography.hazmat.primitives import serialization ++ from cryptography.hazmat.primitives.asymmetric import rsa ++ ++ key = rsa.generate_private_key( ++ public_exponent=65537, ++ key_size=keylength, ++ ) ++ priv_key_pem = key.private_bytes( ++ encoding=serialization.Encoding.PEM, ++ format=serialization.PrivateFormat.TraditionalOpenSSL, ++ encryption_algorithm=serialization.NoEncryption(), ++ ) ++ pub_key_pem = key.public_key().public_bytes( ++ encoding=serialization.Encoding.PEM, ++ format=serialization.PublicFormat.SubjectPublicKeyInfo, ++ ) ++ ++ return priv_key_pem, pub_key_pem ++ ++ ++def generate_keys(opts): ++ # This will generate keys and certificates and write them to the paths that ++ # are specified as input paths. ++ if opts.sb_key or opts.sb_cert: ++ fqdn = socket.getfqdn() ++ cn = f'SecureBoot signing key on host {fqdn}' ++ key_pem, cert_pem = generate_key_cert_pair( ++ common_name=cn, ++ valid_days=opts.sb_cert_validity, ++ ) ++ print(f'Writing SecureBoot private key to {opts.sb_key}') ++ with temporary_umask(0o077): ++ opts.sb_key.write_bytes(key_pem) ++ print(f'Writing SecureBoot certificate to {opts.sb_cert}') ++ opts.sb_cert.write_bytes(cert_pem) ++ ++ for priv_key, pub_key, _ in key_path_groups(opts): ++ priv_key_pem, pub_key_pem = generate_priv_pub_key_pair() ++ ++ print(f'Writing private key for PCR signing to {priv_key}') ++ with temporary_umask(0o077): ++ priv_key.write_bytes(priv_key_pem) ++ if pub_key: ++ print(f'Writing public key for PCR signing to {pub_key}') ++ pub_key.write_bytes(pub_key_pem) ++ ++ ++def inspect_section(opts, section): ++ name = section.Name.rstrip(b"\x00").decode() ++ ++ # find the config for this section in opts and whether to show it ++ config = opts.sections_by_name.get(name, None) ++ show = (config or ++ opts.all or ++ (name in DEFAULT_SECTIONS_TO_SHOW and not opts.sections)) ++ if not show: ++ return name, None ++ ++ ttype = config.output_mode if config else DEFAULT_SECTIONS_TO_SHOW.get(name, 'binary') ++ ++ size = section.Misc_VirtualSize ++ # TODO: Use ignore_padding once we can depend on a newer version of pefile ++ data = section.get_data(length=size) ++ digest = sha256(data).hexdigest() ++ ++ struct = { ++ 'size' : size, ++ 'sha256' : digest, ++ } ++ ++ if ttype == 'text': ++ try: ++ struct['text'] = data.decode() ++ except UnicodeDecodeError as e: ++ print(f"Section {name!r} is not valid text: {e}") ++ struct['text'] = '(not valid UTF-8)' ++ ++ if config and config.content: ++ assert isinstance(config.content, pathlib.Path) ++ config.content.write_bytes(data) ++ ++ if opts.json == 'off': ++ print(f"{name}:\n size: {size} bytes\n sha256: {digest}") ++ if ttype == 'text': ++ text = textwrap.indent(struct['text'].rstrip(), ' ' * 4) ++ print(f" text:\n{text}") ++ ++ return name, struct ++ ++ ++def inspect_sections(opts): ++ indent = 4 if opts.json == 'pretty' else None ++ ++ for file in opts.files: ++ pe = pefile.PE(file, fast_load=True) ++ gen = (inspect_section(opts, section) for section in pe.sections) ++ descs = {key:val for (key, val) in gen if val} ++ if opts.json != 'off': ++ json.dump(descs, sys.stdout, indent=indent) ++ ++ ++@dataclasses.dataclass(frozen=True) ++class ConfigItem: ++ @staticmethod ++ def config_list_prepend( ++ namespace: argparse.Namespace, ++ group: Optional[str], ++ dest: str, ++ value: Any, ++ ) -> None: ++ "Prepend value to namespace." ++ ++ assert not group ++ ++ old = getattr(namespace, dest, []) ++ if old is None: ++ old = [] ++ setattr(namespace, dest, value + old) ++ ++ @staticmethod ++ def config_set_if_unset( ++ namespace: argparse.Namespace, ++ group: Optional[str], ++ dest: str, ++ value: Any, ++ ) -> None: ++ "Set namespace. to value only if it was None" ++ ++ assert not group ++ ++ if getattr(namespace, dest) is None: ++ setattr(namespace, dest, value) ++ ++ @staticmethod ++ def config_set( ++ namespace: argparse.Namespace, ++ group: Optional[str], ++ dest: str, ++ value: Any, ++ ) -> None: ++ "Set namespace. to value only if it was None" ++ ++ assert not group ++ ++ setattr(namespace, dest, value) ++ ++ @staticmethod ++ def config_set_group( ++ namespace: argparse.Namespace, ++ group: Optional[str], ++ dest: str, ++ value: Any, ++ ) -> None: ++ "Set namespace.[idx] to value, with idx derived from group" ++ ++ # pylint: disable=protected-access ++ if group not in namespace._groups: ++ namespace._groups += [group] ++ idx = namespace._groups.index(group) ++ ++ old = getattr(namespace, dest, None) ++ if old is None: ++ old = [] ++ setattr(namespace, dest, ++ old + ([None] * (idx - len(old))) + [value]) ++ ++ @staticmethod ++ def parse_boolean(s: str) -> bool: ++ "Parse 1/true/yes/y/t/on as true and 0/false/no/n/f/off/None as false" ++ s_l = s.lower() ++ if s_l in {'1', 'true', 'yes', 'y', 't', 'on'}: ++ return True ++ if s_l in {'0', 'false', 'no', 'n', 'f', 'off'}: ++ return False ++ raise ValueError('f"Invalid boolean literal: {s!r}') ++ ++ # arguments for argparse.ArgumentParser.add_argument() ++ name: Union[str, tuple[str, str]] ++ dest: Optional[str] = None ++ metavar: Optional[str] = None ++ type: Optional[Callable] = None ++ nargs: Optional[str] = None ++ action: Optional[Union[str, Callable]] = None ++ default: Any = None ++ version: Optional[str] = None ++ choices: Optional[tuple[str, ...]] = None ++ const: Optional[Any] = None ++ help: Optional[str] = None ++ ++ # metadata for config file parsing ++ config_key: Optional[str] = None ++ config_push: Callable[[argparse.Namespace, Optional[str], str, Any], None] = \ ++ config_set_if_unset ++ ++ def _names(self) -> tuple[str, ...]: ++ return self.name if isinstance(self.name, tuple) else (self.name,) ++ ++ def argparse_dest(self) -> str: ++ # It'd be nice if argparse exported this, but I don't see that in the API ++ if self.dest: ++ return self.dest ++ return self._names()[0].lstrip('-').replace('-', '_') ++ ++ def add_to(self, parser: argparse.ArgumentParser): ++ kwargs = { key:val ++ for key in dataclasses.asdict(self) ++ if (key not in ('name', 'config_key', 'config_push') and ++ (val := getattr(self, key)) is not None) } ++ args = self._names() ++ parser.add_argument(*args, **kwargs) ++ ++ def apply_config(self, namespace, section, group, key, value) -> None: ++ assert f'{section}/{key}' == self.config_key ++ dest = self.argparse_dest() ++ ++ conv: Callable[[str], Any] ++ if self.action == argparse.BooleanOptionalAction: ++ # We need to handle this case separately: the options are called ++ # --foo and --no-foo, and no argument is parsed. But in the config ++ # file, we have Foo=yes or Foo=no. ++ conv = self.parse_boolean ++ elif self.type: ++ conv = self.type ++ else: ++ conv = lambda s:s ++ ++ # This is a bit ugly, but --initrd is the only option which is specified ++ # with multiple args on the command line and a space-separated list in the ++ # config file. ++ if self.name == '--initrd': ++ value = [conv(v) for v in value.split()] ++ else: ++ value = conv(value) ++ ++ self.config_push(namespace, group, dest, value) ++ ++ def config_example(self) -> tuple[Optional[str], Optional[str], Optional[str]]: ++ if not self.config_key: ++ return None, None, None ++ section_name, key = self.config_key.split('/', 1) ++ if section_name.endswith(':'): ++ section_name += 'NAME' ++ if self.choices: ++ value = '|'.join(self.choices) ++ else: ++ value = self.metavar or self.argparse_dest().upper() ++ return (section_name, key, value) ++ ++ ++VERBS = ('build', 'genkey', 'inspect') ++ ++CONFIG_ITEMS = [ ++ ConfigItem( ++ 'positional', ++ metavar = 'VERB', ++ nargs = '*', ++ help = argparse.SUPPRESS, ++ ), ++ ++ ConfigItem( ++ '--version', ++ action = 'version', ++ version = f'ukify {__version__}', ++ ), ++ ++ ConfigItem( ++ '--summary', ++ help = 'print parsed config and exit', ++ action = 'store_true', ++ ), ++ ++ ConfigItem( ++ '--linux', ++ type = pathlib.Path, ++ help = 'vmlinuz file [.linux section]', ++ config_key = 'UKI/Linux', ++ ), ++ ++ ConfigItem( ++ '--initrd', ++ metavar = 'INITRD', ++ type = pathlib.Path, ++ action = 'append', ++ help = 'initrd file [part of .initrd section]', ++ config_key = 'UKI/Initrd', ++ config_push = ConfigItem.config_list_prepend, ++ ), ++ ++ ConfigItem( ++ ('--config', '-c'), ++ metavar = 'PATH', ++ type = pathlib.Path, ++ help = 'configuration file', ++ ), ++ ++ ConfigItem( ++ '--cmdline', ++ metavar = 'TEXT|@PATH', ++ help = 'kernel command line [.cmdline section]', ++ config_key = 'UKI/Cmdline', ++ ), ++ ++ ConfigItem( ++ '--os-release', ++ metavar = 'TEXT|@PATH', ++ help = 'path to os-release file [.osrel section]', ++ config_key = 'UKI/OSRelease', ++ ), ++ ++ ConfigItem( ++ '--devicetree', ++ metavar = 'PATH', ++ type = pathlib.Path, ++ help = 'Device Tree file [.dtb section]', ++ config_key = 'UKI/DeviceTree', ++ ), ++ ConfigItem( ++ '--splash', ++ metavar = 'BMP', ++ type = pathlib.Path, ++ help = 'splash image bitmap file [.splash section]', ++ config_key = 'UKI/Splash', ++ ), ++ ConfigItem( ++ '--pcrpkey', ++ metavar = 'KEY', ++ type = pathlib.Path, ++ help = 'embedded public key to seal secrets to [.pcrpkey section]', ++ config_key = 'UKI/PCRPKey', ++ ), ++ ConfigItem( ++ '--uname', ++ metavar='VERSION', ++ help='"uname -r" information [.uname section]', ++ config_key = 'UKI/Uname', ++ ), ++ ++ ConfigItem( ++ '--efi-arch', ++ metavar = 'ARCH', ++ choices = ('ia32', 'x64', 'arm', 'aa64', 'riscv64'), ++ help = 'target EFI architecture', ++ config_key = 'UKI/EFIArch', ++ ), ++ ++ ConfigItem( ++ '--stub', ++ type = pathlib.Path, ++ help = 'path to the sd-stub file [.text,.data,… sections]', ++ config_key = 'UKI/Stub', ++ ), ++ ++ ConfigItem( ++ '--sbat', ++ metavar = 'TEXT|@PATH', ++ help = 'SBAT policy [.sbat section]', ++ default = [], ++ action = 'append', ++ config_key = 'UKI/SBAT', ++ ), ++ ++ ConfigItem( ++ '--section', ++ dest = 'sections', ++ metavar = 'NAME:TEXT|@PATH', ++ action = 'append', ++ default = [], ++ help = 'section as name and contents [NAME section] or section to print', ++ ), ++ ++ ConfigItem( ++ '--pcr-banks', ++ metavar = 'BANK…', ++ type = parse_banks, ++ config_key = 'UKI/PCRBanks', ++ ), ++ ++ ConfigItem( ++ '--signing-engine', ++ metavar = 'ENGINE', ++ help = 'OpenSSL engine to use for signing', ++ config_key = 'UKI/SigningEngine', ++ ), ++ ConfigItem( ++ '--signtool', ++ choices = ('sbsign', 'pesign'), ++ dest = 'signtool', ++ help = 'whether to use sbsign or pesign. It will also be inferred by the other \ ++ parameters given: when using --secureboot-{private-key/certificate}, sbsign \ ++ will be used, otherwise pesign will be used', ++ config_key = 'UKI/SecureBootSigningTool', ++ ), ++ ConfigItem( ++ '--secureboot-private-key', ++ dest = 'sb_key', ++ help = 'required by --signtool=sbsign. Path to key file or engine-specific designation for SB signing', ++ config_key = 'UKI/SecureBootPrivateKey', ++ ), ++ ConfigItem( ++ '--secureboot-certificate', ++ dest = 'sb_cert', ++ help = 'required by --signtool=sbsign. sbsign needs a path to certificate file or engine-specific designation for SB signing', ++ config_key = 'UKI/SecureBootCertificate', ++ ), ++ ConfigItem( ++ '--secureboot-certificate-dir', ++ dest = 'sb_certdir', ++ default = '/etc/pki/pesign', ++ help = 'required by --signtool=pesign. Path to nss certificate database directory for PE signing. Default is /etc/pki/pesign', ++ config_key = 'UKI/SecureBootCertificateDir', ++ config_push = ConfigItem.config_set ++ ), ++ ConfigItem( ++ '--secureboot-certificate-name', ++ dest = 'sb_cert_name', ++ help = 'required by --signtool=pesign. pesign needs a certificate nickname of nss certificate database entry to use for PE signing', ++ config_key = 'UKI/SecureBootCertificateName', ++ ), ++ ConfigItem( ++ '--secureboot-certificate-validity', ++ metavar = 'DAYS', ++ dest = 'sb_cert_validity', ++ default = 365 * 10, ++ help = "period of validity (in days) for a certificate created by 'genkey'", ++ config_key = 'UKI/SecureBootCertificateValidity', ++ config_push = ConfigItem.config_set ++ ), ++ ++ ConfigItem( ++ '--sign-kernel', ++ action = argparse.BooleanOptionalAction, ++ help = 'Sign the embedded kernel', ++ config_key = 'UKI/SignKernel', ++ ), ++ ++ ConfigItem( ++ '--pcr-private-key', ++ dest = 'pcr_private_keys', ++ metavar = 'PATH', ++ type = pathlib.Path, ++ action = 'append', ++ help = 'private part of the keypair for signing PCR signatures', ++ config_key = 'PCRSignature:/PCRPrivateKey', ++ config_push = ConfigItem.config_set_group, ++ ), ++ ConfigItem( ++ '--pcr-public-key', ++ dest = 'pcr_public_keys', ++ metavar = 'PATH', ++ type = pathlib.Path, ++ action = 'append', ++ help = 'public part of the keypair for signing PCR signatures', ++ config_key = 'PCRSignature:/PCRPublicKey', ++ config_push = ConfigItem.config_set_group, ++ ), ++ ConfigItem( ++ '--phases', ++ dest = 'phase_path_groups', ++ metavar = 'PHASE-PATH…', ++ type = parse_phase_paths, ++ action = 'append', ++ help = 'phase-paths to create signatures for', ++ config_key = 'PCRSignature:/Phases', ++ config_push = ConfigItem.config_set_group, ++ ), ++ ++ ConfigItem( ++ '--tools', ++ type = pathlib.Path, ++ action = 'append', ++ help = 'Directories to search for tools (systemd-measure, …)', ++ ), ++ ++ ConfigItem( ++ ('--output', '-o'), ++ type = pathlib.Path, ++ help = 'output file path', ++ ), ++ ++ ConfigItem( ++ '--measure', ++ action = argparse.BooleanOptionalAction, ++ help = 'print systemd-measure output for the UKI', ++ ), ++ ++ ConfigItem( ++ '--json', ++ choices = ('pretty', 'short', 'off'), ++ default = 'off', ++ help = 'generate JSON output', ++ ), ++ ConfigItem( ++ '-j', ++ dest='json', ++ action='store_const', ++ const='pretty', ++ help='equivalent to --json=pretty', ++ ), ++ ++ ConfigItem( ++ '--all', ++ help = 'print all sections', ++ action = 'store_true', ++ ), ++] ++ ++CONFIGFILE_ITEMS = { item.config_key:item ++ for item in CONFIG_ITEMS ++ if item.config_key } ++ ++ ++def apply_config(namespace, filename=None): ++ if filename is None: ++ if namespace.config: ++ # Config set by the user, use that. ++ filename = namespace.config ++ print(f'Using config file: {filename}') ++ else: ++ # Try to look for a config file then use the first one found. ++ for config_dir in DEFAULT_CONFIG_DIRS: ++ filename = pathlib.Path(config_dir) / DEFAULT_CONFIG_FILE ++ if filename.is_file(): ++ # Found a config file, use it. ++ print(f'Using found config file: {filename}') ++ break ++ else: ++ # No config file specified or found, nothing to do. ++ return ++ ++ # Fill in ._groups based on --pcr-public-key=, --pcr-private-key=, and --phases=. ++ assert '_groups' not in namespace ++ n_pcr_priv = len(namespace.pcr_private_keys or ()) ++ namespace._groups = list(range(n_pcr_priv)) # pylint: disable=protected-access ++ ++ cp = configparser.ConfigParser( ++ comment_prefixes='#', ++ inline_comment_prefixes='#', ++ delimiters='=', ++ empty_lines_in_values=False, ++ interpolation=None, ++ strict=False) ++ # Do not make keys lowercase ++ cp.optionxform = lambda option: option ++ ++ # The API is not great. ++ read = cp.read(filename) ++ if not read: ++ raise IOError(f'Failed to read {filename}') ++ ++ for section_name, section in cp.items(): ++ idx = section_name.find(':') ++ if idx >= 0: ++ section_name, group = section_name[:idx+1], section_name[idx+1:] ++ if not section_name or not group: ++ raise ValueError('Section name components cannot be empty') ++ if ':' in group: ++ raise ValueError('Section name cannot contain more than one ":"') ++ else: ++ group = None ++ for key, value in section.items(): ++ if item := CONFIGFILE_ITEMS.get(f'{section_name}/{key}'): ++ item.apply_config(namespace, section_name, group, key, value) ++ else: ++ print(f'Unknown config setting [{section_name}] {key}=') ++ ++ ++def config_example(): ++ prev_section = None ++ for item in CONFIG_ITEMS: ++ section, key, value = item.config_example() ++ if section: ++ if prev_section != section: ++ if prev_section: ++ yield '' ++ yield f'[{section}]' ++ prev_section = section ++ yield f'{key} = {value}' ++ ++ ++class PagerHelpAction(argparse._HelpAction): # pylint: disable=protected-access ++ def __call__( ++ self, ++ parser: argparse.ArgumentParser, ++ namespace: argparse.Namespace, ++ values: Union[str, Sequence[Any], None] = None, ++ option_string: Optional[str] = None ++ ) -> None: ++ page(parser.format_help(), True) ++ parser.exit() ++ ++ ++def create_parser(): ++ p = argparse.ArgumentParser( ++ description='Build and sign Unified Kernel Images', ++ usage='\n ' + textwrap.dedent('''\ ++ ukify {b}build{e} [--linux=LINUX] [--initrd=INITRD] [options…] ++ ukify {b}genkey{e} [options…] ++ ukify {b}inspect{e} FILE… [options…] ++ ''').format(b=Style.bold, e=Style.reset), ++ allow_abbrev=False, ++ add_help=False, ++ epilog='\n '.join(('config file:', *config_example())), ++ formatter_class=argparse.RawDescriptionHelpFormatter, ++ ) ++ ++ for item in CONFIG_ITEMS: ++ item.add_to(p) ++ ++ # Suppress printing of usage synopsis on errors ++ p.error = lambda message: p.exit(2, f'{p.prog}: error: {message}\n') ++ ++ # Make --help paged ++ p.add_argument( ++ '-h', '--help', ++ action=PagerHelpAction, ++ help='show this help message and exit', ++ ) ++ ++ return p ++ ++ ++def finalize_options(opts): ++ # Figure out which syntax is being used, one of: ++ # ukify verb --arg --arg --arg ++ # ukify linux initrd… ++ if len(opts.positional) >= 1 and opts.positional[0] == 'inspect': ++ opts.verb = opts.positional[0] ++ opts.files = opts.positional[1:] ++ if not opts.files: ++ raise ValueError('file(s) to inspect must be specified') ++ if len(opts.files) > 1 and opts.json != 'off': ++ # We could allow this in the future, but we need to figure out the right structure ++ raise ValueError('JSON output is not allowed with multiple files') ++ elif len(opts.positional) == 1 and opts.positional[0] in VERBS: ++ opts.verb = opts.positional[0] ++ elif opts.linux or opts.initrd: ++ raise ValueError('--linux/--initrd options cannot be used with positional arguments') ++ else: ++ print("Assuming obsolete command line syntax with no verb. Please use 'build'.") ++ if opts.positional: ++ opts.linux = pathlib.Path(opts.positional[0]) ++ # If we have initrds from parsing config files, append our positional args at the end ++ opts.initrd = (opts.initrd or []) + [pathlib.Path(arg) for arg in opts.positional[1:]] ++ opts.verb = 'build' ++ ++ # Check that --pcr-public-key=, --pcr-private-key=, and --phases= ++ # have either the same number of arguments are are not specified at all. ++ n_pcr_pub = None if opts.pcr_public_keys is None else len(opts.pcr_public_keys) ++ n_pcr_priv = None if opts.pcr_private_keys is None else len(opts.pcr_private_keys) ++ n_phase_path_groups = None if opts.phase_path_groups is None else len(opts.phase_path_groups) ++ if n_pcr_pub is not None and n_pcr_pub != n_pcr_priv: ++ raise ValueError('--pcr-public-key= specifications must match --pcr-private-key=') ++ if n_phase_path_groups is not None and n_phase_path_groups != n_pcr_priv: ++ raise ValueError('--phases= specifications must match --pcr-private-key=') ++ ++ if opts.cmdline and opts.cmdline.startswith('@'): ++ opts.cmdline = pathlib.Path(opts.cmdline[1:]) ++ elif opts.cmdline: ++ # Drop whitespace from the command line. If we're reading from a file, ++ # we copy the contents verbatim. But configuration specified on the command line ++ # or in the config file may contain additional whitespace that has no meaning. ++ opts.cmdline = ' '.join(opts.cmdline.split()) ++ ++ if opts.os_release and opts.os_release.startswith('@'): ++ opts.os_release = pathlib.Path(opts.os_release[1:]) ++ elif not opts.os_release and opts.linux: ++ p = pathlib.Path('/etc/os-release') ++ if not p.exists(): ++ p = pathlib.Path('/usr/lib/os-release') ++ opts.os_release = p ++ ++ if opts.efi_arch is None: ++ opts.efi_arch = guess_efi_arch() ++ ++ if opts.stub is None: ++ if opts.linux is not None: ++ opts.stub = pathlib.Path(f'/usr/lib/systemd/boot/efi/linux{opts.efi_arch}.efi.stub') ++ else: ++ opts.stub = pathlib.Path(f'/usr/lib/systemd/boot/efi/addon{opts.efi_arch}.efi.stub') ++ ++ if opts.signing_engine is None: ++ if opts.sb_key: ++ opts.sb_key = pathlib.Path(opts.sb_key) ++ if opts.sb_cert: ++ opts.sb_cert = pathlib.Path(opts.sb_cert) ++ ++ if bool(opts.sb_key) ^ bool(opts.sb_cert): ++ # one param only given, sbsign needs both ++ raise ValueError('--secureboot-private-key= and --secureboot-certificate= must be specified together') ++ elif bool(opts.sb_key) and bool(opts.sb_cert): ++ # both param given, infer sbsign and in case it was given, ensure signtool=sbsign ++ if opts.signtool and opts.signtool != 'sbsign': ++ raise ValueError(f'Cannot provide --signtool={opts.signtool} with --secureboot-private-key= and --secureboot-certificate=') ++ opts.signtool = 'sbsign' ++ elif bool(opts.sb_cert_name): ++ # sb_cert_name given, infer pesign and in case it was given, ensure signtool=pesign ++ if opts.signtool and opts.signtool != 'pesign': ++ raise ValueError(f'Cannot provide --signtool={opts.signtool} with --secureboot-certificate-name=') ++ opts.signtool = 'pesign' ++ ++ if opts.sign_kernel and not opts.sb_key and not opts.sb_cert_name: ++ raise ValueError('--sign-kernel requires either --secureboot-private-key= and --secureboot-certificate= (for sbsign) or --secureboot-certificate-name= (for pesign) to be specified') ++ ++ if opts.verb == 'build' and opts.output is None: ++ if opts.linux is None: ++ raise ValueError('--output= must be specified when building a PE addon') ++ suffix = '.efi' if opts.sb_key or opts.sb_cert_name else '.unsigned.efi' ++ opts.output = opts.linux.name + suffix ++ ++ # Now that we know if we're inputting or outputting, really parse section config ++ f = Section.parse_output if opts.verb == 'inspect' else Section.parse_input ++ opts.sections = [f(s) for s in opts.sections] ++ # A convenience dictionary to make it easy to look up sections ++ opts.sections_by_name = {s.name:s for s in opts.sections} ++ ++ if opts.summary: ++ # TODO: replace pprint() with some fancy formatting. ++ pprint.pprint(vars(opts)) ++ sys.exit() ++ ++ ++def parse_args(args=None): ++ opts = create_parser().parse_args(args) ++ apply_config(opts) ++ finalize_options(opts) ++ return opts ++ ++ ++def main(): ++ opts = parse_args() ++ if opts.verb == 'build': ++ check_inputs(opts) ++ make_uki(opts) ++ elif opts.verb == 'genkey': ++ check_cert_and_keys_nonexistent(opts) ++ generate_keys(opts) ++ elif opts.verb == 'inspect': ++ inspect_sections(opts) ++ else: ++ assert False ++ ++ ++if __name__ == '__main__': ++ main() diff --git a/SOURCES/0408-bootctl-make-json-output-normal-json.patch b/SOURCES/0408-bootctl-make-json-output-normal-json.patch new file mode 100644 index 0000000..ab9404e --- /dev/null +++ b/SOURCES/0408-bootctl-make-json-output-normal-json.patch @@ -0,0 +1,54 @@ +From 27e95f2513e24a6abc26c56f05c67c34492442d7 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Tue, 15 Nov 2022 15:00:57 +0100 +Subject: [PATCH] bootctl: make --json output normal json + +We would output a sequence of concatenated JSON strings. 'jq' accepts such +output without fuss, and can even automatically build an array with --slurp/-s. +Nevertheless, parsing this format is more effort for the reader, since it's not +"standard JSON". E.g. Python's json module cannot do this out-of-the-box, but +needs some loop with json.JSONDecoder.raw_decode() and then collecting the +objects into an array. Such streaming output make sense in case of logs, where +we stream the output and it has no predefined length. But here we expect at +most a few dozen entries, so it's nicer to write normal JSON that is trivial to +parse. + +I'm treating this is a bugfix and not attempting to provide compatibility +backwards. I don't think the previous format was seeing much use, and it's +trivial to adapt to the new one. + +(cherry picked from commit b570204a97bccfbfce8fc4ffa65306f8a06fe16e) + +Related: RHEL-13199 +--- + src/shared/bootspec.c | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/src/shared/bootspec.c b/src/shared/bootspec.c +index d3cfb41a12..fe44b5e9d2 100644 +--- a/src/shared/bootspec.c ++++ b/src/shared/bootspec.c +@@ -1408,6 +1408,8 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { + assert(config); + + if (!FLAGS_SET(json_format, JSON_FORMAT_OFF)) { ++ _cleanup_(json_variant_unrefp) JsonVariant *array = NULL; ++ + for (size_t i = 0; i < config->n_entries; i++) { + _cleanup_free_ char *opts = NULL; + const BootEntry *e = config->entries + i; +@@ -1447,9 +1449,13 @@ int show_boot_entries(const BootConfig *config, JsonFormatFlags json_format) { + if (r < 0) + return log_oom(); + +- json_variant_dump(v, json_format, stdout, NULL); ++ r = json_variant_append_array(&array, v); ++ if (r < 0) ++ return log_oom(); + } + ++ json_variant_dump(array, json_format, NULL, NULL); ++ + } else { + for (size_t n = 0; n < config->n_entries; n++) { + r = show_boot_entry( diff --git a/SOURCES/0409-test-replace-readfp-with-read_file.patch b/SOURCES/0409-test-replace-readfp-with-read_file.patch new file mode 100644 index 0000000..a35f451 --- /dev/null +++ b/SOURCES/0409-test-replace-readfp-with-read_file.patch @@ -0,0 +1,28 @@ +From 8fced1b2ed30b9cda338c35946d8dcc3820ac25a Mon Sep 17 00:00:00 2001 +From: Frantisek Sumsal +Date: Wed, 5 Jul 2023 19:43:43 +0200 +Subject: [PATCH] test: replace readfp() with read_file() + +ConfigParser.readfp() has been deprecated since Python 3.2 and was +dropped completely in Python 3.11. + +(cherry picked from commit ba4a1cd8a863f65ff016be72e520c323aa1e1a6f) + +Related: RHEL-13199 +--- + test/sysv-generator-test.py | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/test/sysv-generator-test.py b/test/sysv-generator-test.py +index 484b610a02..84237bab61 100755 +--- a/test/sysv-generator-test.py ++++ b/test/sysv-generator-test.py +@@ -80,7 +80,7 @@ class SysvGeneratorTest(unittest.TestCase): + cp = RawConfigParser(dict_type=MultiDict) + cp.optionxform = lambda o: o # don't lower-case option names + with open(service) as f: +- cp.readfp(f) ++ cp.read_file(f) + results[os.path.basename(service)] = cp + + return (err, results) diff --git a/SOURCES/0410-stub-measure-document-and-measure-.uname-UKI-section.patch b/SOURCES/0410-stub-measure-document-and-measure-.uname-UKI-section.patch new file mode 100644 index 0000000..967b063 --- /dev/null +++ b/SOURCES/0410-stub-measure-document-and-measure-.uname-UKI-section.patch @@ -0,0 +1,81 @@ +From fe66c5955044cf2b93fa788ae7bdfe3a07f11449 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Sun, 21 May 2023 14:32:09 +0100 +Subject: [PATCH] stub/measure: document and measure .uname UKI section + +(cherry picked from commit b6f2e6860220aa89550f690b12246c4e8eb6e908) + +Resolves: RHEL-13199 +--- + man/systemd-stub.xml | 3 +++ + src/boot/measure.c | 3 +++ + src/fundamental/tpm-pcr.c | 1 + + src/fundamental/tpm-pcr.h | 1 + + 4 files changed, 8 insertions(+) + +diff --git a/man/systemd-stub.xml b/man/systemd-stub.xml +index 415d663f53..85d30129d6 100644 +--- a/man/systemd-stub.xml ++++ b/man/systemd-stub.xml +@@ -57,6 +57,9 @@ + os-release5 file of + the OS the kernel belongs to, in the .osrel PE section. + ++ Kernel version information, i.e. the output of uname -r for the ++ kernel included in the UKI, in the .uname PE section. ++ + The initrd will be loaded from the .initrd PE section. + + +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 0bbd386449..67ab84753e 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -79,6 +79,7 @@ static int help(int argc, char *argv[], void *userdata) { + " --initrd=PATH Path to initrd image file %7$s .initrd\n" + " --splash=PATH Path to splash bitmap file %7$s .splash\n" + " --dtb=PATH Path to Devicetree file %7$s .dtb\n" ++ " --uname=PATH Path to 'uname -r' file %7$s .uname\n" + " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, +@@ -118,6 +119,7 @@ static int parse_argv(int argc, char *argv[]) { + ARG_INITRD, + ARG_SPLASH, + ARG_DTB, ++ ARG_UNAME, + _ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */ + _ARG_SECTION_LAST, + ARG_PCRPKEY = _ARG_SECTION_LAST, +@@ -139,6 +141,7 @@ static int parse_argv(int argc, char *argv[]) { + { "initrd", required_argument, NULL, ARG_INITRD }, + { "splash", required_argument, NULL, ARG_SPLASH }, + { "dtb", required_argument, NULL, ARG_DTB }, ++ { "uname", required_argument, NULL, ARG_UNAME }, + { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, + { "current", no_argument, NULL, 'c' }, + { "bank", required_argument, NULL, ARG_BANK }, +diff --git a/src/fundamental/tpm-pcr.c b/src/fundamental/tpm-pcr.c +index 7609d83c2e..0685d37b05 100644 +--- a/src/fundamental/tpm-pcr.c ++++ b/src/fundamental/tpm-pcr.c +@@ -11,6 +11,7 @@ const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = { + [UNIFIED_SECTION_INITRD] = ".initrd", + [UNIFIED_SECTION_SPLASH] = ".splash", + [UNIFIED_SECTION_DTB] = ".dtb", ++ [UNIFIED_SECTION_UNAME] = ".uname", + [UNIFIED_SECTION_PCRSIG] = ".pcrsig", + [UNIFIED_SECTION_PCRPKEY] = ".pcrpkey", + NULL, +diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h +index 235d4841b0..24240b82ed 100644 +--- a/src/fundamental/tpm-pcr.h ++++ b/src/fundamental/tpm-pcr.h +@@ -34,6 +34,7 @@ typedef enum UnifiedSection { + UNIFIED_SECTION_INITRD, + UNIFIED_SECTION_SPLASH, + UNIFIED_SECTION_DTB, ++ UNIFIED_SECTION_UNAME, + UNIFIED_SECTION_PCRSIG, + UNIFIED_SECTION_PCRPKEY, + _UNIFIED_SECTION_MAX, diff --git a/SOURCES/0411-boot-measure-.sbat-section.patch b/SOURCES/0411-boot-measure-.sbat-section.patch new file mode 100644 index 0000000..92f13cd --- /dev/null +++ b/SOURCES/0411-boot-measure-.sbat-section.patch @@ -0,0 +1,96 @@ +From 16b3bb1a1bb8a0a42ad6eb56fd33dcb800c8af04 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Thu, 29 Jun 2023 23:41:48 +0100 +Subject: [PATCH] boot: measure .sbat section + +We are now merging .sbat sections from sd-stub and kernel image, so +measure it in PCR11. + +(cherry picked from commit d5f91cf79361cab58e32bf7b76c41ba244add75f) + +Resolves: RHEL-13199 +--- + man/systemd-measure.xml | 8 +++++--- + src/boot/measure.c | 3 +++ + src/fundamental/tpm-pcr.c | 1 + + src/fundamental/tpm-pcr.h | 1 + + 4 files changed, 10 insertions(+), 3 deletions(-) + +diff --git a/man/systemd-measure.xml b/man/systemd-measure.xml +index 46fc979654..e08dbcdac9 100644 +--- a/man/systemd-measure.xml ++++ b/man/systemd-measure.xml +@@ -66,9 +66,10 @@ + Pre-calculate the expected values seen in PCR register 11 after boot-up of a unified + kernel image consisting of the components specified with , + , , , +- , , see below. Only +- is mandatory. (Alternatively, specify to use the +- current values of PCR register 11 instead.) ++ , , , ++ see below. Only is mandatory. (Alternatively, ++ specify to use the current values of PCR register 11 instead.) ++ + + + +@@ -104,6 +105,7 @@ + + + ++ + + + When used with the calculate or sign verb, +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 67ab84753e..84a7c357a4 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -80,6 +80,7 @@ static int help(int argc, char *argv[], void *userdata) { + " --splash=PATH Path to splash bitmap file %7$s .splash\n" + " --dtb=PATH Path to Devicetree file %7$s .dtb\n" + " --uname=PATH Path to 'uname -r' file %7$s .uname\n" ++ " --sbat=PATH Path to SBAT file %7$s .sbat\n" + " --pcrpkey=PATH Path to public key for PCR signatures %7$s .pcrpkey\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, +@@ -120,6 +121,7 @@ static int parse_argv(int argc, char *argv[]) { + ARG_SPLASH, + ARG_DTB, + ARG_UNAME, ++ ARG_SBAT, + _ARG_PCRSIG, /* the .pcrsig section is not input for signing, hence not actually an argument here */ + _ARG_SECTION_LAST, + ARG_PCRPKEY = _ARG_SECTION_LAST, +@@ -142,6 +144,7 @@ static int parse_argv(int argc, char *argv[]) { + { "splash", required_argument, NULL, ARG_SPLASH }, + { "dtb", required_argument, NULL, ARG_DTB }, + { "uname", required_argument, NULL, ARG_UNAME }, ++ { "sbat", required_argument, NULL, ARG_SBAT }, + { "pcrpkey", required_argument, NULL, ARG_PCRPKEY }, + { "current", no_argument, NULL, 'c' }, + { "bank", required_argument, NULL, ARG_BANK }, +diff --git a/src/fundamental/tpm-pcr.c b/src/fundamental/tpm-pcr.c +index 0685d37b05..2f7e9b428d 100644 +--- a/src/fundamental/tpm-pcr.c ++++ b/src/fundamental/tpm-pcr.c +@@ -12,6 +12,7 @@ const char* const unified_sections[_UNIFIED_SECTION_MAX + 1] = { + [UNIFIED_SECTION_SPLASH] = ".splash", + [UNIFIED_SECTION_DTB] = ".dtb", + [UNIFIED_SECTION_UNAME] = ".uname", ++ [UNIFIED_SECTION_SBAT] = ".sbat", + [UNIFIED_SECTION_PCRSIG] = ".pcrsig", + [UNIFIED_SECTION_PCRPKEY] = ".pcrpkey", + NULL, +diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h +index 24240b82ed..794d593825 100644 +--- a/src/fundamental/tpm-pcr.h ++++ b/src/fundamental/tpm-pcr.h +@@ -35,6 +35,7 @@ typedef enum UnifiedSection { + UNIFIED_SECTION_SPLASH, + UNIFIED_SECTION_DTB, + UNIFIED_SECTION_UNAME, ++ UNIFIED_SECTION_SBAT, + UNIFIED_SECTION_PCRSIG, + UNIFIED_SECTION_PCRPKEY, + _UNIFIED_SECTION_MAX, diff --git a/SOURCES/0412-Revert-test_ukify-no-stinky-root-needed-for-signing.patch b/SOURCES/0412-Revert-test_ukify-no-stinky-root-needed-for-signing.patch new file mode 100644 index 0000000..1091ce0 --- /dev/null +++ b/SOURCES/0412-Revert-test_ukify-no-stinky-root-needed-for-signing.patch @@ -0,0 +1,42 @@ +From 3c235c8d26813ae428053c284b67ddfe70d9caed Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Wed, 22 Nov 2023 15:38:47 +0100 +Subject: [PATCH] Revert "test_ukify: no stinky root needed for signing" + +This reverts commit 0d66468243d888dd721ba0072cbad742ab6fc690. + +There was a huge rewrite of the tpm2 code that removed the requirement for +device access, but backporting that would be a huge effort. Let's instead skip +the tests for now. (They pass under root.) + +Related: RHEL-13199 +--- + src/ukify/test/test_ukify.py | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/src/ukify/test/test_ukify.py b/src/ukify/test/test_ukify.py +index b12c09d4bf..5a42a94799 100755 +--- a/src/ukify/test/test_ukify.py ++++ b/src/ukify/test/test_ukify.py +@@ -661,6 +661,10 @@ def test_pcr_signing(kernel_initrd, tmpdir): + pytest.skip('linux+initrd not found') + if systemd_measure() is None: + pytest.skip('systemd-measure not found') ++ if os.getuid() != 0: ++ pytest.skip('must be root to access tpm2') ++ if subprocess.call(['systemd-creds', 'has-tpm2', '-q']) != 0: ++ pytest.skip('tpm2 is not available') + + ourdir = pathlib.Path(__file__).parent + pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64') +@@ -724,6 +728,10 @@ def test_pcr_signing2(kernel_initrd, tmpdir): + pytest.skip('linux+initrd not found') + if systemd_measure() is None: + pytest.skip('systemd-measure not found') ++ if os.getuid() != 0: ++ pytest.skip('must be root to access tpm2') ++ if subprocess.call(['systemd-creds', 'has-tpm2', '-q']) != 0: ++ pytest.skip('tpm2 is not available') + + ourdir = pathlib.Path(__file__).parent + pub = unbase64(ourdir / 'example.tpm2-pcr-public.pem.base64') diff --git a/SOURCES/0413-ukify-move-to-usr-bin-and-mark-as-non-non-experiment.patch b/SOURCES/0413-ukify-move-to-usr-bin-and-mark-as-non-non-experiment.patch new file mode 100644 index 0000000..dde0df5 --- /dev/null +++ b/SOURCES/0413-ukify-move-to-usr-bin-and-mark-as-non-non-experiment.patch @@ -0,0 +1,36 @@ +From 2adec0d845e6d00a604ddaa5639759896b78728f Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Mon, 28 Aug 2023 18:22:43 +0300 +Subject: [PATCH] ukify: move to /usr/bin and mark as non non-experimental + +The tool is moved into the $PATH and a compat symlink is provided. + +It is fairly widely used now, and realistically we need to keep backwards +compat or people will be very unhappy. + +(cherry picked from commit f65aa477d90ab7fbbc50ba05c55180213d5992e0) + +Related: RHEL-13199 +--- + meson.build | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/meson.build b/meson.build +index b874c2f9b4..e0495ed36a 100644 +--- a/meson.build ++++ b/meson.build +@@ -3861,9 +3861,13 @@ ukify = custom_target( + command : [jinja2_cmdline, '@INPUT@', '@OUTPUT@'], + install : want_ukify, + install_mode : 'rwxr-xr-x', +- install_dir : rootlibexecdir) ++ install_dir : bindir) + if want_ukify + public_programs += ukify ++ ++ meson.add_install_script(sh, '-c', ++ ln_s.format(bindir / 'ukify', ++ rootlibexecdir / 'ukify')) + endif + + if want_tests != 'false' and want_kernel_install diff --git a/SOURCES/0414-kernel-install-Add-uki-layout.patch b/SOURCES/0414-kernel-install-Add-uki-layout.patch new file mode 100644 index 0000000..baefb8a --- /dev/null +++ b/SOURCES/0414-kernel-install-Add-uki-layout.patch @@ -0,0 +1,221 @@ +From 90f26ba66f256fd65c6e7c38ed6f2138fed6b3ed Mon Sep 17 00:00:00 2001 +From: Joerg Behrmann +Date: Wed, 23 Nov 2022 16:43:19 +0100 +Subject: [PATCH] kernel-install: Add uki layout + +Currently the kernel-install man page only documents the bls layout for use +with the boot loader spec type #1. 90-loaderentry.install uses this layout to +generate loader entries and copy the kernel image and initrd to $BOOT. + +This commit documents a second layout "uki" and adds 90-uki-copy.install, +which copies a UKI "uki.efi" from the staging area or any file with the .efi +extension given on the command line to +$BOOT/EFI/Linux/$ENTRY_TOKEN-$KERNEl_VERSION(+$TRIES).efi + +This allows for both locally generated and distro-provided UKIs to be handled +by kernel-install. + +(cherry picked from commit 0ccfd3564b2532a4da6526a9e030362c4a142b77) + +Resolves: RHEL-16354 +--- + man/kernel-install.xml | 38 ++++++++-- + src/kernel-install/90-uki-copy.install | 97 ++++++++++++++++++++++++++ + src/kernel-install/meson.build | 2 + + 3 files changed, 131 insertions(+), 6 deletions(-) + create mode 100755 src/kernel-install/90-uki-copy.install + +diff --git a/man/kernel-install.xml b/man/kernel-install.xml +index b8ea2b16b2..b1822a8847 100644 +--- a/man/kernel-install.xml ++++ b/man/kernel-install.xml +@@ -108,6 +108,14 @@ + is missing), or "Linux KERNEL-VERSION", if unset. + + If $KERNEL_INSTALL_LAYOUT is not "bls", this plugin does nothing. ++ ++ 90-uki-copy.install copies a file ++ uki.efi from $KERNEL_INSTALL_STAGING_AREA or if it does ++ not exist the KERNEL-IMAGE argument, iff it has a ++ .efi extension, to ++ $BOOT/EFI/Linux/ENTRY-TOKEN-KERNEL-VERSION.efi. ++ ++ If $KERNEL_INSTALL_LAYOUT is not "uki", this plugin does nothing. + + + +@@ -132,6 +140,9 @@ + + 90-loaderentry.install removes the file + $BOOT/loader/entries/ENTRY-TOKEN-KERNEL-VERSION.conf. ++ ++ 90-uki-copy.install removes the file ++ $BOOT/EFI/Linux/ENTRY-TOKEN-KERNEL-VERSION.efi. + + + +@@ -213,7 +224,7 @@ + (EFI System Partition) are mounted, and also conceptually referred to as $BOOT. Can + be overridden by setting $BOOT_ROOT (see below). + +- $KERNEL_INSTALL_LAYOUT=bls|other|... is set for the plugins to specify the ++ $KERNEL_INSTALL_LAYOUT=bls|uki|other|... is set for the plugins to specify the + installation layout. Defaults to if + $BOOT/ENTRY-TOKEN exists, or + otherwise. Additional layout names may be defined by convention. If a plugin uses a special layout, +@@ -235,6 +246,18 @@ + Implemented by 90-loaderentry.install. + + ++ ++ uki ++ ++ Standard Boot Loader ++ Specification Type #2 layout, compatible with ++ systemd-boot7: ++ unified kernel images under $BOOT/EFI/Linux as ++ $BOOT/EFI/Linux/ENTRY-TOKEN-KERNEL-VERSION[+TRIES].efi. ++ Implemented by 90-uki-copy.install. ++ ++ + + other + +@@ -312,12 +335,15 @@ + /etc/kernel/tries + + +- Read by 90-loaderentry.install. If this file exists a numeric value is read from +- it and the naming of the generated entry file is slightly altered to include it as +- $BOOT/loader/entries/MACHINE-ID-KERNEL-VERSION+TRIES.conf. This ++ Read by 90-loaderentry.install and ++ 90-uki-copy.install. If this file exists a numeric value is read from it ++ and the naming of the generated entry file or UKI is slightly altered to include it as ++ $BOOT/loader/entries/ENTRY-TOKEN-KERNEL-VERSION+TRIES.conf ++ or ++ $BOOT/EFI/Linux/ENTRY-TOKEN-KERNEL-VERSION+TRIES.conf, respectively. This + is useful for boot loaders such as +- systemd-boot7 which +- implement boot attempt counting with a counter embedded in the entry file name. ++ systemd-boot7 ++ which implement boot attempt counting with a counter embedded in the entry file name. + $KERNEL_INSTALL_CONF_ROOT may be used to override the path. + + +diff --git a/src/kernel-install/90-uki-copy.install b/src/kernel-install/90-uki-copy.install +new file mode 100755 +index 0000000000..d6e3deb723 +--- /dev/null ++++ b/src/kernel-install/90-uki-copy.install +@@ -0,0 +1,97 @@ ++#!/bin/sh ++# -*- mode: shell-script; indent-tabs-mode: nil; sh-basic-offset: 4; -*- ++# ex: ts=8 sw=4 sts=4 et filetype=sh ++# SPDX-License-Identifier: LGPL-2.1-or-later ++# ++# This file is part of systemd. ++# ++# systemd is free software; you can redistribute it and/or modify it ++# under the terms of the GNU Lesser General Public License as published by ++# the Free Software Foundation; either version 2.1 of the License, or ++# (at your option) any later version. ++# ++# systemd is distributed in the hope that it will be useful, but ++# WITHOUT ANY WARRANTY; without even the implied warranty of ++# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU ++# General Public License for more details. ++# ++# You should have received a copy of the GNU Lesser General Public License ++# along with systemd; If not, see . ++ ++set -e ++ ++COMMAND="${1:?}" ++KERNEL_VERSION="${2:?}" ++# shellcheck disable=SC2034 ++ENTRY_DIR_ABS="$3" ++KERNEL_IMAGE="$4" ++ ++[ "$KERNEL_INSTALL_LAYOUT" = "uki" ] || exit 0 ++ ++ENTRY_TOKEN="$KERNEL_INSTALL_ENTRY_TOKEN" ++BOOT_ROOT="$KERNEL_INSTALL_BOOT_ROOT" ++ ++UKI_DIR="$BOOT_ROOT/EFI/Linux" ++ ++case "$COMMAND" in ++ remove) ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && \ ++ echo "Removing $UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION*.efi" ++ exec rm -f \ ++ "$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION.efi" \ ++ "$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION+"*".efi" ++ ;; ++ add) ++ ;; ++ *) ++ exit 0 ++ ;; ++esac ++ ++if ! [ -d "$UKI_DIR" ]; then ++ echo "Error: entry directory '$UKI_DIR' does not exist" >&2 ++ exit 1 ++fi ++ ++TRIES_FILE="${KERNEL_INSTALL_CONF_ROOT:-/etc/kernel}/tries" ++ ++if [ -f "$TRIES_FILE" ]; then ++ read -r TRIES <"$TRIES_FILE" ++ if ! echo "$TRIES" | grep -q '^[0-9][0-9]*$'; then ++ echo "$TRIES_FILE does not contain an integer." >&2 ++ exit 1 ++ fi ++ UKI_FILE="$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION+$TRIES.efi" ++else ++ UKI_FILE="$UKI_DIR/$ENTRY_TOKEN-$KERNEL_VERSION.efi" ++fi ++ ++# If there is a UKI named uki.efi on the staging area use that, if not use what ++# was passed in as $KERNEL_IMAGE but insist it has a .efi extension ++if [ -f "$KERNEL_INSTALL_STAGING_AREA/uki.efi" ]; then ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $KERNEL_INSTALL_STAGING_AREA/uki.efi" ++ install -m 0644 "$KERNEL_INSTALL_STAGING_AREA/uki.efi" "$UKI_FILE" || { ++ echo "Error: could not copy '$KERNEL_INSTALL_STAGING_AREA/uki.efi' to '$UKI_FILE'." >&2 ++ exit 1 ++ } ++elif [ -n "$KERNEL_IMAGE" ]; then ++ [ -f "$KERNEL_IMAGE" ] || { ++ echo "Error: UKI '$KERNEL_IMAGE' not a file." >&2 ++ exit 1 ++ } ++ [ "$KERNEL_IMAGE" != "${KERNEL_IMAGE%*.efi}.efi" ] && { ++ echo "Error: $KERNEL_IMAGE is missing .efi suffix." >&2 ++ exit 1 ++ } ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $KERNEL_IMAGE" ++ install -m 0644 "$KERNEL_IMAGE" "$UKI_FILE" || { ++ echo "Error: could not copy '$KERNEL_IMAGE' to '$UKI_FILE'." >&2 ++ exit 1 ++ } ++else ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "No UKI available. Nothing to do." ++ exit 0 ++fi ++chown root:root "$UKI_FILE" || : ++ ++exit 0 +diff --git a/src/kernel-install/meson.build b/src/kernel-install/meson.build +index 90a0e3ae49..68a4d43862 100644 +--- a/src/kernel-install/meson.build ++++ b/src/kernel-install/meson.build +@@ -3,6 +3,8 @@ + kernel_install_in = files('kernel-install.in') + loaderentry_install = files('90-loaderentry.install') + ++uki_copy_install = files('90-uki-copy.install') ++ + if want_kernel_install + install_data('50-depmod.install', + loaderentry_install, diff --git a/SOURCES/0415-kernel-install-remove-math-slang-from-man-page.patch b/SOURCES/0415-kernel-install-remove-math-slang-from-man-page.patch new file mode 100644 index 0000000..789d450 --- /dev/null +++ b/SOURCES/0415-kernel-install-remove-math-slang-from-man-page.patch @@ -0,0 +1,25 @@ +From 5d3d683fd54100fea2a355b0ebc8b03d9b6c964b Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Wed, 25 Jan 2023 13:57:09 +0100 +Subject: [PATCH] kernel-install: remove math slang from man page + +(cherry picked from commit 642617f431457382ec2140a52ee08bfbb3e5e1db) + +Related: RHEL-16354 +--- + man/kernel-install.xml | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/man/kernel-install.xml b/man/kernel-install.xml +index b1822a8847..1bc6e7aa05 100644 +--- a/man/kernel-install.xml ++++ b/man/kernel-install.xml +@@ -111,7 +111,7 @@ + + 90-uki-copy.install copies a file + uki.efi from $KERNEL_INSTALL_STAGING_AREA or if it does +- not exist the KERNEL-IMAGE argument, iff it has a ++ not exist the KERNEL-IMAGE argument, only if it has a + .efi extension, to + $BOOT/EFI/Linux/ENTRY-TOKEN-KERNEL-VERSION.efi. + diff --git a/SOURCES/0416-kernel-install-handle-uki-installs-automatically.patch b/SOURCES/0416-kernel-install-handle-uki-installs-automatically.patch new file mode 100644 index 0000000..744b1cb --- /dev/null +++ b/SOURCES/0416-kernel-install-handle-uki-installs-automatically.patch @@ -0,0 +1,80 @@ +From 4f3593718196c007838eebf5d9b42f09318bd4e6 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Fri, 20 Jan 2023 09:05:18 +0100 +Subject: [PATCH] kernel-install: handle uki installs automatically + +Detect image type using "bootctl kernel-identify $kernel", +store result in KERNEL_INSTALL_IMAGE_TYPE. + +Extend layout autodetection to check the kernel image type +and pick layout=uki for UKIs. + +Resolves: https://github.com/systemd/systemd/issues/25822 +(cherry picked from commit 3d5f0bfe4e72fdc4d8f8d65f96dc5501dfed8a64) + +Related: RHEL-16354 +--- + man/kernel-install.xml | 17 +++++++++++++---- + src/kernel-install/kernel-install.in | 12 ++++++++++-- + 2 files changed, 23 insertions(+), 6 deletions(-) + +diff --git a/man/kernel-install.xml b/man/kernel-install.xml +index 1bc6e7aa05..4d91b7b20b 100644 +--- a/man/kernel-install.xml ++++ b/man/kernel-install.xml +@@ -224,10 +224,8 @@ + (EFI System Partition) are mounted, and also conceptually referred to as $BOOT. Can + be overridden by setting $BOOT_ROOT (see below). + +- $KERNEL_INSTALL_LAYOUT=bls|uki|other|... is set for the plugins to specify the +- installation layout. Defaults to if +- $BOOT/ENTRY-TOKEN exists, or +- otherwise. Additional layout names may be defined by convention. If a plugin uses a special layout, ++ $KERNEL_INSTALL_LAYOUT=auto|bls|uki|other|... is set for the plugins to specify the ++ installation layout. Additional layout names may be defined by convention. If a plugin uses a special layout, + it's encouraged to declare its own layout name and configure layout= in + install.conf upon initial installation. The following values are currently + understood: +@@ -264,6 +262,17 @@ + Some other layout not understood natively by kernel-install. + + ++ ++ auto ++ ++ Pick the layout automatically. If the kernel is a UKI set layout to ++ . If not default to if ++ $BOOT/loader/entries.srel with content type1 or ++ $BOOT/ENTRY-TOKEN exists, or ++ otherwise. ++ Leaving layout blank has the same effect. This is the default. ++ ++ + + + $KERNEL_INSTALL_INITRD_GENERATOR is set for plugins to select the initrd +diff --git a/src/kernel-install/kernel-install.in b/src/kernel-install/kernel-install.in +index fa2c0d5276..25884fc0e2 100755 +--- a/src/kernel-install/kernel-install.in ++++ b/src/kernel-install/kernel-install.in +@@ -250,10 +250,18 @@ if [ -z "$ENTRY_TOKEN" ]; then + echo "No entry-token candidate matched, using \"$ENTRY_TOKEN\" from machine-id" + fi + +-if [ -z "$layout" ]; then ++export KERNEL_INSTALL_IMAGE_TYPE="" ++if [ -f "$1" ]; then ++ KERNEL_INSTALL_IMAGE_TYPE="$(bootctl kernel-identify "$1" 2>/dev/null || echo "unknown")" ++fi ++ ++if [ "$layout" = "auto" ] || [ -z "$layout" ]; then + # No layout configured by the administrator. Let's try to figure it out + # automatically from metadata already contained in $BOOT_ROOT. +- if [ -e "$BOOT_ROOT/loader/entries.srel" ]; then ++ if [ "$KERNEL_INSTALL_IMAGE_TYPE" = "uki" ]; then ++ layout="uki" ++ log_verbose "Kernel image is UKI, using layout=$layout" ++ elif [ -e "$BOOT_ROOT/loader/entries.srel" ]; then + read -r ENTRIES_SREL <"$BOOT_ROOT/loader/entries.srel" + if [ "$ENTRIES_SREL" = "type1" ]; then + # The loader/entries.srel file clearly indicates that the installed diff --git a/SOURCES/0417-90-uki-copy.install-create-BOOT-EFI-Linux-directory-.patch b/SOURCES/0417-90-uki-copy.install-create-BOOT-EFI-Linux-directory-.patch new file mode 100644 index 0000000..90d3490 --- /dev/null +++ b/SOURCES/0417-90-uki-copy.install-create-BOOT-EFI-Linux-directory-.patch @@ -0,0 +1,31 @@ +From cb2ead4b9fc17554a8694fd213bfb100d9c15678 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Fri, 20 Jan 2023 12:59:33 +0100 +Subject: [PATCH] 90-uki-copy.install: create $BOOT/EFI/Linux directory if + needed + +Do not consider a missing 'Linux' subdirectory an error. +Just create it instead. + +(cherry picked from commit c7314ee7e290b3978e2f2d7726d07656eda071f9) + +Related: RHEL-16354 +--- + src/kernel-install/90-uki-copy.install | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/kernel-install/90-uki-copy.install b/src/kernel-install/90-uki-copy.install +index d6e3deb723..6c71b211d7 100755 +--- a/src/kernel-install/90-uki-copy.install ++++ b/src/kernel-install/90-uki-copy.install +@@ -49,8 +49,8 @@ case "$COMMAND" in + esac + + if ! [ -d "$UKI_DIR" ]; then +- echo "Error: entry directory '$UKI_DIR' does not exist" >&2 +- exit 1 ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "creating $UKI_DIR" ++ mkdir -p "$UKI_DIR" + fi + + TRIES_FILE="${KERNEL_INSTALL_CONF_ROOT:-/etc/kernel}/tries" diff --git a/SOURCES/0418-kernel-install-Log-location-that-uki-is-installed-in.patch b/SOURCES/0418-kernel-install-Log-location-that-uki-is-installed-in.patch new file mode 100644 index 0000000..494e1c4 --- /dev/null +++ b/SOURCES/0418-kernel-install-Log-location-that-uki-is-installed-in.patch @@ -0,0 +1,36 @@ +From d0b5386bde65b8c488d23f16ec4049d1e6378c25 Mon Sep 17 00:00:00 2001 +From: Daan De Meyer +Date: Sun, 5 Nov 2023 13:50:25 +0100 +Subject: [PATCH] kernel-install: Log location that uki is installed in + +Let's log where we install a UKI when running in verbose mode. + +(cherry picked from commit 4f5278eead35bc66cc943a493eab8a8b78174400) + +Related: RHEL-16354 +--- + src/kernel-install/90-uki-copy.install | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/kernel-install/90-uki-copy.install b/src/kernel-install/90-uki-copy.install +index 6c71b211d7..c66c09719c 100755 +--- a/src/kernel-install/90-uki-copy.install ++++ b/src/kernel-install/90-uki-copy.install +@@ -69,7 +69,7 @@ fi + # If there is a UKI named uki.efi on the staging area use that, if not use what + # was passed in as $KERNEL_IMAGE but insist it has a .efi extension + if [ -f "$KERNEL_INSTALL_STAGING_AREA/uki.efi" ]; then +- [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $KERNEL_INSTALL_STAGING_AREA/uki.efi" ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $KERNEL_INSTALL_STAGING_AREA/uki.efi as $UKI_FILE" + install -m 0644 "$KERNEL_INSTALL_STAGING_AREA/uki.efi" "$UKI_FILE" || { + echo "Error: could not copy '$KERNEL_INSTALL_STAGING_AREA/uki.efi' to '$UKI_FILE'." >&2 + exit 1 +@@ -83,7 +83,7 @@ elif [ -n "$KERNEL_IMAGE" ]; then + echo "Error: $KERNEL_IMAGE is missing .efi suffix." >&2 + exit 1 + } +- [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $KERNEL_IMAGE" ++ [ "$KERNEL_INSTALL_VERBOSE" -gt 0 ] && echo "Installing $KERNEL_IMAGE as $UKI_FILE" + install -m 0644 "$KERNEL_IMAGE" "$UKI_FILE" || { + echo "Error: could not copy '$KERNEL_IMAGE' to '$UKI_FILE'." >&2 + exit 1 diff --git a/SOURCES/0419-bootctl-fix-errno-logging.patch b/SOURCES/0419-bootctl-fix-errno-logging.patch new file mode 100644 index 0000000..c868226 --- /dev/null +++ b/SOURCES/0419-bootctl-fix-errno-logging.patch @@ -0,0 +1,25 @@ +From f0ab67eb46103c68a1fc708b45e2fa6b93780efb Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 19 Dec 2022 22:25:28 +0100 +Subject: [PATCH] bootctl: fix errno logging + +(cherry picked from commit e425849e995e448f529d3c106bf1e3de2ca23a35) + +Related: RHEL-16354 +--- + src/boot/bootctl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index 7da48b4ca4..67fcbcc8cd 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -2028,7 +2028,7 @@ static int install_random_seed(const char *esp) { + return log_error_errno(SYNTHETIC_ERRNO(EIO), "Short write while writing random seed file."); + + if (rename(tmp, path) < 0) +- return log_error_errno(r, "Failed to move random seed file into place: %m"); ++ return log_error_errno(errno, "Failed to move random seed file into place: %m"); + + tmp = mfree(tmp); + diff --git a/SOURCES/0420-bootctl-add-kernel-identity-command.patch b/SOURCES/0420-bootctl-add-kernel-identity-command.patch new file mode 100644 index 0000000..1571df7 --- /dev/null +++ b/SOURCES/0420-bootctl-add-kernel-identity-command.patch @@ -0,0 +1,214 @@ +From ca716a0fdfd434e52384dd74ff4c909aa95b6c81 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Mon, 16 Jan 2023 18:58:21 +0100 +Subject: [PATCH] bootctl: add kernel-identity command + +The command takes a kernel as argument and checks what kind of kernel +the image is. Returns one of uki, pe or unknown. + +(cherry picked from commit 53c368d71ba43da7414ac86c58291a11da05ba84) + +Resolves: RHEL-16354 +--- + man/bootctl.xml | 13 +++++ + meson.build | 5 +- + src/boot/bootctl-uki.c | 109 +++++++++++++++++++++++++++++++++++++++++ + src/boot/bootctl-uki.h | 3 ++ + src/boot/bootctl.c | 4 ++ + 5 files changed, 133 insertions(+), 1 deletion(-) + create mode 100644 src/boot/bootctl-uki.c + create mode 100644 src/boot/bootctl-uki.h + +diff --git a/man/bootctl.xml b/man/bootctl.xml +index dfc56d6125..0f992ec383 100644 +--- a/man/bootctl.xml ++++ b/man/bootctl.xml +@@ -217,6 +217,19 @@ + + + ++ ++ <command>kernel</command> Commands ++ ++ ++ ++ kernel ++ ++ Takes a kernel image as argument. Checks what kind of kernel the image is. Returns ++ one of uki, pe or unknown. ++ ++ ++ ++ + + Options + The following options are understood: +diff --git a/meson.build b/meson.build +index e0495ed36a..936e612a01 100644 +--- a/meson.build ++++ b/meson.build +@@ -2566,7 +2566,10 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1 + + public_programs += executable( + 'bootctl', +- 'src/boot/bootctl.c', ++ ['src/boot/bootctl.c', ++ 'src/boot/bootctl-uki.c', ++ 'src/boot/bootctl-uki.h', ++ ], + include_directories : includes, + link_with : [boot_link_with], + dependencies : [libblkid], +diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c +new file mode 100644 +index 0000000000..7e8e8a570b +--- /dev/null ++++ b/src/boot/bootctl-uki.c +@@ -0,0 +1,109 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++#include "bootctl-uki.h" ++#include "fd-util.h" ++#include "parse-util.h" ++#include "pe-header.h" ++ ++#define MAX_SECTIONS 96 ++ ++static const uint8_t dos_file_magic[2] = "MZ"; ++static const uint8_t pe_file_magic[4] = "PE\0\0"; ++ ++static const uint8_t name_osrel[8] = ".osrel"; ++static const uint8_t name_linux[8] = ".linux"; ++static const uint8_t name_initrd[8] = ".initrd"; ++ ++static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) { ++ _cleanup_free_ struct PeSectionHeader *sections = NULL; ++ struct DosFileHeader dos; ++ struct PeHeader pe; ++ size_t scount; ++ uint64_t soff, items; ++ int rc; ++ ++ items = fread(&dos, 1, sizeof(dos), uki); ++ if (items != sizeof(dos)) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "DOS header read error"); ++ if (memcmp(dos.Magic, dos_file_magic, sizeof(dos_file_magic)) != 0) ++ goto no_sections; ++ ++ rc = fseek(uki, le32toh(dos.ExeHeader), SEEK_SET); ++ if (rc < 0) ++ return log_error_errno(errno, "seek to PE header"); ++ items = fread(&pe, 1, sizeof(pe), uki); ++ if (items != sizeof(pe)) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "PE header read error"); ++ if (memcmp(pe.Magic, pe_file_magic, sizeof(pe_file_magic)) != 0) ++ goto no_sections; ++ ++ soff = le32toh(dos.ExeHeader) + sizeof(pe) + le16toh(pe.FileHeader.SizeOfOptionalHeader); ++ rc = fseek(uki, soff, SEEK_SET); ++ if (rc < 0) ++ return log_error_errno(errno, "seek to PE section headers"); ++ ++ scount = le16toh(pe.FileHeader.NumberOfSections); ++ if (scount > MAX_SECTIONS) ++ goto no_sections; ++ sections = new(struct PeSectionHeader, scount); ++ if (!sections) ++ return log_oom(); ++ items = fread(sections, sizeof(*sections), scount, uki); ++ if (items != scount) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "PE section header read error"); ++ ++ *ret = TAKE_PTR(sections); ++ *ret_n = scount; ++ return 0; ++ ++no_sections: ++ *ret = NULL; ++ *ret_n = 0; ++ return 0; ++} ++ ++static int find_pe_section(struct PeSectionHeader *sections, size_t scount, ++ const uint8_t *name, size_t namelen, size_t *ret) { ++ for (size_t s = 0; s < scount; s++) { ++ if (memcmp_nn(sections[s].Name, sizeof(sections[s].Name), ++ name, namelen) == 0) { ++ if (ret) ++ *ret = s; ++ return 1; ++ } ++ } ++ return 0; ++} ++ ++static bool is_uki(struct PeSectionHeader *sections, size_t scount) { ++ return (find_pe_section(sections, scount, name_osrel, sizeof(name_osrel), NULL) && ++ find_pe_section(sections, scount, name_linux, sizeof(name_linux), NULL) && ++ find_pe_section(sections, scount, name_initrd, sizeof(name_initrd), NULL)); ++} ++ ++int verb_kernel_identify(int argc, char *argv[], void *userdata) { ++ _cleanup_fclose_ FILE *uki = NULL; ++ _cleanup_free_ struct PeSectionHeader *sections = NULL; ++ size_t scount; ++ int rc; ++ ++ uki = fopen(argv[1], "re"); ++ if (!uki) ++ return log_error_errno(errno, "Failed to open UKI file '%s': %m", argv[1]); ++ ++ rc = pe_sections(uki, §ions, &scount); ++ if (rc < 0) ++ return EXIT_FAILURE; ++ ++ if (sections) { ++ if (is_uki(sections, scount)) { ++ puts("uki"); ++ return EXIT_SUCCESS; ++ } ++ puts("pe"); ++ return EXIT_SUCCESS; ++ } ++ ++ puts("unknown"); ++ return EXIT_SUCCESS; ++} +diff --git a/src/boot/bootctl-uki.h b/src/boot/bootctl-uki.h +new file mode 100644 +index 0000000000..3c1fb5bc6a +--- /dev/null ++++ b/src/boot/bootctl-uki.h +@@ -0,0 +1,3 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++int verb_kernel_identify(int argc, char *argv[], void *userdata); +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index 67fcbcc8cd..58e4af85f8 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -14,6 +14,7 @@ + + #include "alloc-util.h" + #include "blkid-util.h" ++#include "bootctl-uki.h" + #include "bootspec.h" + #include "chase-symlinks.h" + #include "copy.h" +@@ -1453,6 +1454,8 @@ static int help(int argc, char *argv[], void *userdata) { + " remove Remove systemd-boot from the ESP and EFI variables\n" + " is-installed Test whether systemd-boot is installed in the ESP\n" + " random-seed Initialize random seed in ESP and EFI variables\n" ++ "\n%3$skernel Commands:%4$s\n" ++ " kernel-identify Identify kernel image type.\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Print version\n" +@@ -2567,6 +2570,7 @@ static int bootctl_main(int argc, char *argv[]) { + { "update", VERB_ANY, 1, 0, verb_install }, + { "remove", VERB_ANY, 1, 0, verb_remove }, + { "is-installed", VERB_ANY, 1, 0, verb_is_installed }, ++ { "kernel-identify", 2, 2, 0, verb_kernel_identify }, + { "list", VERB_ANY, 1, 0, verb_list }, + { "set-default", 2, 2, 0, verb_set_efivar }, + { "set-oneshot", 2, 2, 0, verb_set_efivar }, diff --git a/SOURCES/0421-bootctl-add-kernel-inspect-command.patch b/SOURCES/0421-bootctl-add-kernel-inspect-command.patch new file mode 100644 index 0000000..fdf869b --- /dev/null +++ b/SOURCES/0421-bootctl-add-kernel-inspect-command.patch @@ -0,0 +1,150 @@ +From 3012047a48f67f6cceba50ce326497aa647074ea Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Tue, 17 Jan 2023 22:06:06 +0100 +Subject: [PATCH] bootctl: add kernel-inspect command + +Takes a kernel image as argument. Prints details about the kernel. + +Signed-off-by: Gerd Hoffmann +(cherry picked from commit a05255981ba5b04f1cf54ea656fbce1dfd9c3a68) + +Resolves: RHEL-16354 +--- + man/bootctl.xml | 6 ++++ + src/boot/bootctl-uki.c | 79 ++++++++++++++++++++++++++++++++++++++++++ + src/boot/bootctl-uki.h | 1 + + src/boot/bootctl.c | 1 + + 4 files changed, 87 insertions(+) + +diff --git a/man/bootctl.xml b/man/bootctl.xml +index 0f992ec383..c12fe93214 100644 +--- a/man/bootctl.xml ++++ b/man/bootctl.xml +@@ -227,6 +227,12 @@ + Takes a kernel image as argument. Checks what kind of kernel the image is. Returns + one of uki, pe or unknown. + ++ ++ ++ kernel ++ ++ Takes a kernel image as argument. Prints details about the kernel. ++ + + + +diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c +index 7e8e8a570b..3085f703a8 100644 +--- a/src/boot/bootctl-uki.c ++++ b/src/boot/bootctl-uki.c +@@ -13,6 +13,8 @@ static const uint8_t pe_file_magic[4] = "PE\0\0"; + static const uint8_t name_osrel[8] = ".osrel"; + static const uint8_t name_linux[8] = ".linux"; + static const uint8_t name_initrd[8] = ".initrd"; ++static const uint8_t name_cmdline[8] = ".cmdline"; ++static const uint8_t name_uname[8] = ".uname"; + + static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) { + _cleanup_free_ struct PeSectionHeader *sections = NULL; +@@ -107,3 +109,80 @@ int verb_kernel_identify(int argc, char *argv[], void *userdata) { + puts("unknown"); + return EXIT_SUCCESS; + } ++ ++static int read_pe_section(FILE *uki, const struct PeSectionHeader *section, ++ void **ret, size_t *ret_n) { ++ _cleanup_free_ void *data = NULL; ++ uint32_t size, bytes; ++ uint64_t soff; ++ int rc; ++ ++ soff = le32toh(section->PointerToRawData); ++ size = le32toh(section->VirtualSize); ++ ++ if (size > 16 * 1024) ++ return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "PE section too big"); ++ ++ rc = fseek(uki, soff, SEEK_SET); ++ if (rc < 0) ++ return log_error_errno(errno, "seek to PE section"); ++ ++ data = malloc(size+1); ++ if (!data) ++ return log_oom(); ++ ((uint8_t*) data)[size] = 0; /* safety NUL byte */ ++ ++ bytes = fread(data, 1, size, uki); ++ if (bytes != size) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "PE section read error"); ++ ++ *ret = TAKE_PTR(data); ++ if (ret_n) ++ *ret_n = size; ++ return 0; ++} ++ ++static void inspect_uki(FILE *uki, struct PeSectionHeader *sections, size_t scount) { ++ _cleanup_free_ char *cmdline = NULL; ++ _cleanup_free_ char *uname = NULL; ++ size_t idx; ++ ++ if (find_pe_section(sections, scount, name_cmdline, sizeof(name_cmdline), &idx)) ++ read_pe_section(uki, sections + idx, (void**)&cmdline, NULL); ++ ++ if (find_pe_section(sections, scount, name_uname, sizeof(name_uname), &idx)) ++ read_pe_section(uki, sections + idx, (void**)&uname, NULL); ++ ++ if (cmdline) ++ printf(" Cmdline: %s\n", cmdline); ++ if (uname) ++ printf(" Version: %s\n", uname); ++} ++ ++int verb_kernel_inspect(int argc, char *argv[], void *userdata) { ++ _cleanup_fclose_ FILE *uki = NULL; ++ _cleanup_free_ struct PeSectionHeader *sections = NULL; ++ size_t scount; ++ int rc; ++ ++ uki = fopen(argv[1], "re"); ++ if (!uki) ++ return log_error_errno(errno, "Failed to open UKI file '%s': %m", argv[1]); ++ ++ rc = pe_sections(uki, §ions, &scount); ++ if (rc < 0) ++ return EXIT_FAILURE; ++ ++ if (sections) { ++ if (is_uki(sections, scount)) { ++ puts("Kernel Type: uki"); ++ inspect_uki(uki, sections, scount); ++ return EXIT_SUCCESS; ++ } ++ puts("Kernel Type: pe"); ++ return EXIT_SUCCESS; ++ } ++ ++ puts("Kernel Type: unknown"); ++ return EXIT_SUCCESS; ++} +diff --git a/src/boot/bootctl-uki.h b/src/boot/bootctl-uki.h +index 3c1fb5bc6a..effb984d80 100644 +--- a/src/boot/bootctl-uki.h ++++ b/src/boot/bootctl-uki.h +@@ -1,3 +1,4 @@ + /* SPDX-License-Identifier: LGPL-2.1-or-later */ + + int verb_kernel_identify(int argc, char *argv[], void *userdata); ++int verb_kernel_inspect(int argc, char *argv[], void *userdata); +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index 58e4af85f8..b98673cd11 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -2571,6 +2571,7 @@ static int bootctl_main(int argc, char *argv[]) { + { "remove", VERB_ANY, 1, 0, verb_remove }, + { "is-installed", VERB_ANY, 1, 0, verb_is_installed }, + { "kernel-identify", 2, 2, 0, verb_kernel_identify }, ++ { "kernel-inspect", 2, 2, 0, verb_kernel_inspect }, + { "list", VERB_ANY, 1, 0, verb_list }, + { "set-default", 2, 2, 0, verb_set_efivar }, + { "set-oneshot", 2, 2, 0, verb_set_efivar }, diff --git a/SOURCES/0422-bootctl-add-kernel-inspect-to-help-text.patch b/SOURCES/0422-bootctl-add-kernel-inspect-to-help-text.patch new file mode 100644 index 0000000..95218e4 --- /dev/null +++ b/SOURCES/0422-bootctl-add-kernel-inspect-to-help-text.patch @@ -0,0 +1,24 @@ +From c1736cd4c9d0cb7b39333280bd30657e68481436 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Fri, 20 Jan 2023 13:30:48 +0100 +Subject: [PATCH] bootctl: add kernel-inspect to --help text + +(cherry picked from commit 24a3b37f12a1a752e5a1379cadc15031b04c3fba) + +Related: RHEL-16354 +--- + src/boot/bootctl.c | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index b98673cd11..cc35147376 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -1456,6 +1456,7 @@ static int help(int argc, char *argv[], void *userdata) { + " random-seed Initialize random seed in ESP and EFI variables\n" + "\n%3$skernel Commands:%4$s\n" + " kernel-identify Identify kernel image type.\n" ++ " kernel-inspect Prints details about the kernel.\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Print version\n" diff --git a/SOURCES/0423-bootctl-drop-full-stop-at-end-of-help-texts.patch b/SOURCES/0423-bootctl-drop-full-stop-at-end-of-help-texts.patch new file mode 100644 index 0000000..ba546ee --- /dev/null +++ b/SOURCES/0423-bootctl-drop-full-stop-at-end-of-help-texts.patch @@ -0,0 +1,29 @@ +From d5d960b0b8db73ffe4cff9d156b0883876d722ac Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 20 Jan 2023 18:29:13 +0100 +Subject: [PATCH] bootctl: drop full stop at end of --help texts + +We never do that, don't do so here either. + +(cherry picked from commit 2b197967bf251ecf58b93fed0f51b9d4cd83fda4) + +Related: RHEL-16354 +--- + src/boot/bootctl.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index cc35147376..681c5bd44f 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -1455,8 +1455,8 @@ static int help(int argc, char *argv[], void *userdata) { + " is-installed Test whether systemd-boot is installed in the ESP\n" + " random-seed Initialize random seed in ESP and EFI variables\n" + "\n%3$skernel Commands:%4$s\n" +- " kernel-identify Identify kernel image type.\n" +- " kernel-inspect Prints details about the kernel.\n" ++ " kernel-identify Identify kernel image type\n" ++ " kernel-inspect Prints details about the kernel\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Print version\n" diff --git a/SOURCES/0424-bootctl-change-section-title-for-kernel-image-comman.patch b/SOURCES/0424-bootctl-change-section-title-for-kernel-image-comman.patch new file mode 100644 index 0000000..9743c47 --- /dev/null +++ b/SOURCES/0424-bootctl-change-section-title-for-kernel-image-comman.patch @@ -0,0 +1,44 @@ +From 6ee66a4736bde681956ef8ab601a72a5cb7b19ed Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 20 Jan 2023 18:30:06 +0100 +Subject: [PATCH] bootctl: change section title for kernel image commands + +Let's call them kernel *images*, not just *kernels*. + +(cherry picked from commit 1e7d6cc07211de425bcc5c408c1f4376d6717305) + +Related: RHEL-16354 +--- + man/bootctl.xml | 2 +- + src/boot/bootctl.c | 4 ++-- + 2 files changed, 3 insertions(+), 3 deletions(-) + +diff --git a/man/bootctl.xml b/man/bootctl.xml +index c12fe93214..d82f12d5bb 100644 +--- a/man/bootctl.xml ++++ b/man/bootctl.xml +@@ -218,7 +218,7 @@ + + + +- <command>kernel</command> Commands ++ Kernel Image Commands + + + +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index 681c5bd44f..4f2a6288fb 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -1454,9 +1454,9 @@ static int help(int argc, char *argv[], void *userdata) { + " remove Remove systemd-boot from the ESP and EFI variables\n" + " is-installed Test whether systemd-boot is installed in the ESP\n" + " random-seed Initialize random seed in ESP and EFI variables\n" +- "\n%3$skernel Commands:%4$s\n" ++ "\n%3$sKernel Image Commands:%4$s\n" + " kernel-identify Identify kernel image type\n" +- " kernel-inspect Prints details about the kernel\n" ++ " kernel-inspect Prints details about the kernel image\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" + " --version Print version\n" diff --git a/SOURCES/0425-bootctl-remove-space-that-should-not-be-there.patch b/SOURCES/0425-bootctl-remove-space-that-should-not-be-there.patch new file mode 100644 index 0000000..11dbf91 --- /dev/null +++ b/SOURCES/0425-bootctl-remove-space-that-should-not-be-there.patch @@ -0,0 +1,25 @@ +From 2eae3094d018587e2550aaf895a7cbdeaea679bd Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 20 Jan 2023 18:40:57 +0100 +Subject: [PATCH] bootctl: remove space that should not be there + +(cherry picked from commit e684d2d5f85a82ed47eb063809145540df01ae1a) + +Related: RHEL-16354 +--- + src/boot/bootctl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index 4f2a6288fb..3044f67b5a 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -1433,7 +1433,7 @@ static int help(int argc, char *argv[], void *userdata) { + if (r < 0) + return log_oom(); + +- printf("%1$s [OPTIONS...] COMMAND ...\n" ++ printf("%1$s [OPTIONS...] COMMAND ...\n" + "\n%5$sControl EFI firmware boot settings and manage boot loader.%6$s\n" + "\n%3$sGeneric EFI Firmware/Boot Loader Commands:%4$s\n" + " status Show status of installed boot loader and EFI variables\n" diff --git a/SOURCES/0426-bootctl-kernel-inspect-print-os-info.patch b/SOURCES/0426-bootctl-kernel-inspect-print-os-info.patch new file mode 100644 index 0000000..bf20bc1 --- /dev/null +++ b/SOURCES/0426-bootctl-kernel-inspect-print-os-info.patch @@ -0,0 +1,73 @@ +From 6204aa43883fdf02d72bd0db6d3cfbfa7c075213 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Fri, 20 Jan 2023 15:40:36 +0100 +Subject: [PATCH] bootctl: kernel-inspect: print os info + +(cherry picked from commit 2d4260482cb8463f4de9502efd26bf8c64262669) + +Related: RHEL-16354 +--- + src/boot/bootctl-uki.c | 29 ++++++++++++++++++++++++++++- + 1 file changed, 28 insertions(+), 1 deletion(-) + +diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c +index 3085f703a8..6bdf926350 100644 +--- a/src/boot/bootctl-uki.c ++++ b/src/boot/bootctl-uki.c +@@ -1,6 +1,7 @@ + /* SPDX-License-Identifier: LGPL-2.1-or-later */ + + #include "bootctl-uki.h" ++#include "env-file.h" + #include "fd-util.h" + #include "parse-util.h" + #include "pe-header.h" +@@ -142,10 +143,31 @@ static int read_pe_section(FILE *uki, const struct PeSectionHeader *section, + return 0; + } + ++static void inspect_osrel(char *osrel, size_t osrel_size) { ++ _cleanup_fclose_ FILE *s = NULL; ++ _cleanup_free_ char *pname = NULL, *name = NULL; ++ int r; ++ ++ assert(osrel); ++ s = fmemopen(osrel, osrel_size, "r"); ++ if (!s) ++ return (void) log_warning_errno(errno, "Failed to open embedded os-release file, ignoring: %m"); ++ ++ r = parse_env_file(s, NULL, ++ "PRETTY_NAME", &pname, ++ "NAME", &name); ++ if (r < 0) ++ return (void) log_warning_errno(r, "Failed to parse embedded os-release file, ignoring: %m"); ++ ++ if (pname || name) ++ printf(" OS: %s\n", pname ?: name); ++} ++ + static void inspect_uki(FILE *uki, struct PeSectionHeader *sections, size_t scount) { + _cleanup_free_ char *cmdline = NULL; + _cleanup_free_ char *uname = NULL; +- size_t idx; ++ _cleanup_free_ char *osrel = NULL; ++ size_t osrel_size, idx; + + if (find_pe_section(sections, scount, name_cmdline, sizeof(name_cmdline), &idx)) + read_pe_section(uki, sections + idx, (void**)&cmdline, NULL); +@@ -153,10 +175,15 @@ static void inspect_uki(FILE *uki, struct PeSectionHeader *sections, size_t scou + if (find_pe_section(sections, scount, name_uname, sizeof(name_uname), &idx)) + read_pe_section(uki, sections + idx, (void**)&uname, NULL); + ++ if (find_pe_section(sections, scount, name_osrel, sizeof(name_osrel), &idx)) ++ read_pe_section(uki, sections + idx, (void**)&osrel, &osrel_size); ++ + if (cmdline) + printf(" Cmdline: %s\n", cmdline); + if (uname) + printf(" Version: %s\n", uname); ++ if (osrel) ++ inspect_osrel(osrel, osrel_size); + } + + int verb_kernel_inspect(int argc, char *argv[], void *userdata) { diff --git a/SOURCES/0427-bootctl-uki-several-coding-style-fixlets.patch b/SOURCES/0427-bootctl-uki-several-coding-style-fixlets.patch new file mode 100644 index 0000000..17116fd --- /dev/null +++ b/SOURCES/0427-bootctl-uki-several-coding-style-fixlets.patch @@ -0,0 +1,207 @@ +From a9c7fe86260ca906c7378503e059eca0ad014947 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Tue, 24 Jan 2023 22:59:59 +0900 +Subject: [PATCH] bootctl-uki: several coding style fixlets + +Mostly follow-ups for #26082. + +(cherry picked from commit 5b532e14e3ac1d8f8cef90fd8c5b1ce277458ae7) + +Related: RHEL-16354 +--- + src/boot/bootctl-uki.c | 105 +++++++++++++++++++++++------------------ + 1 file changed, 60 insertions(+), 45 deletions(-) + +diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c +index 6bdf926350..3492fa548f 100644 +--- a/src/boot/bootctl-uki.c ++++ b/src/boot/bootctl-uki.c +@@ -23,7 +23,10 @@ static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) { + struct PeHeader pe; + size_t scount; + uint64_t soff, items; +- int rc; ++ ++ assert(uki); ++ assert(ret); ++ assert(ret_n); + + items = fread(&dos, 1, sizeof(dos), uki); + if (items != sizeof(dos)) +@@ -31,9 +34,9 @@ static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) { + if (memcmp(dos.Magic, dos_file_magic, sizeof(dos_file_magic)) != 0) + goto no_sections; + +- rc = fseek(uki, le32toh(dos.ExeHeader), SEEK_SET); +- if (rc < 0) ++ if (fseek(uki, le32toh(dos.ExeHeader), SEEK_SET) < 0) + return log_error_errno(errno, "seek to PE header"); ++ + items = fread(&pe, 1, sizeof(pe), uki); + if (items != sizeof(pe)) + return log_error_errno(SYNTHETIC_ERRNO(EIO), "PE header read error"); +@@ -41,8 +44,7 @@ static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) { + goto no_sections; + + soff = le32toh(dos.ExeHeader) + sizeof(pe) + le16toh(pe.FileHeader.SizeOfOptionalHeader); +- rc = fseek(uki, soff, SEEK_SET); +- if (rc < 0) ++ if (fseek(uki, soff, SEEK_SET) < 0) + return log_error_errno(errno, "seek to PE section headers"); + + scount = le16toh(pe.FileHeader.NumberOfSections); +@@ -65,58 +67,72 @@ no_sections: + return 0; + } + +-static int find_pe_section(struct PeSectionHeader *sections, size_t scount, +- const uint8_t *name, size_t namelen, size_t *ret) { +- for (size_t s = 0; s < scount; s++) { +- if (memcmp_nn(sections[s].Name, sizeof(sections[s].Name), +- name, namelen) == 0) { ++static bool find_pe_section( ++ struct PeSectionHeader *sections, ++ size_t scount, ++ const uint8_t *name, ++ size_t namelen, ++ size_t *ret) { ++ ++ assert(sections || scount == 0); ++ assert(name || namelen == 0); ++ ++ for (size_t s = 0; s < scount; s++) ++ if (memcmp_nn(sections[s].Name, sizeof(sections[s].Name), name, namelen) == 0) { + if (ret) + *ret = s; +- return 1; ++ return true; + } +- } +- return 0; ++ ++ return false; + } + + static bool is_uki(struct PeSectionHeader *sections, size_t scount) { +- return (find_pe_section(sections, scount, name_osrel, sizeof(name_osrel), NULL) && ++ assert(sections || scount == 0); ++ ++ return ++ find_pe_section(sections, scount, name_osrel, sizeof(name_osrel), NULL) && + find_pe_section(sections, scount, name_linux, sizeof(name_linux), NULL) && +- find_pe_section(sections, scount, name_initrd, sizeof(name_initrd), NULL)); ++ find_pe_section(sections, scount, name_initrd, sizeof(name_initrd), NULL); + } + + int verb_kernel_identify(int argc, char *argv[], void *userdata) { + _cleanup_fclose_ FILE *uki = NULL; + _cleanup_free_ struct PeSectionHeader *sections = NULL; + size_t scount; +- int rc; ++ int r; + + uki = fopen(argv[1], "re"); + if (!uki) + return log_error_errno(errno, "Failed to open UKI file '%s': %m", argv[1]); + +- rc = pe_sections(uki, §ions, &scount); +- if (rc < 0) +- return EXIT_FAILURE; ++ r = pe_sections(uki, §ions, &scount); ++ if (r < 0) ++ return r; + +- if (sections) { +- if (is_uki(sections, scount)) { +- puts("uki"); +- return EXIT_SUCCESS; +- } ++ if (!sections) ++ puts("unknown"); ++ else if (is_uki(sections, scount)) ++ puts("uki"); ++ else + puts("pe"); +- return EXIT_SUCCESS; +- } + +- puts("unknown"); + return EXIT_SUCCESS; + } + +-static int read_pe_section(FILE *uki, const struct PeSectionHeader *section, +- void **ret, size_t *ret_n) { ++static int read_pe_section( ++ FILE *uki, ++ const struct PeSectionHeader *section, ++ void **ret, ++ size_t *ret_n) { ++ + _cleanup_free_ void *data = NULL; + uint32_t size, bytes; + uint64_t soff; +- int rc; ++ ++ assert(uki); ++ assert(section); ++ assert(ret); + + soff = le32toh(section->PointerToRawData); + size = le32toh(section->VirtualSize); +@@ -124,8 +140,7 @@ static int read_pe_section(FILE *uki, const struct PeSectionHeader *section, + if (size > 16 * 1024) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "PE section too big"); + +- rc = fseek(uki, soff, SEEK_SET); +- if (rc < 0) ++ if (fseek(uki, soff, SEEK_SET) < 0) + return log_error_errno(errno, "seek to PE section"); + + data = malloc(size+1); +@@ -169,6 +184,9 @@ static void inspect_uki(FILE *uki, struct PeSectionHeader *sections, size_t scou + _cleanup_free_ char *osrel = NULL; + size_t osrel_size, idx; + ++ assert(uki); ++ assert(sections || scount == 0); ++ + if (find_pe_section(sections, scount, name_cmdline, sizeof(name_cmdline), &idx)) + read_pe_section(uki, sections + idx, (void**)&cmdline, NULL); + +@@ -190,26 +208,23 @@ int verb_kernel_inspect(int argc, char *argv[], void *userdata) { + _cleanup_fclose_ FILE *uki = NULL; + _cleanup_free_ struct PeSectionHeader *sections = NULL; + size_t scount; +- int rc; ++ int r; + + uki = fopen(argv[1], "re"); + if (!uki) + return log_error_errno(errno, "Failed to open UKI file '%s': %m", argv[1]); + +- rc = pe_sections(uki, §ions, &scount); +- if (rc < 0) +- return EXIT_FAILURE; ++ r = pe_sections(uki, §ions, &scount); ++ if (r < 0) ++ return r; + +- if (sections) { +- if (is_uki(sections, scount)) { +- puts("Kernel Type: uki"); +- inspect_uki(uki, sections, scount); +- return EXIT_SUCCESS; +- } ++ if (!sections) ++ puts("Kernel Type: unknown"); ++ else if (is_uki(sections, scount)) { ++ puts("Kernel Type: uki"); ++ inspect_uki(uki, sections, scount); ++ } else + puts("Kernel Type: pe"); +- return EXIT_SUCCESS; +- } + +- puts("Kernel Type: unknown"); + return EXIT_SUCCESS; + } diff --git a/SOURCES/0428-tree-wide-unify-how-we-pick-OS-pretty-name-to-displa.patch b/SOURCES/0428-tree-wide-unify-how-we-pick-OS-pretty-name-to-displa.patch new file mode 100644 index 0000000..bd3ff4f --- /dev/null +++ b/SOURCES/0428-tree-wide-unify-how-we-pick-OS-pretty-name-to-displa.patch @@ -0,0 +1,171 @@ +From a83545ba5d0cc1b29f2bb4e2b794ce14b138e3fe Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 23 Jan 2023 12:28:38 +0100 +Subject: [PATCH] tree-wide: unify how we pick OS pretty name to display + +(cherry picked from commit 02b7005e38db756711cd6463bda34e93cf304c3c) + +Related: RHEL-16354 +--- + src/analyze/analyze-plot.c | 3 ++- + src/basic/os-util.c | 9 +++++++++ + src/basic/os-util.h | 2 ++ + src/core/main.c | 2 +- + src/firstboot/firstboot.c | 5 +++-- + src/hostname/hostnamed.c | 7 ++++++- + src/journal-remote/journal-gatewayd.c | 9 ++++++--- + 7 files changed, 29 insertions(+), 8 deletions(-) + +diff --git a/src/analyze/analyze-plot.c b/src/analyze/analyze-plot.c +index 24f4add099..8aca691b3d 100644 +--- a/src/analyze/analyze-plot.c ++++ b/src/analyze/analyze-plot.c +@@ -6,6 +6,7 @@ + #include "bus-error.h" + #include "bus-map-properties.h" + #include "format-table.h" ++#include "os-util.h" + #include "sort-util.h" + #include "version.h" + +@@ -283,7 +284,7 @@ static int produce_plot_as_svg( + svg("%s", pretty_times); + if (host) + svg("%s %s (%s %s %s) %s %s", +- isempty(host->os_pretty_name) ? "Linux" : host->os_pretty_name, ++ os_release_pretty_name(host->os_pretty_name, NULL), + strempty(host->hostname), + strempty(host->kernel_name), + strempty(host->kernel_release), +diff --git a/src/basic/os-util.c b/src/basic/os-util.c +index 8f8bb0881e..66cbfc76a5 100644 +--- a/src/basic/os-util.c ++++ b/src/basic/os-util.c +@@ -370,3 +370,12 @@ int os_release_support_ended(const char *support_end, bool quiet) { + usec_t ts = now(CLOCK_REALTIME); + return DIV_ROUND_UP(ts, USEC_PER_SEC) > (usec_t) eol; + } ++ ++const char *os_release_pretty_name(const char *pretty_name, const char *name) { ++ /* Distills a "pretty" name to show from os-release data. First argument is supposed to be the ++ * PRETTY_NAME= field, the second one the NAME= field. This function is trivial, of course, and ++ * exists mostly to ensure we use the same logic wherever possible. */ ++ ++ return empty_to_null(pretty_name) ?: ++ empty_to_null(name) ?: "Linux"; ++} +diff --git a/src/basic/os-util.h b/src/basic/os-util.h +index d22f5ab8e7..1239f6f6b7 100644 +--- a/src/basic/os-util.h ++++ b/src/basic/os-util.h +@@ -33,3 +33,5 @@ int load_os_release_pairs(const char *root, char ***ret); + int load_os_release_pairs_with_prefix(const char *root, const char *prefix, char ***ret); + + int os_release_support_ended(const char *support_end, bool quiet); ++ ++const char *os_release_pretty_name(const char *pretty_name, const char *name); +diff --git a/src/core/main.c b/src/core/main.c +index d3ec526e7e..0e2e5448bb 100644 +--- a/src/core/main.c ++++ b/src/core/main.c +@@ -1339,7 +1339,7 @@ static int os_release_status(void) { + return log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to read os-release file, ignoring: %m"); + +- const char *label = empty_to_null(pretty_name) ?: empty_to_null(name) ?: "Linux"; ++ const char *label = os_release_pretty_name(pretty_name, name); + + if (show_status_on(arg_show_status)) { + if (log_get_show_color()) +diff --git a/src/firstboot/firstboot.c b/src/firstboot/firstboot.c +index 63db78b52d..10eda1d302 100644 +--- a/src/firstboot/firstboot.c ++++ b/src/firstboot/firstboot.c +@@ -96,7 +96,7 @@ static bool press_any_key(void) { + } + + static void print_welcome(void) { +- _cleanup_free_ char *pretty_name = NULL, *ansi_color = NULL; ++ _cleanup_free_ char *pretty_name = NULL, *os_name = NULL, *ansi_color = NULL; + static bool done = false; + const char *pn, *ac; + int r; +@@ -110,12 +110,13 @@ static void print_welcome(void) { + r = parse_os_release( + arg_root, + "PRETTY_NAME", &pretty_name, ++ "NAME", &os_name, + "ANSI_COLOR", &ansi_color); + if (r < 0) + log_full_errno(r == -ENOENT ? LOG_DEBUG : LOG_WARNING, r, + "Failed to read os-release file, ignoring: %m"); + +- pn = isempty(pretty_name) ? "Linux" : pretty_name; ++ pn = os_release_pretty_name(pretty_name, os_name); + ac = isempty(ansi_color) ? "0" : ansi_color; + + if (colors_enabled()) +diff --git a/src/hostname/hostnamed.c b/src/hostname/hostnamed.c +index 486b093062..2a675caada 100644 +--- a/src/hostname/hostnamed.c ++++ b/src/hostname/hostnamed.c +@@ -147,6 +147,7 @@ static void context_read_machine_info(Context *c) { + } + + static void context_read_os_release(Context *c) { ++ _cleanup_free_ char *os_name = NULL, *os_pretty_name = NULL; + struct stat current_stat = {}; + int r; + +@@ -163,12 +164,16 @@ static void context_read_os_release(Context *c) { + (UINT64_C(1) << PROP_OS_HOME_URL)); + + r = parse_os_release(NULL, +- "PRETTY_NAME", &c->data[PROP_OS_PRETTY_NAME], ++ "PRETTY_NAME", &os_pretty_name, ++ "NAME", &os_name, + "CPE_NAME", &c->data[PROP_OS_CPE_NAME], + "HOME_URL", &c->data[PROP_OS_HOME_URL]); + if (r < 0 && r != -ENOENT) + log_warning_errno(r, "Failed to read os-release file, ignoring: %m"); + ++ if (free_and_strdup(&c->data[PROP_OS_PRETTY_NAME], os_release_pretty_name(os_pretty_name, os_name)) < 0) ++ log_oom(); ++ + c->etc_os_release_stat = current_stat; + } + +diff --git a/src/journal-remote/journal-gatewayd.c b/src/journal-remote/journal-gatewayd.c +index 34def4670e..e848ae5026 100644 +--- a/src/journal-remote/journal-gatewayd.c ++++ b/src/journal-remote/journal-gatewayd.c +@@ -732,7 +732,7 @@ static int request_handler_machine( + _cleanup_(MHD_destroy_responsep) struct MHD_Response *response = NULL; + RequestMeta *m = ASSERT_PTR(connection_cls); + int r; +- _cleanup_free_ char* hostname = NULL, *os_name = NULL; ++ _cleanup_free_ char* hostname = NULL, *pretty_name = NULL, *os_name = NULL; + uint64_t cutoff_from = 0, cutoff_to = 0, usage = 0; + sd_id128_t mid, bid; + _cleanup_free_ char *v = NULL, *json = NULL; +@@ -763,7 +763,10 @@ static int request_handler_machine( + if (r < 0) + return mhd_respondf(connection, r, MHD_HTTP_INTERNAL_SERVER_ERROR, "Failed to determine disk usage: %m"); + +- (void) parse_os_release(NULL, "PRETTY_NAME", &os_name); ++ (void) parse_os_release( ++ NULL, ++ "PRETTY_NAME", &pretty_name, ++ "NAME=", &os_name); + (void) get_virtualization(&v); + + r = asprintf(&json, +@@ -778,7 +781,7 @@ static int request_handler_machine( + SD_ID128_FORMAT_VAL(mid), + SD_ID128_FORMAT_VAL(bid), + hostname_cleanup(hostname), +- os_name ? os_name : "Linux", ++ os_release_pretty_name(pretty_name, os_name), + v ? v : "bare", + usage, + cutoff_from, diff --git a/SOURCES/0429-bootctl-uki-several-follow-ups-for-inspect_osrel.patch b/SOURCES/0429-bootctl-uki-several-follow-ups-for-inspect_osrel.patch new file mode 100644 index 0000000..1397d76 --- /dev/null +++ b/SOURCES/0429-bootctl-uki-several-follow-ups-for-inspect_osrel.patch @@ -0,0 +1,102 @@ +From 5374c8fb7c32e79f9dcd24333e5c117bd8963a1a Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Wed, 25 Jan 2023 11:05:46 +0900 +Subject: [PATCH] bootctl-uki: several follow-ups for inspect_osrel() + +Follow-ups for #26124 and #26158. + +- use os_release_pretty_name(), +- constify the buffer passed to inspect_osrel(), +- propagate errors in inspect_osrele(), and ignore them in the caller + side, +- and several coding style fixlets. + +(cherry picked from commit 1b7586df976d7b033b4901a099337d83578bb8f1) + +Related: RHEL-16354 +--- + src/boot/bootctl-uki.c | 36 ++++++++++++++++++++---------------- + 1 file changed, 20 insertions(+), 16 deletions(-) + +diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c +index 3492fa548f..fd249c43fb 100644 +--- a/src/boot/bootctl-uki.c ++++ b/src/boot/bootctl-uki.c +@@ -3,6 +3,7 @@ + #include "bootctl-uki.h" + #include "env-file.h" + #include "fd-util.h" ++#include "os-util.h" + #include "parse-util.h" + #include "pe-header.h" + +@@ -158,50 +159,53 @@ static int read_pe_section( + return 0; + } + +-static void inspect_osrel(char *osrel, size_t osrel_size) { ++static int inspect_osrel(const void *osrel, size_t osrel_size) { + _cleanup_fclose_ FILE *s = NULL; + _cleanup_free_ char *pname = NULL, *name = NULL; + int r; + +- assert(osrel); +- s = fmemopen(osrel, osrel_size, "r"); ++ assert(osrel || osrel_size == 0); ++ ++ if (!osrel) ++ return 0; ++ ++ s = fmemopen((void*) osrel, osrel_size, "r"); + if (!s) +- return (void) log_warning_errno(errno, "Failed to open embedded os-release file, ignoring: %m"); ++ return log_warning_errno(errno, "Failed to open embedded os-release file, ignoring: %m"); + + r = parse_env_file(s, NULL, + "PRETTY_NAME", &pname, + "NAME", &name); + if (r < 0) +- return (void) log_warning_errno(r, "Failed to parse embedded os-release file, ignoring: %m"); ++ return log_warning_errno(r, "Failed to parse embedded os-release file, ignoring: %m"); + +- if (pname || name) +- printf(" OS: %s\n", pname ?: name); ++ printf(" OS: %s\n", os_release_pretty_name(pname, name)); ++ return 0; + } + + static void inspect_uki(FILE *uki, struct PeSectionHeader *sections, size_t scount) { +- _cleanup_free_ char *cmdline = NULL; +- _cleanup_free_ char *uname = NULL; +- _cleanup_free_ char *osrel = NULL; +- size_t osrel_size, idx; ++ _cleanup_free_ char *cmdline = NULL, *uname = NULL; ++ _cleanup_free_ void *osrel = NULL; ++ size_t osrel_size = 0, idx; + + assert(uki); + assert(sections || scount == 0); + + if (find_pe_section(sections, scount, name_cmdline, sizeof(name_cmdline), &idx)) +- read_pe_section(uki, sections + idx, (void**)&cmdline, NULL); ++ read_pe_section(uki, sections + idx, (void**) &cmdline, NULL); + + if (find_pe_section(sections, scount, name_uname, sizeof(name_uname), &idx)) +- read_pe_section(uki, sections + idx, (void**)&uname, NULL); ++ read_pe_section(uki, sections + idx, (void**) &uname, NULL); + + if (find_pe_section(sections, scount, name_osrel, sizeof(name_osrel), &idx)) +- read_pe_section(uki, sections + idx, (void**)&osrel, &osrel_size); ++ read_pe_section(uki, sections + idx, &osrel, &osrel_size); + + if (cmdline) + printf(" Cmdline: %s\n", cmdline); + if (uname) + printf(" Version: %s\n", uname); +- if (osrel) +- inspect_osrel(osrel, osrel_size); ++ ++ (void) inspect_osrel(osrel, osrel_size); + } + + int verb_kernel_inspect(int argc, char *argv[], void *userdata) { diff --git a/SOURCES/0430-bootctl-Add-missing-m.patch b/SOURCES/0430-bootctl-Add-missing-m.patch new file mode 100644 index 0000000..3775ab1 --- /dev/null +++ b/SOURCES/0430-bootctl-Add-missing-m.patch @@ -0,0 +1,25 @@ +From 47a9030cad80a684b9affcc2c35ef264e7a8b145 Mon Sep 17 00:00:00 2001 +From: Daan De Meyer +Date: Thu, 9 Feb 2023 10:44:35 +0100 +Subject: [PATCH] bootctl: Add missing %m + +(cherry picked from commit 3b42ffe590c5728af50feb138890a44264c4b02e) + +Related: RHEL-16354 +--- + src/boot/bootctl.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/boot/bootctl.c b/src/boot/bootctl.c +index 3044f67b5a..a0ca2afec2 100644 +--- a/src/boot/bootctl.c ++++ b/src/boot/bootctl.c +@@ -1420,7 +1420,7 @@ static int install_entry_token(void) { + + r = write_string_file("/etc/kernel/entry-token", arg_entry_token, WRITE_STRING_FILE_CREATE|WRITE_STRING_FILE_ATOMIC|WRITE_STRING_FILE_MKDIR_0755); + if (r < 0) +- return log_error_errno(r, "Failed to write entry token '%s' to /etc/kernel/entry-token", arg_entry_token); ++ return log_error_errno(r, "Failed to write entry token '%s' to /etc/kernel/entry-token: %m", arg_entry_token); + + return 0; + } diff --git a/SOURCES/0431-bootctl-tweak-DOS-header-magic-check.patch b/SOURCES/0431-bootctl-tweak-DOS-header-magic-check.patch new file mode 100644 index 0000000..72c6840 --- /dev/null +++ b/SOURCES/0431-bootctl-tweak-DOS-header-magic-check.patch @@ -0,0 +1,41 @@ +From 618e38b8b775f45c0a18975ae33753b92c954092 Mon Sep 17 00:00:00 2001 +From: Gerd Hoffmann +Date: Tue, 24 Jan 2023 10:28:25 +0100 +Subject: [PATCH] bootctl: tweak DOS header magic check + +Read the magic first, try reading the full DOS exe header only in case +the magic check succeeds. + +This avoids throwing an header read error on small dummy files as used +by test-kernel-install. + +(cherry picked from commit 78088b8f43717a43661cd2c1627a9860904c4794) + +Related: RHEL-16354 +--- + src/boot/bootctl-uki.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/src/boot/bootctl-uki.c b/src/boot/bootctl-uki.c +index fd249c43fb..d90a850b1c 100644 +--- a/src/boot/bootctl-uki.c ++++ b/src/boot/bootctl-uki.c +@@ -30,11 +30,16 @@ static int pe_sections(FILE *uki, struct PeSectionHeader **ret, size_t *ret_n) { + assert(ret_n); + + items = fread(&dos, 1, sizeof(dos), uki); +- if (items != sizeof(dos)) +- return log_error_errno(SYNTHETIC_ERRNO(EIO), "DOS header read error"); ++ if (items < sizeof(dos.Magic)) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "File is smaller than DOS magic (got %"PRIu64" of %zu bytes)", ++ items, sizeof(dos.Magic)); + if (memcmp(dos.Magic, dos_file_magic, sizeof(dos_file_magic)) != 0) + goto no_sections; + ++ if (items != sizeof(dos)) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "File is smaller than DOS header (got %"PRIu64" of %zu bytes)", ++ items, sizeof(dos)); ++ + if (fseek(uki, le32toh(dos.ExeHeader), SEEK_SET) < 0) + return log_error_errno(errno, "seek to PE header"); + diff --git a/SOURCES/0432-meson-fix-installation-of-ukify.patch b/SOURCES/0432-meson-fix-installation-of-ukify.patch new file mode 100644 index 0000000..20b3ac1 --- /dev/null +++ b/SOURCES/0432-meson-fix-installation-of-ukify.patch @@ -0,0 +1,30 @@ +From a1bd733809ff01c64a8a304a45e57277a5a98463 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Fri, 8 Dec 2023 19:35:37 +0100 +Subject: [PATCH] meson: fix installation of ukify + +ln_s was added in upstream later on. It's not present in this branch. +Fixup for b98da2d9e7. + +Related: RHEL-13199 +--- + meson.build | 6 +++--- + 1 file changed, 3 insertions(+), 3 deletions(-) + +diff --git a/meson.build b/meson.build +index 936e612a01..e09c426a72 100644 +--- a/meson.build ++++ b/meson.build +@@ -3868,9 +3868,9 @@ ukify = custom_target( + if want_ukify + public_programs += ukify + +- meson.add_install_script(sh, '-c', +- ln_s.format(bindir / 'ukify', +- rootlibexecdir / 'ukify')) ++ meson.add_install_script(meson_make_symlink, ++ bindir / 'ukify', ++ rootlibexecdir / 'ukify') + endif + + if want_tests != 'false' and want_kernel_install diff --git a/SOURCES/0433-sd-id128-introduce-id128_hash_ops_free.patch b/SOURCES/0433-sd-id128-introduce-id128_hash_ops_free.patch new file mode 100644 index 0000000..64398e9 --- /dev/null +++ b/SOURCES/0433-sd-id128-introduce-id128_hash_ops_free.patch @@ -0,0 +1,37 @@ +From 81802cf297a05d202aae5de21673fcc7064f9b7d Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Fri, 28 Oct 2022 09:08:09 +0900 +Subject: [PATCH] sd-id128: introduce id128_hash_ops_free + +(cherry picked from commit 3e61656fab869bb40f019c38c3347885238294de) + +Related: RHEL-5988 +--- + src/libsystemd/sd-id128/id128-util.c | 1 + + src/libsystemd/sd-id128/id128-util.h | 1 + + 2 files changed, 2 insertions(+) + +diff --git a/src/libsystemd/sd-id128/id128-util.c b/src/libsystemd/sd-id128/id128-util.c +index 4f52c14f64..cef340f3bc 100644 +--- a/src/libsystemd/sd-id128/id128-util.c ++++ b/src/libsystemd/sd-id128/id128-util.c +@@ -184,6 +184,7 @@ sd_id128_t id128_make_v4_uuid(sd_id128_t id) { + } + + DEFINE_HASH_OPS(id128_hash_ops, sd_id128_t, id128_hash_func, id128_compare_func); ++DEFINE_HASH_OPS_WITH_KEY_DESTRUCTOR(id128_hash_ops_free, sd_id128_t, id128_hash_func, id128_compare_func, free); + + int id128_get_product(sd_id128_t *ret) { + sd_id128_t uuid; +diff --git a/src/libsystemd/sd-id128/id128-util.h b/src/libsystemd/sd-id128/id128-util.h +index 17b180c10c..9d8fe93641 100644 +--- a/src/libsystemd/sd-id128/id128-util.h ++++ b/src/libsystemd/sd-id128/id128-util.h +@@ -30,6 +30,7 @@ int id128_write(const char *p, Id128Format f, sd_id128_t id, bool do_sync); + void id128_hash_func(const sd_id128_t *p, struct siphash *state); + int id128_compare_func(const sd_id128_t *a, const sd_id128_t *b) _pure_; + extern const struct hash_ops id128_hash_ops; ++extern const struct hash_ops id128_hash_ops_free; + + sd_id128_t id128_make_v4_uuid(sd_id128_t id); + diff --git a/SOURCES/0434-udevadm-trigger-allow-to-fallback-without-synthetic-.patch b/SOURCES/0434-udevadm-trigger-allow-to-fallback-without-synthetic-.patch new file mode 100644 index 0000000..7a0131e --- /dev/null +++ b/SOURCES/0434-udevadm-trigger-allow-to-fallback-without-synthetic-.patch @@ -0,0 +1,104 @@ +From 597c41edd3e94f2c16209359fbd8de7ed44225d7 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Fri, 28 Oct 2022 10:14:09 +0900 +Subject: [PATCH] udevadm-trigger: allow to fallback without synthetic UUID + only first time + +If a device is successfully triggered with synthetic UUID, then that means +the kernel support it. Hence, it is not necessary to fallback without UUID +for later devices. + +(cherry picked from commit b15039425feba8f316fb306b75d96e2f0f0b82fa) + +Related: RHEL-5988 +--- + src/udev/udevadm-trigger.c | 22 ++++++++++++++-------- + 1 file changed, 14 insertions(+), 8 deletions(-) + +diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c +index 1d421064d7..cda31edd75 100644 +--- a/src/udev/udevadm-trigger.c ++++ b/src/udev/udevadm-trigger.c +@@ -26,17 +26,20 @@ static bool arg_verbose = false; + static bool arg_dry_run = false; + static bool arg_quiet = false; + static bool arg_uuid = false; ++static bool arg_settle = false; + + static int exec_list( + sd_device_enumerator *e, + sd_device_action_t action, + Hashmap *settle_hashmap) { + +- bool skip_uuid_logic = false; ++ int uuid_supported = -1; + const char *action_str; + sd_device *d; + int r, ret = 0; + ++ assert(e); ++ + action_str = device_action_to_string(action); + + FOREACH_DEVICE_AND_SUBSYSTEM(e, d) { +@@ -57,14 +60,14 @@ static int exec_list( + + /* Use the UUID mode if the user explicitly asked for it, or if --settle has been specified, + * so that we can recognize our own uevent. */ +- r = sd_device_trigger_with_uuid(d, action, (arg_uuid || settle_hashmap) && !skip_uuid_logic ? &id : NULL); +- if (r == -EINVAL && !arg_uuid && settle_hashmap && !skip_uuid_logic) { ++ r = sd_device_trigger_with_uuid(d, action, (arg_uuid || arg_settle) && uuid_supported != 0 ? &id : NULL); ++ if (r == -EINVAL && !arg_uuid && arg_settle && uuid_supported < 0) { + /* If we specified a UUID because of the settling logic, and we got EINVAL this might + * be caused by an old kernel which doesn't know the UUID logic (pre-4.13). Let's try + * if it works without the UUID logic then. */ + r = sd_device_trigger(d, action); + if (r != -EINVAL) +- skip_uuid_logic = true; /* dropping the uuid stuff changed the return code, ++ uuid_supported = false; /* dropping the uuid stuff changed the return code, + * hence don't bother next time */ + } + if (r < 0) { +@@ -108,11 +111,14 @@ static int exec_list( + continue; + } + ++ if (uuid_supported < 0) ++ uuid_supported = true; ++ + /* If the user asked for it, write event UUID to stdout */ + if (arg_uuid) + printf(SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id)); + +- if (settle_hashmap) { ++ if (arg_settle) { + _cleanup_free_ sd_id128_t *mid = NULL; + _cleanup_free_ char *sp = NULL; + +@@ -285,7 +291,7 @@ int trigger_main(int argc, char *argv[], void *userdata) { + _cleanup_(sd_event_unrefp) sd_event *event = NULL; + _cleanup_hashmap_free_ Hashmap *settle_hashmap = NULL; + usec_t ping_timeout_usec = 5 * USEC_PER_SEC; +- bool settle = false, ping = false; ++ bool ping = false; + int c, r; + + if (running_in_chroot() > 0) { +@@ -389,7 +395,7 @@ int trigger_main(int argc, char *argv[], void *userdata) { + break; + } + case 'w': +- settle = true; ++ arg_settle = true; + break; + + case ARG_NAME: { +@@ -477,7 +483,7 @@ int trigger_main(int argc, char *argv[], void *userdata) { + return log_error_errno(r, "Failed to add parent match '%s': %m", argv[optind]); + } + +- if (settle) { ++ if (arg_settle) { + settle_hashmap = hashmap_new(&path_hash_ops_free_free); + if (!settle_hashmap) + return log_oom(); diff --git a/SOURCES/0435-udevadm-trigger-settle-with-synthetic-UUID-if-the-ke.patch b/SOURCES/0435-udevadm-trigger-settle-with-synthetic-UUID-if-the-ke.patch new file mode 100644 index 0000000..e31953e --- /dev/null +++ b/SOURCES/0435-udevadm-trigger-settle-with-synthetic-UUID-if-the-ke.patch @@ -0,0 +1,196 @@ +From 73dbfdaab1d633e3a1ae96cc15c551eaa2fd4243 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Fri, 28 Oct 2022 10:21:57 +0900 +Subject: [PATCH] udevadm-trigger: settle with synthetic UUID if the kernel + support it + +If the kernel support synthetic UUID in uevent, then let's assume that +the UUID is unique, and check only if the received UUID matches we +specified. + +Partially fixes #25115. + +(cherry picked from commit dfbd824a0b780310d7f865a6ea0d60434d924683) + +Related: RHEL-5988 +--- + src/udev/udevadm-trigger.c | 82 +++++++++++++++++++------------------- + 1 file changed, 40 insertions(+), 42 deletions(-) + +diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c +index cda31edd75..3909fa237c 100644 +--- a/src/udev/udevadm-trigger.c ++++ b/src/udev/udevadm-trigger.c +@@ -11,10 +11,12 @@ + #include "device-util.h" + #include "fd-util.h" + #include "fileio.h" ++#include "id128-util.h" + #include "parse-util.h" + #include "path-util.h" + #include "process-util.h" + #include "set.h" ++#include "static-destruct.h" + #include "string-util.h" + #include "strv.h" + #include "udevadm.h" +@@ -31,8 +33,9 @@ static bool arg_settle = false; + static int exec_list( + sd_device_enumerator *e, + sd_device_action_t action, +- Hashmap *settle_hashmap) { ++ Set **ret_settle_path_or_ids) { + ++ _cleanup_set_free_ Set *settle_path_or_ids = NULL; + int uuid_supported = -1; + const char *action_str; + sd_device *d; +@@ -119,60 +122,62 @@ static int exec_list( + printf(SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id)); + + if (arg_settle) { +- _cleanup_free_ sd_id128_t *mid = NULL; +- _cleanup_free_ char *sp = NULL; ++ if (uuid_supported) { ++ sd_id128_t *dup; + +- sp = strdup(syspath); +- if (!sp) +- return log_oom(); ++ dup = newdup(sd_id128_t, &id, 1); ++ if (!dup) ++ return log_oom(); + +- mid = newdup(sd_id128_t, &id, 1); +- if (!d) +- return log_oom(); ++ r = set_ensure_consume(&settle_path_or_ids, &id128_hash_ops_free, dup); ++ } else { ++ char *dup; ++ ++ dup = strdup(syspath); ++ if (!dup) ++ return log_oom(); + +- r = hashmap_put(settle_hashmap, sp, mid); ++ r = set_ensure_consume(&settle_path_or_ids, &path_hash_ops_free, dup); ++ } + if (r < 0) + return log_oom(); +- +- TAKE_PTR(sp); +- TAKE_PTR(mid); + } + } + ++ if (ret_settle_path_or_ids) ++ *ret_settle_path_or_ids = TAKE_PTR(settle_path_or_ids); ++ + return ret; + } + + static int device_monitor_handler(sd_device_monitor *m, sd_device *dev, void *userdata) { +- Hashmap *settle_hashmap = ASSERT_PTR(userdata); +- sd_id128_t *settle_id; ++ Set *settle_path_or_ids = * (Set**) ASSERT_PTR(userdata); + const char *syspath; +- char *k; ++ sd_id128_t id; + int r; + + assert(dev); + + r = sd_device_get_syspath(dev, &syspath); + if (r < 0) { +- log_debug_errno(r, "Failed to get syspath of device event, ignoring: %m"); ++ log_device_debug_errno(dev, r, "Failed to get syspath of device event, ignoring: %m"); + return 0; + } + +- settle_id = hashmap_get2(settle_hashmap, syspath, (void**) &k); +- if (!settle_id) { +- log_debug("Got uevent for unexpected device '%s', ignoring.", syspath); +- return 0; +- } +- if (!sd_id128_is_null(*settle_id)) { /* If this is SD_ID128_NULL then we are on pre-4.13 and have no UUID to check, hence don't */ +- sd_id128_t event_id; ++ if (sd_device_get_trigger_uuid(dev, &id) >= 0) { ++ _cleanup_free_ sd_id128_t *saved = NULL; + +- r = sd_device_get_trigger_uuid(dev, &event_id); +- if (r < 0) { +- log_debug_errno(r, "Got uevent without synthetic UUID for device '%s', ignoring: %m", syspath); ++ saved = set_remove(settle_path_or_ids, &id); ++ if (!saved) { ++ log_device_debug(dev, "Got uevent not matching expected UUID, ignoring."); + return 0; + } ++ } else { ++ _cleanup_free_ char *saved = NULL; + +- if (!sd_id128_equal(event_id, *settle_id)) { +- log_debug("Got uevent not matching expected UUID for device '%s', ignoring.", syspath); ++ saved = set_remove(settle_path_or_ids, syspath); ++ if (!saved) { ++ log_device_debug(dev, "Got uevent for unexpected device, ignoring."); + return 0; + } + } +@@ -181,12 +186,9 @@ static int device_monitor_handler(sd_device_monitor *m, sd_device *dev, void *us + printf("settle %s\n", syspath); + + if (arg_uuid) +- printf("settle " SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(*settle_id)); ++ printf("settle " SD_ID128_UUID_FORMAT_STR "\n", SD_ID128_FORMAT_VAL(id)); + +- free(hashmap_remove(settle_hashmap, syspath)); +- free(k); +- +- if (hashmap_isempty(settle_hashmap)) ++ if (set_isempty(settle_path_or_ids)) + return sd_event_exit(sd_device_monitor_get_event(m), 0); + + return 0; +@@ -289,7 +291,7 @@ int trigger_main(int argc, char *argv[], void *userdata) { + _cleanup_(sd_device_enumerator_unrefp) sd_device_enumerator *e = NULL; + _cleanup_(sd_device_monitor_unrefp) sd_device_monitor *m = NULL; + _cleanup_(sd_event_unrefp) sd_event *event = NULL; +- _cleanup_hashmap_free_ Hashmap *settle_hashmap = NULL; ++ _cleanup_set_free_ Set *settle_path_or_ids = NULL; + usec_t ping_timeout_usec = 5 * USEC_PER_SEC; + bool ping = false; + int c, r; +@@ -484,10 +486,6 @@ int trigger_main(int argc, char *argv[], void *userdata) { + } + + if (arg_settle) { +- settle_hashmap = hashmap_new(&path_hash_ops_free_free); +- if (!settle_hashmap) +- return log_oom(); +- + r = sd_event_default(&event); + if (r < 0) + return log_error_errno(r, "Failed to get default event: %m"); +@@ -500,7 +498,7 @@ int trigger_main(int argc, char *argv[], void *userdata) { + if (r < 0) + return log_error_errno(r, "Failed to attach event to device monitor: %m"); + +- r = sd_device_monitor_start(m, device_monitor_handler, settle_hashmap); ++ r = sd_device_monitor_start(m, device_monitor_handler, &settle_path_or_ids); + if (r < 0) + return log_error_errno(r, "Failed to start device monitor: %m"); + } +@@ -525,11 +523,11 @@ int trigger_main(int argc, char *argv[], void *userdata) { + assert_not_reached(); + } + +- r = exec_list(e, action, settle_hashmap); ++ r = exec_list(e, action, arg_settle ? &settle_path_or_ids : NULL); + if (r < 0) + return r; + +- if (event && !hashmap_isempty(settle_hashmap)) { ++ if (!set_isempty(settle_path_or_ids)) { + r = sd_event_loop(event); + if (r < 0) + return log_error_errno(r, "Event loop failed: %m"); diff --git a/SOURCES/0436-udevadm-trigger-also-check-with-the-original-syspath.patch b/SOURCES/0436-udevadm-trigger-also-check-with-the-original-syspath.patch new file mode 100644 index 0000000..919a50b --- /dev/null +++ b/SOURCES/0436-udevadm-trigger-also-check-with-the-original-syspath.patch @@ -0,0 +1,56 @@ +From 4007f494b2e4c45f2d59948af3f4053258d3f127 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Fri, 28 Oct 2022 09:06:02 +0900 +Subject: [PATCH] udevadm-trigger: also check with the original syspath if + device is renamed + +For older kernels that synthetic UUID is not supported, we need to also +check the original device name, as udevd broadcasts uevent with new +sysname. + +Fixes #25115. + +(cherry picked from commit 1193448cb68e5a90cab027e16a093bbd367e9494) + +Related: RHEL-5988 +--- + src/udev/udevadm-trigger.c | 26 ++++++++++++++++++++++++++ + 1 file changed, 26 insertions(+) + +diff --git a/src/udev/udevadm-trigger.c b/src/udev/udevadm-trigger.c +index 3909fa237c..40ee5085a0 100644 +--- a/src/udev/udevadm-trigger.c ++++ b/src/udev/udevadm-trigger.c +@@ -176,6 +176,32 @@ static int device_monitor_handler(sd_device_monitor *m, sd_device *dev, void *us + _cleanup_free_ char *saved = NULL; + + saved = set_remove(settle_path_or_ids, syspath); ++ if (!saved) { ++ const char *old_sysname; ++ ++ /* When the device is renamed, the new name is broadcast, and the old name is saved ++ * in INTERFACE_OLD. */ ++ ++ if (sd_device_get_property_value(dev, "INTERFACE_OLD", &old_sysname) >= 0) { ++ _cleanup_free_ char *dir = NULL, *old_syspath = NULL; ++ ++ r = path_extract_directory(syspath, &dir); ++ if (r < 0) { ++ log_device_debug_errno(dev, r, ++ "Failed to extract directory from '%s', ignoring: %m", ++ syspath); ++ return 0; ++ } ++ ++ old_syspath = path_join(dir, old_sysname); ++ if (!old_syspath) { ++ log_oom_debug(); ++ return 0; ++ } ++ ++ saved = set_remove(settle_path_or_ids, old_syspath); ++ } ++ } + if (!saved) { + log_device_debug(dev, "Got uevent for unexpected device, ignoring."); + return 0; diff --git a/SOURCES/0437-test-use-udevadm-trigger-settle-even-if-device-is-re.patch b/SOURCES/0437-test-use-udevadm-trigger-settle-even-if-device-is-re.patch new file mode 100644 index 0000000..ed90733 --- /dev/null +++ b/SOURCES/0437-test-use-udevadm-trigger-settle-even-if-device-is-re.patch @@ -0,0 +1,37 @@ +From ff755f035485eab0317d1320caa2748d5d4a2d78 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Thu, 27 Oct 2022 05:48:05 +0900 +Subject: [PATCH] test: use 'udevadm trigger --settle' even if device is + renamed + +(cherry picked from commit ff4d2a09fd141474cb552d4b5bd5a53d9748a1b4) + +Related: RHEL-5988 +--- + test/units/testsuite-17.02.sh | 6 ++---- + 1 file changed, 2 insertions(+), 4 deletions(-) + +diff --git a/test/units/testsuite-17.02.sh b/test/units/testsuite-17.02.sh +index ed3b39d074..82f9fd1f62 100755 +--- a/test/units/testsuite-17.02.sh ++++ b/test/units/testsuite-17.02.sh +@@ -61,9 +61,7 @@ EOF + + udevadm control --log-priority=debug --reload --timeout=30 + +-# FIXME(?): the 'add' uevent is broadcast as for 'foobar', instead of 'hoge'. Hence, we cannot use --settle here. +-# See issue #25115. +-udevadm trigger --action=add /sys/devices/virtual/net/hoge ++udevadm trigger --action=add --settle /sys/devices/virtual/net/hoge + udevadm wait --timeout=30 --settle /sys/devices/virtual/net/foobar + assert_not_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/foobar)" + timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "inactive" ]]; do sleep .5; done' +@@ -71,7 +69,7 @@ timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /s + timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/foobar)" != "active" ]]; do sleep .5; done' + timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/subsystem/net/devices/foobar)" != "active" ]]; do sleep .5; done' + +-udevadm trigger --action=add /sys/devices/virtual/net/foobar ++udevadm trigger --action=add --settle /sys/devices/virtual/net/foobar + udevadm wait --timeout=30 --settle /sys/devices/virtual/net/hoge + assert_not_in "ID_RENAMING=" "$(udevadm info /sys/devices/virtual/net/hoge)" + timeout 30 bash -c 'while [[ "$(systemctl show --property=ActiveState --value /sys/devices/virtual/net/hoge)" != "active" ]]; do sleep .5; done' diff --git a/SOURCES/0438-sd-event-don-t-mistake-USEC_INFINITY-passed-in-for-o.patch b/SOURCES/0438-sd-event-don-t-mistake-USEC_INFINITY-passed-in-for-o.patch new file mode 100644 index 0000000..8c0ecc7 --- /dev/null +++ b/SOURCES/0438-sd-event-don-t-mistake-USEC_INFINITY-passed-in-for-o.patch @@ -0,0 +1,33 @@ +From e92c85b68932845c908cb3f38b2130c57065e263 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 6 Jan 2023 11:27:17 +0100 +Subject: [PATCH] sd-event: don't mistake USEC_INFINITY passed in for overflow + +Let's pass USEC_INFINITY from sd_event_source_set_time_relative() to +sd_event_source_set_time() instead of raising EOVERFLOW. + +We should raise EOVERFLOW only if your addition fails, but not if the +input already is USEC_INFINITY, since it's an entirely valid operation +to have an infinite time-out, and we should support that. + +(cherry picked from commit ef8591951aefccb668201f24aa481aa6cda834da) + +Related: RHEL-6090 +--- + src/libsystemd/sd-event/sd-event.c | 3 +++ + 1 file changed, 3 insertions(+) + +diff --git a/src/libsystemd/sd-event/sd-event.c b/src/libsystemd/sd-event/sd-event.c +index 778070a5fb..cd73cd8bfd 100644 +--- a/src/libsystemd/sd-event/sd-event.c ++++ b/src/libsystemd/sd-event/sd-event.c +@@ -2723,6 +2723,9 @@ _public_ int sd_event_source_set_time_relative(sd_event_source *s, uint64_t usec + assert_return(s, -EINVAL); + assert_return(EVENT_SOURCE_IS_TIME(s->type), -EDOM); + ++ if (usec == USEC_INFINITY) ++ return sd_event_source_set_time(s, USEC_INFINITY); ++ + r = sd_event_now(s->event, event_source_type_to_clock(s->type), &t); + if (r < 0) + return r; diff --git a/SOURCES/0439-pid1-rework-service_arm_timer-to-optionally-take-a-r.patch b/SOURCES/0439-pid1-rework-service_arm_timer-to-optionally-take-a-r.patch new file mode 100644 index 0000000..690b878 --- /dev/null +++ b/SOURCES/0439-pid1-rework-service_arm_timer-to-optionally-take-a-r.patch @@ -0,0 +1,118 @@ +From 917b03f2b5ccdd668a49da7df72baaddd338c071 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 2 Jan 2023 15:06:10 +0100 +Subject: [PATCH] pid1: rework service_arm_timer() to optionally take a + relative time value + +In most cases this is actually what we want, hence simplify this case. + +(cherry picked from commit e5d6dcce7f852b978251d062afb2fcba16714eb9) + +Related: RHEL-6090 +--- + src/core/service.c | 24 ++++++++++++------------ + 1 file changed, 12 insertions(+), 12 deletions(-) + +diff --git a/src/core/service.c b/src/core/service.c +index 1e14cdc6ca..aa76b4ad9a 100644 +--- a/src/core/service.c ++++ b/src/core/service.c +@@ -530,13 +530,13 @@ static usec_t service_running_timeout(Service *s) { + delta); + } + +-static int service_arm_timer(Service *s, usec_t usec) { ++static int service_arm_timer(Service *s, bool relative, usec_t usec) { + int r; + + assert(s); + + if (s->timer_event_source) { +- r = sd_event_source_set_time(s->timer_event_source, usec); ++ r = (relative ? sd_event_source_set_time_relative : sd_event_source_set_time)(s->timer_event_source, usec); + if (r < 0) + return r; + +@@ -546,7 +546,7 @@ static int service_arm_timer(Service *s, usec_t usec) { + if (usec == USEC_INFINITY) + return 0; + +- r = sd_event_add_time( ++ r = (relative ? sd_event_add_time_relative : sd_event_add_time)( + UNIT(s)->manager->event, + &s->timer_event_source, + CLOCK_MONOTONIC, +@@ -1195,7 +1195,7 @@ static int service_coldplug(Unit *u) { + if (s->deserialized_state == s->state) + return 0; + +- r = service_arm_timer(s, service_coldplug_timeout(s)); ++ r = service_arm_timer(s, /* relative= */ false, service_coldplug_timeout(s)); + if (r < 0) + return r; + +@@ -1538,7 +1538,7 @@ static int service_spawn_internal( + return r; + } + +- r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), timeout)); ++ r = service_arm_timer(s, /* relative= */ true, timeout); + if (r < 0) + return r; + +@@ -1857,7 +1857,7 @@ static void service_enter_dead(Service *s, ServiceResult f, bool allow_restart) + if (s->will_auto_restart) { + s->will_auto_restart = false; + +- r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->restart_usec)); ++ r = service_arm_timer(s, /* relative= */ true, s->restart_usec); + if (r < 0) { + s->n_keep_fd_store--; + goto fail; +@@ -1989,8 +1989,8 @@ static void service_enter_signal(Service *s, ServiceState state, ServiceResult f + goto fail; + + if (r > 0) { +- r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), +- kill_operation == KILL_WATCHDOG ? service_timeout_abort_usec(s) : s->timeout_stop_usec)); ++ r = service_arm_timer(s, /* relative= */ true, ++ kill_operation == KILL_WATCHDOG ? service_timeout_abort_usec(s) : s->timeout_stop_usec); + if (r < 0) + goto fail; + +@@ -2020,7 +2020,7 @@ static void service_enter_stop_by_notify(Service *s) { + + (void) unit_enqueue_rewatch_pids(UNIT(s)); + +- service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_stop_usec)); ++ service_arm_timer(s, /* relative= */ true, s->timeout_stop_usec); + + /* The service told us it's stopping, so it's as if we SIGTERM'd it. */ + service_set_state(s, SERVICE_STOP_SIGTERM); +@@ -2099,7 +2099,7 @@ static void service_enter_running(Service *s, ServiceResult f) { + service_enter_stop_by_notify(s); + else { + service_set_state(s, SERVICE_RUNNING); +- service_arm_timer(s, service_running_timeout(s)); ++ service_arm_timer(s, /* relative= */ false, service_running_timeout(s)); + } + + } else if (s->remain_after_exit) +@@ -2398,7 +2398,7 @@ static void service_enter_reload_by_notify(Service *s) { + + assert(s); + +- service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->timeout_start_usec)); ++ service_arm_timer(s, /* relative= */ true, s->timeout_start_usec); + service_set_state(s, SERVICE_RELOAD); + + /* service_enter_reload_by_notify is never called during a reload, thus no loops are possible. */ +@@ -4570,7 +4570,7 @@ static int service_clean(Unit *u, ExecCleanMask mask) { + s->control_command = NULL; + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; + +- r = service_arm_timer(s, usec_add(now(CLOCK_MONOTONIC), s->exec_context.timeout_clean_usec)); ++ r = service_arm_timer(s, /* relative= */ true, s->exec_context.timeout_clean_usec); + if (r < 0) + goto fail; + diff --git a/SOURCES/0440-manager-add-one-more-assert.patch b/SOURCES/0440-manager-add-one-more-assert.patch new file mode 100644 index 0000000..b9c3c9f --- /dev/null +++ b/SOURCES/0440-manager-add-one-more-assert.patch @@ -0,0 +1,25 @@ +From efe1737efae0950b7ded32d9c5e1a9cfaea7296b Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 2 Jan 2023 15:00:05 +0100 +Subject: [PATCH] manager: add one more assert() + +(cherry picked from commit 7fa49280bc33ba5135228401fb24dce0de5f9195) + +Related: RHEL-6090 +--- + src/core/manager.c | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/src/core/manager.c b/src/core/manager.c +index 657263eb73..6371810ce3 100644 +--- a/src/core/manager.c ++++ b/src/core/manager.c +@@ -806,6 +806,8 @@ static int manager_find_credentials_dirs(Manager *m) { + } + + void manager_set_switching_root(Manager *m, bool switching_root) { ++ assert(m); ++ + m->switching_root = MANAGER_IS_SYSTEM(m) && switching_root; + } + diff --git a/SOURCES/0441-pid1-add-new-Type-notify-reload-service-type.patch b/SOURCES/0441-pid1-add-new-Type-notify-reload-service-type.patch new file mode 100644 index 0000000..a0aa911 --- /dev/null +++ b/SOURCES/0441-pid1-add-new-Type-notify-reload-service-type.patch @@ -0,0 +1,688 @@ +From f64d331351e33199c4096b2ae4a4b9d24d127661 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 2 Jan 2023 16:49:23 +0100 +Subject: [PATCH] pid1: add new Type=notify-reload service type + +Fixes: #6162 +(cherry picked from commit 3bd28bf721dc70722ff1c675026ed0b44ad968a3) + +Resolves: RHEL-6090 +--- + man/org.freedesktop.systemd1.xml | 6 + + src/basic/unit-def.c | 2 + + src/basic/unit-def.h | 4 +- + src/core/dbus-service.c | 5 + + src/core/load-fragment-gperf.gperf.in | 1 + + src/core/service.c | 226 ++++++++++++++++++-------- + src/core/service.h | 18 +- + src/shared/bus-unit-util.c | 3 +- + 8 files changed, 189 insertions(+), 76 deletions(-) + +diff --git a/man/org.freedesktop.systemd1.xml b/man/org.freedesktop.systemd1.xml +index 13a84af747..c18428a092 100644 +--- a/man/org.freedesktop.systemd1.xml ++++ b/man/org.freedesktop.systemd1.xml +@@ -2570,6 +2570,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + readonly u NRestarts = ...; + @org.freedesktop.DBus.Property.EmitsChangedSignal("const") + readonly s OOMPolicy = '...'; ++ @org.freedesktop.DBus.Property.EmitsChangedSignal("const") ++ readonly i ReloadSignal = ...; + readonly t ExecMainStartTimestamp = ...; + readonly t ExecMainStartTimestampMonotonic = ...; + readonly t ExecMainExitTimestamp = ...; +@@ -3163,6 +3165,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + ++ ++ + + + +@@ -3715,6 +3719,8 @@ node /org/freedesktop/systemd1/unit/avahi_2ddaemon_2eservice { + + + ++ ++ + + + +diff --git a/src/basic/unit-def.c b/src/basic/unit-def.c +index 94cd603e32..bdb1860246 100644 +--- a/src/basic/unit-def.c ++++ b/src/basic/unit-def.c +@@ -188,6 +188,8 @@ static const char* const service_state_table[_SERVICE_STATE_MAX] = { + [SERVICE_RUNNING] = "running", + [SERVICE_EXITED] = "exited", + [SERVICE_RELOAD] = "reload", ++ [SERVICE_RELOAD_SIGNAL] = "reload-signal", ++ [SERVICE_RELOAD_NOTIFY] = "reload-notify", + [SERVICE_STOP] = "stop", + [SERVICE_STOP_WATCHDOG] = "stop-watchdog", + [SERVICE_STOP_SIGTERM] = "stop-sigterm", +diff --git a/src/basic/unit-def.h b/src/basic/unit-def.h +index 5fcd51c095..bae132ea09 100644 +--- a/src/basic/unit-def.h ++++ b/src/basic/unit-def.h +@@ -132,7 +132,9 @@ typedef enum ServiceState { + SERVICE_START_POST, + SERVICE_RUNNING, + SERVICE_EXITED, /* Nothing is running anymore, but RemainAfterExit is true hence this is OK */ +- SERVICE_RELOAD, ++ SERVICE_RELOAD, /* Reloading via ExecReload= */ ++ SERVICE_RELOAD_SIGNAL, /* Reloading via SIGHUP requested */ ++ SERVICE_RELOAD_NOTIFY, /* Waiting for READY=1 after RELOADING=1 notify */ + SERVICE_STOP, /* No STOP_PRE state, instead just register multiple STOP executables */ + SERVICE_STOP_WATCHDOG, + SERVICE_STOP_SIGTERM, +diff --git a/src/core/dbus-service.c b/src/core/dbus-service.c +index 6e4bc0bd1a..3d130db66a 100644 +--- a/src/core/dbus-service.c ++++ b/src/core/dbus-service.c +@@ -228,6 +228,7 @@ const sd_bus_vtable bus_service_vtable[] = { + SD_BUS_PROPERTY("GID", "u", bus_property_get_gid, offsetof(Unit, ref_gid), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("NRestarts", "u", bus_property_get_unsigned, offsetof(Service, n_restarts), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + SD_BUS_PROPERTY("OOMPolicy", "s", bus_property_get_oom_policy, offsetof(Service, oom_policy), SD_BUS_VTABLE_PROPERTY_CONST), ++ SD_BUS_PROPERTY("ReloadSignal", "i", bus_property_get_int, offsetof(Service, reload_signal), SD_BUS_VTABLE_PROPERTY_CONST), + + BUS_EXEC_STATUS_VTABLE("ExecMain", offsetof(Service, main_exec_status), SD_BUS_VTABLE_PROPERTY_EMITS_CHANGE), + BUS_EXEC_COMMAND_LIST_VTABLE("ExecCondition", offsetof(Service, exec_command[SERVICE_EXEC_CONDITION]), SD_BUS_VTABLE_PROPERTY_EMITS_INVALIDATION), +@@ -374,6 +375,7 @@ static BUS_DEFINE_SET_TRANSIENT_PARSE(service_restart, ServiceRestart, service_r + static BUS_DEFINE_SET_TRANSIENT_PARSE(oom_policy, OOMPolicy, oom_policy_from_string); + static BUS_DEFINE_SET_TRANSIENT_STRING_WITH_CHECK(bus_name, sd_bus_service_name_is_valid); + static BUS_DEFINE_SET_TRANSIENT_PARSE(timeout_failure_mode, ServiceTimeoutFailureMode, service_timeout_failure_mode_from_string); ++static BUS_DEFINE_SET_TRANSIENT_TO_STRING(reload_signal, "i", int32_t, int, "%" PRIi32, signal_to_string_with_check); + + static int bus_service_set_transient_property( + Service *s, +@@ -532,6 +534,9 @@ static int bus_service_set_transient_property( + if (streq(name, "StandardErrorFileDescriptor")) + return bus_set_transient_std_fd(u, name, &s->stderr_fd, &s->exec_context.stdio_as_fds, message, flags, error); + ++ if (streq(name, "ReloadSignal")) ++ return bus_set_transient_reload_signal(u, name, &s->reload_signal, message, flags, error); ++ + return 0; + } + +diff --git a/src/core/load-fragment-gperf.gperf.in b/src/core/load-fragment-gperf.gperf.in +index 81a5971339..53089d5590 100644 +--- a/src/core/load-fragment-gperf.gperf.in ++++ b/src/core/load-fragment-gperf.gperf.in +@@ -424,6 +424,7 @@ Service.BusPolicy, config_parse_warn_compat, + Service.USBFunctionDescriptors, config_parse_unit_path_printf, 0, offsetof(Service, usb_function_descriptors) + Service.USBFunctionStrings, config_parse_unit_path_printf, 0, offsetof(Service, usb_function_strings) + Service.OOMPolicy, config_parse_oom_policy, 0, offsetof(Service, oom_policy) ++Service.ReloadSignal, config_parse_signal, 0, offsetof(Service, reload_signal) + {{ EXEC_CONTEXT_CONFIG_ITEMS('Service') }} + {{ CGROUP_CONTEXT_CONFIG_ITEMS('Service') }} + {{ KILL_CONTEXT_CONFIG_ITEMS('Service') }} +diff --git a/src/core/service.c b/src/core/service.c +index aa76b4ad9a..902948905f 100644 +--- a/src/core/service.c ++++ b/src/core/service.c +@@ -54,6 +54,8 @@ static const UnitActiveState state_translation_table[_SERVICE_STATE_MAX] = { + [SERVICE_RUNNING] = UNIT_ACTIVE, + [SERVICE_EXITED] = UNIT_ACTIVE, + [SERVICE_RELOAD] = UNIT_RELOADING, ++ [SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING, ++ [SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING, + [SERVICE_STOP] = UNIT_DEACTIVATING, + [SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, +@@ -78,6 +80,8 @@ static const UnitActiveState state_translation_table_idle[_SERVICE_STATE_MAX] = + [SERVICE_RUNNING] = UNIT_ACTIVE, + [SERVICE_EXITED] = UNIT_ACTIVE, + [SERVICE_RELOAD] = UNIT_RELOADING, ++ [SERVICE_RELOAD_SIGNAL] = UNIT_RELOADING, ++ [SERVICE_RELOAD_NOTIFY] = UNIT_RELOADING, + [SERVICE_STOP] = UNIT_DEACTIVATING, + [SERVICE_STOP_WATCHDOG] = UNIT_DEACTIVATING, + [SERVICE_STOP_SIGTERM] = UNIT_DEACTIVATING, +@@ -124,6 +128,8 @@ static void service_init(Unit *u) { + s->watchdog_original_usec = USEC_INFINITY; + + s->oom_policy = _OOM_POLICY_INVALID; ++ s->reload_begin_usec = USEC_INFINITY; ++ s->reload_signal = SIGHUP; + } + + static void service_unwatch_control_pid(Service *s) { +@@ -765,7 +771,7 @@ static int service_add_extras(Service *s) { + + /* If the service needs the notify socket, let's enable it automatically. */ + if (s->notify_access == NOTIFY_NONE && +- (s->type == SERVICE_NOTIFY || s->watchdog_usec > 0 || s->n_fd_store_max > 0)) ++ (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) || s->watchdog_usec > 0 || s->n_fd_store_max > 0)) + s->notify_access = NOTIFY_MAIN; + + /* If no OOM policy was explicitly set, then default to the configure default OOM policy. Except when +@@ -830,7 +836,8 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { + "%sRestart: %s\n" + "%sNotifyAccess: %s\n" + "%sNotifyState: %s\n" +- "%sOOMPolicy: %s\n", ++ "%sOOMPolicy: %s\n" ++ "%sReloadSignal: %s\n", + prefix, service_state_to_string(s->state), + prefix, service_result_to_string(s->result), + prefix, service_result_to_string(s->reload_result), +@@ -843,7 +850,8 @@ static void service_dump(Unit *u, FILE *f, const char *prefix) { + prefix, service_restart_to_string(s->restart), + prefix, notify_access_to_string(s->notify_access), + prefix, notify_state_to_string(s->notify_state), +- prefix, oom_policy_to_string(s->oom_policy)); ++ prefix, oom_policy_to_string(s->oom_policy), ++ prefix, signal_to_string(s->reload_signal)); + + if (s->control_pid > 0) + fprintf(f, +@@ -1088,7 +1096,7 @@ static void service_set_state(Service *s, ServiceState state) { + if (!IN_SET(state, + SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, + SERVICE_RUNNING, +- SERVICE_RELOAD, ++ SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, + SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, + SERVICE_AUTO_RESTART, +@@ -1097,7 +1105,8 @@ static void service_set_state(Service *s, ServiceState state) { + + if (!IN_SET(state, + SERVICE_START, SERVICE_START_POST, +- SERVICE_RUNNING, SERVICE_RELOAD, ++ SERVICE_RUNNING, ++ SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, + SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL)) { + service_unwatch_main_pid(s); +@@ -1106,7 +1115,7 @@ static void service_set_state(Service *s, ServiceState state) { + + if (!IN_SET(state, + SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, +- SERVICE_RELOAD, ++ SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, + SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, + SERVICE_CLEANING)) { +@@ -1122,7 +1131,8 @@ static void service_set_state(Service *s, ServiceState state) { + + if (!IN_SET(state, + SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, +- SERVICE_RUNNING, SERVICE_RELOAD, ++ SERVICE_RUNNING, ++ SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, + SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL) && + !(state == SERVICE_DEAD && UNIT(s)->job)) +@@ -1131,7 +1141,7 @@ static void service_set_state(Service *s, ServiceState state) { + if (state != SERVICE_START) + s->exec_fd_event_source = sd_event_source_disable_unref(s->exec_fd_event_source); + +- if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) ++ if (!IN_SET(state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY)) + service_stop_watchdog(s); + + /* For the inactive states unit_notify() will trim the cgroup, +@@ -1157,6 +1167,8 @@ static usec_t service_coldplug_timeout(Service *s) { + case SERVICE_START: + case SERVICE_START_POST: + case SERVICE_RELOAD: ++ case SERVICE_RELOAD_SIGNAL: ++ case SERVICE_RELOAD_NOTIFY: + return usec_add(UNIT(s)->state_change_timestamp.monotonic, s->timeout_start_usec); + + case SERVICE_RUNNING: +@@ -1203,7 +1215,8 @@ static int service_coldplug(Unit *u) { + pid_is_unwaited(s->main_pid) && + (IN_SET(s->deserialized_state, + SERVICE_START, SERVICE_START_POST, +- SERVICE_RUNNING, SERVICE_RELOAD, ++ SERVICE_RUNNING, ++ SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, + SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL))) { + r = unit_watch_pid(UNIT(s), s->main_pid, false); +@@ -1215,7 +1228,7 @@ static int service_coldplug(Unit *u) { + pid_is_unwaited(s->control_pid) && + IN_SET(s->deserialized_state, + SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, +- SERVICE_RELOAD, ++ SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, + SERVICE_STOP, SERVICE_STOP_WATCHDOG, SERVICE_STOP_SIGTERM, SERVICE_STOP_SIGKILL, SERVICE_STOP_POST, + SERVICE_FINAL_WATCHDOG, SERVICE_FINAL_SIGTERM, SERVICE_FINAL_SIGKILL, + SERVICE_CLEANING)) { +@@ -1230,7 +1243,7 @@ static int service_coldplug(Unit *u) { + (void) unit_setup_exec_runtime(u); + } + +- if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) ++ if (IN_SET(s->deserialized_state, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY)) + service_start_watchdog(s); + + if (UNIT_ISSET(s->accept_socket)) { +@@ -2255,7 +2268,7 @@ static void service_enter_start(Service *s) { + s->control_pid = pid; + service_set_state(s, SERVICE_START); + +- } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY, SERVICE_EXEC)) { ++ } else if (IN_SET(s->type, SERVICE_ONESHOT, SERVICE_DBUS, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD, SERVICE_EXEC)) { + + /* For oneshot services we wait until the start process exited, too, but it is our main process. */ + +@@ -2399,7 +2412,7 @@ static void service_enter_reload_by_notify(Service *s) { + assert(s); + + service_arm_timer(s, /* relative= */ true, s->timeout_start_usec); +- service_set_state(s, SERVICE_RELOAD); ++ service_set_state(s, SERVICE_RELOAD_NOTIFY); + + /* service_enter_reload_by_notify is never called during a reload, thus no loops are possible. */ + r = manager_propagate_reload(UNIT(s)->manager, UNIT(s), JOB_FAIL, &error); +@@ -2408,6 +2421,7 @@ static void service_enter_reload_by_notify(Service *s) { + } + + static void service_enter_reload(Service *s) { ++ bool killed = false; + int r; + + assert(s); +@@ -2415,6 +2429,18 @@ static void service_enter_reload(Service *s) { + service_unwatch_control_pid(s); + s->reload_result = SERVICE_SUCCESS; + ++ usec_t ts = now(CLOCK_MONOTONIC); ++ ++ if (s->type == SERVICE_NOTIFY_RELOAD && s->main_pid > 0) { ++ r = kill_and_sigcont(s->main_pid, s->reload_signal); ++ if (r < 0) { ++ log_unit_warning_errno(UNIT(s), r, "Failed to send reload signal: %m"); ++ goto fail; ++ } ++ ++ killed = true; ++ } ++ + s->control_command = s->exec_command[SERVICE_EXEC_RELOAD]; + if (s->control_command) { + s->control_command_id = SERVICE_EXEC_RELOAD; +@@ -2424,17 +2450,28 @@ static void service_enter_reload(Service *s) { + s->timeout_start_usec, + EXEC_APPLY_SANDBOXING|EXEC_APPLY_CHROOT|EXEC_IS_CONTROL|EXEC_CONTROL_CGROUP, + &s->control_pid); +- if (r < 0) ++ if (r < 0) { ++ log_unit_warning_errno(UNIT(s), r, "Failed to run 'reload' task: %m"); + goto fail; ++ } + + service_set_state(s, SERVICE_RELOAD); +- } else ++ } else if (killed) { ++ service_arm_timer(s, /* relative= */ true, s->timeout_start_usec); ++ service_set_state(s, SERVICE_RELOAD_SIGNAL); ++ } else { + service_enter_running(s, SERVICE_SUCCESS); ++ return; ++ } + ++ /* Store the timestamp when we started reloading: when reloading via SIGHUP we won't leave the reload ++ * state until we received both RELOADING=1 and READY=1 with MONOTONIC_USEC= set to a value above ++ * this. Thus we know for sure the reload cycle was executed *after* we requested it, and is not one ++ * that was already in progress before. */ ++ s->reload_begin_usec = ts; + return; + + fail: +- log_unit_warning_errno(UNIT(s), r, "Failed to run 'reload' task: %m"); + s->reload_result = SERVICE_FAILURE_RESOURCES; + service_enter_running(s, SERVICE_SUCCESS); + } +@@ -2597,9 +2634,8 @@ static int service_stop(Unit *u) { + return 0; + } + +- /* If there's already something running we go directly into +- * kill mode. */ +- if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_STOP_WATCHDOG)) { ++ /* If there's already something running we go directly into kill mode. */ ++ if (IN_SET(s->state, SERVICE_CONDITION, SERVICE_START_PRE, SERVICE_START, SERVICE_START_POST, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY, SERVICE_STOP_WATCHDOG)) { + service_enter_signal(s, SERVICE_STOP_SIGTERM, SERVICE_SUCCESS); + return 0; + } +@@ -2632,7 +2668,8 @@ _pure_ static bool service_can_reload(Unit *u) { + + assert(s); + +- return !!s->exec_command[SERVICE_EXEC_RELOAD]; ++ return s->exec_command[SERVICE_EXEC_RELOAD] || ++ s->type == SERVICE_NOTIFY_RELOAD; + } + + static unsigned service_exec_command_index(Unit *u, ServiceExecCommand id, ExecCommand *current) { +@@ -2808,6 +2845,9 @@ static int service_serialize(Unit *u, FILE *f, FDSet *fds) { + if (s->watchdog_original_usec != USEC_INFINITY) + (void) serialize_item_format(f, "watchdog-original-usec", USEC_FMT, s->watchdog_original_usec); + ++ if (s->reload_begin_usec != USEC_INFINITY) ++ (void) serialize_item_format(f, "reload-begin-usec", USEC_FMT, s->reload_begin_usec); ++ + return 0; + } + +@@ -3146,6 +3186,10 @@ static int service_deserialize_item(Unit *u, const char *key, const char *value, + log_unit_debug_errno(u, r, "Failed to parse serialized flush restart counter setting '%s': %m", value); + else + s->flush_n_restarts = r; ++ } else if (streq(key, "reload-begin-usec")) { ++ r = deserialize_usec(value, &s->reload_begin_usec); ++ if (r < 0) ++ log_unit_debug_errno(u, r, "Failed to parse serialized reload begin timestamp '%s', ignoring: %m", value); + } else + log_unit_debug(u, "Unknown serialization key: %s", key); + +@@ -3349,7 +3393,7 @@ static void service_notify_cgroup_empty_event(Unit *u) { + * SIGCHLD for. */ + + case SERVICE_START: +- if (s->type == SERVICE_NOTIFY && ++ if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) && + main_pid_good(s) == 0 && + control_pid_good(s) == 0) { + /* No chance of getting a ready notification anymore */ +@@ -3553,17 +3597,19 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { + } else { + s->main_command = NULL; + +- /* Services with ExitType=cgroup do not act on main PID exiting, +- * unless the cgroup is already empty */ ++ /* Services with ExitType=cgroup do not act on main PID exiting, unless the cgroup is ++ * already empty */ + if (s->exit_type == SERVICE_EXIT_MAIN || cgroup_good(s) <= 0) { + /* The service exited, so the service is officially gone. */ + switch (s->state) { + + case SERVICE_START_POST: + case SERVICE_RELOAD: +- /* If neither main nor control processes are running then +- * the current state can never exit cleanly, hence immediately +- * terminate the service. */ ++ case SERVICE_RELOAD_SIGNAL: ++ case SERVICE_RELOAD_NOTIFY: ++ /* If neither main nor control processes are running then the current ++ * state can never exit cleanly, hence immediately terminate the ++ * service. */ + if (control_pid_good(s) <= 0) + service_enter_stop(s, f); + +@@ -3582,7 +3628,7 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { + else + service_enter_signal(s, SERVICE_STOP_SIGTERM, f); + break; +- } else if (s->type == SERVICE_NOTIFY) { ++ } else if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD)) { + /* Only enter running through a notification, so that the + * SERVICE_START state signifies that no ready notification + * has been received */ +@@ -3675,15 +3721,13 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { + s->control_command->command_next && + f == SERVICE_SUCCESS) { + +- /* There is another command to * +- * execute, so let's do that. */ ++ /* There is another command to * execute, so let's do that. */ + + log_unit_debug(u, "Running next control command for state %s.", service_state_to_string(s->state)); + service_run_next_control(s); + + } else { +- /* No further commands for this step, so let's +- * figure out what to do next */ ++ /* No further commands for this step, so let's figure out what to do next */ + + s->control_command = NULL; + s->control_command_id = _SERVICE_EXEC_COMMAND_INVALID; +@@ -3761,12 +3805,22 @@ static void service_sigchld_event(Unit *u, pid_t pid, int code, int status) { + break; + + case SERVICE_RELOAD: ++ case SERVICE_RELOAD_SIGNAL: ++ case SERVICE_RELOAD_NOTIFY: + if (f == SERVICE_SUCCESS) + if (service_load_pid_file(s, true) < 0) + service_search_main_pid(s); + + s->reload_result = f; +- service_enter_running(s, SERVICE_SUCCESS); ++ ++ /* If the last notification we received from the service process indiciates ++ * we are still reloading, then don't leave reloading state just yet, just ++ * transition into SERVICE_RELOAD_NOTIFY, to wait for the READY=1 coming, ++ * too. */ ++ if (s->notify_state == NOTIFY_RELOADING) ++ service_set_state(s, SERVICE_RELOAD_NOTIFY); ++ else ++ service_enter_running(s, SERVICE_SUCCESS); + break; + + case SERVICE_STOP: +@@ -3869,6 +3923,8 @@ static int service_dispatch_timer(sd_event_source *source, usec_t usec, void *us + break; + + case SERVICE_RELOAD: ++ case SERVICE_RELOAD_SIGNAL: ++ case SERVICE_RELOAD_NOTIFY: + log_unit_warning(UNIT(s), "Reload operation timed out. Killing reload process."); + service_kill_control_process(s); + s->reload_result = SERVICE_FAILURE_TIMEOUT; +@@ -4094,6 +4150,7 @@ static void service_notify_message( + + Service *s = SERVICE(u); + bool notify_dbus = false; ++ usec_t monotonic_usec = USEC_INFINITY; + const char *e; + int r; + +@@ -4112,7 +4169,7 @@ static void service_notify_message( + + /* Interpret MAINPID= */ + e = strv_find_startswith(tags, "MAINPID="); +- if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD)) { ++ if (e && IN_SET(s->state, SERVICE_START, SERVICE_START_POST, SERVICE_RUNNING, SERVICE_RELOAD, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY)) { + pid_t new_main_pid; + + if (parse_pid(e, &new_main_pid) < 0) +@@ -4141,43 +4198,73 @@ static void service_notify_message( + } + } + +- /* Interpret READY=/STOPPING=/RELOADING=. Last one wins. */ +- STRV_FOREACH_BACKWARDS(i, tags) { ++ /* Parse MONOTONIC_USEC= */ ++ e = strv_find_startswith(tags, "MONOTONIC_USEC="); ++ if (e) { ++ r = safe_atou64(e, &monotonic_usec); ++ if (r < 0) ++ log_unit_warning_errno(u, r, "Failed to parse MONOTONIC_USEC= field in notification message, ignoring: %s", e); ++ } + +- if (streq(*i, "READY=1")) { +- s->notify_state = NOTIFY_READY; ++ /* Interpret READY=/STOPPING=/RELOADING=. STOPPING= wins over the others, and READY= over RELOADING= */ ++ if (strv_contains(tags, "STOPPING=1")) { ++ s->notify_state = NOTIFY_STOPPING; + +- /* Type=notify services inform us about completed +- * initialization with READY=1 */ +- if (s->type == SERVICE_NOTIFY && s->state == SERVICE_START) +- service_enter_start_post(s); ++ if (IN_SET(s->state, SERVICE_RUNNING, SERVICE_RELOAD_SIGNAL, SERVICE_RELOAD_NOTIFY)) ++ service_enter_stop_by_notify(s); + +- /* Sending READY=1 while we are reloading informs us +- * that the reloading is complete */ +- if (s->state == SERVICE_RELOAD && s->control_pid == 0) +- service_enter_running(s, SERVICE_SUCCESS); ++ notify_dbus = true; + +- notify_dbus = true; +- break; ++ } else if (strv_contains(tags, "READY=1")) { + +- } else if (streq(*i, "RELOADING=1")) { +- s->notify_state = NOTIFY_RELOADING; ++ s->notify_state = NOTIFY_READY; + +- if (s->state == SERVICE_RUNNING) +- service_enter_reload_by_notify(s); ++ /* Type=notify services inform us about completed initialization with READY=1 */ ++ if (IN_SET(s->type, SERVICE_NOTIFY, SERVICE_NOTIFY_RELOAD) && ++ s->state == SERVICE_START) ++ service_enter_start_post(s); + +- notify_dbus = true; +- break; ++ /* Sending READY=1 while we are reloading informs us that the reloading is complete. */ ++ if (s->state == SERVICE_RELOAD_NOTIFY) ++ service_enter_running(s, SERVICE_SUCCESS); + +- } else if (streq(*i, "STOPPING=1")) { +- s->notify_state = NOTIFY_STOPPING; ++ /* Combined RELOADING=1 and READY=1? Then this is indication that the service started and ++ * immediately finished reloading. */ ++ if (s->state == SERVICE_RELOAD_SIGNAL && ++ strv_contains(tags, "RELOADING=1") && ++ monotonic_usec != USEC_INFINITY && ++ monotonic_usec >= s->reload_begin_usec) { ++ _cleanup_(sd_bus_error_free) sd_bus_error error = SD_BUS_ERROR_NULL; + +- if (s->state == SERVICE_RUNNING) +- service_enter_stop_by_notify(s); ++ /* Propagate a reload explicitly */ ++ r = manager_propagate_reload(UNIT(s)->manager, UNIT(s), JOB_FAIL, &error); ++ if (r < 0) ++ log_unit_warning(UNIT(s), "Failed to schedule propagation of reload, ignoring: %s", bus_error_message(&error, r)); + +- notify_dbus = true; +- break; ++ service_enter_running(s, SERVICE_SUCCESS); + } ++ ++ notify_dbus = true; ++ ++ } else if (strv_contains(tags, "RELOADING=1")) { ++ ++ s->notify_state = NOTIFY_RELOADING; ++ ++ /* Sending RELOADING=1 after we send SIGHUP to request a reload will transition ++ * things to "reload-notify" state, where we'll wait for READY=1 to let us know the ++ * reload is done. Note that we insist on a timestamp being sent along here, so that ++ * we know for sure this is a reload cycle initiated *after* we sent the signal */ ++ if (s->state == SERVICE_RELOAD_SIGNAL && ++ monotonic_usec != USEC_INFINITY && ++ monotonic_usec >= s->reload_begin_usec) ++ /* Note, we don't call service_enter_reload_by_notify() here, because we ++ * don't need reload propagation nor do we want to restart the time-out. */ ++ service_set_state(s, SERVICE_RELOAD_NOTIFY); ++ ++ if (s->state == SERVICE_RUNNING) ++ service_enter_reload_by_notify(s); ++ ++ notify_dbus = true; + } + + /* Interpret STATUS= */ +@@ -4307,7 +4394,9 @@ static bool pick_up_pid_from_bus_name(Service *s) { + SERVICE_START, + SERVICE_START_POST, + SERVICE_RUNNING, +- SERVICE_RELOAD); ++ SERVICE_RELOAD, ++ SERVICE_RELOAD_SIGNAL, ++ SERVICE_RELOAD_NOTIFY); + } + + static int bus_name_pid_lookup_callback(sd_bus_message *reply, void *userdata, sd_bus_error *ret_error) { +@@ -4514,6 +4603,8 @@ static bool service_needs_console(Unit *u) { + SERVICE_START_POST, + SERVICE_RUNNING, + SERVICE_RELOAD, ++ SERVICE_RELOAD_SIGNAL, ++ SERVICE_RELOAD_NOTIFY, + SERVICE_STOP, + SERVICE_STOP_WATCHDOG, + SERVICE_STOP_SIGTERM, +@@ -4636,13 +4727,14 @@ static const char* const service_restart_table[_SERVICE_RESTART_MAX] = { + DEFINE_STRING_TABLE_LOOKUP(service_restart, ServiceRestart); + + static const char* const service_type_table[_SERVICE_TYPE_MAX] = { +- [SERVICE_SIMPLE] = "simple", +- [SERVICE_FORKING] = "forking", +- [SERVICE_ONESHOT] = "oneshot", +- [SERVICE_DBUS] = "dbus", +- [SERVICE_NOTIFY] = "notify", +- [SERVICE_IDLE] = "idle", +- [SERVICE_EXEC] = "exec", ++ [SERVICE_SIMPLE] = "simple", ++ [SERVICE_FORKING] = "forking", ++ [SERVICE_ONESHOT] = "oneshot", ++ [SERVICE_DBUS] = "dbus", ++ [SERVICE_NOTIFY] = "notify", ++ [SERVICE_NOTIFY_RELOAD] = "notify-reload", ++ [SERVICE_IDLE] = "idle", ++ [SERVICE_EXEC] = "exec", + }; + + DEFINE_STRING_TABLE_LOOKUP(service_type, ServiceType); +diff --git a/src/core/service.h b/src/core/service.h +index 91e02e6d7e..194067f0e1 100644 +--- a/src/core/service.h ++++ b/src/core/service.h +@@ -24,13 +24,14 @@ typedef enum ServiceRestart { + } ServiceRestart; + + typedef enum ServiceType { +- SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons) */ +- SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */ +- SERVICE_ONESHOT, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */ +- SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */ +- SERVICE_NOTIFY, /* we fork and wait until a daemon sends us a ready message with sd_notify() */ +- SERVICE_IDLE, /* much like simple, but delay exec() until all jobs are dispatched. */ +- SERVICE_EXEC, /* we fork and wait until we execute exec() (this means our own setup is waited for) */ ++ SERVICE_SIMPLE, /* we fork and go on right-away (i.e. modern socket activated daemons) */ ++ SERVICE_FORKING, /* forks by itself (i.e. traditional daemons) */ ++ SERVICE_ONESHOT, /* we fork and wait until the program finishes (i.e. programs like fsck which run and need to finish before we continue) */ ++ SERVICE_DBUS, /* we fork and wait until a specific D-Bus name appears on the bus */ ++ SERVICE_NOTIFY, /* we fork and wait until a daemon sends us a ready message with sd_notify() */ ++ SERVICE_NOTIFY_RELOAD, /* just like SERVICE_NOTIFY, but also implements a reload protocol via SIGHUP */ ++ SERVICE_IDLE, /* much like simple, but delay exec() until all jobs are dispatched. */ ++ SERVICE_EXEC, /* we fork and wait until we execute exec() (this means our own setup is waited for) */ + _SERVICE_TYPE_MAX, + _SERVICE_TYPE_INVALID = -EINVAL, + } ServiceType; +@@ -215,6 +216,9 @@ struct Service { + bool flush_n_restarts; + + OOMPolicy oom_policy; ++ ++ int reload_signal; ++ usec_t reload_begin_usec; + }; + + static inline usec_t service_timeout_abort_usec(Service *s) { +diff --git a/src/shared/bus-unit-util.c b/src/shared/bus-unit-util.c +index 922011eccd..a9844e1cc3 100644 +--- a/src/shared/bus-unit-util.c ++++ b/src/shared/bus-unit-util.c +@@ -2065,7 +2065,8 @@ static int bus_append_kill_property(sd_bus_message *m, const char *field, const + if (STR_IN_SET(field, "KillSignal", + "RestartKillSignal", + "FinalKillSignal", +- "WatchdogSignal")) ++ "WatchdogSignal", ++ "ReloadSignal")) + return bus_append_signal_from_string(m, field, eq); + + return 0; diff --git a/SOURCES/0442-man-document-Type-notify-reload.patch b/SOURCES/0442-man-document-Type-notify-reload.patch new file mode 100644 index 0000000..be65f70 --- /dev/null +++ b/SOURCES/0442-man-document-Type-notify-reload.patch @@ -0,0 +1,420 @@ +From e8de964c146f67c91acdaff076420282c2d1b217 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 2 Jan 2023 18:13:27 +0100 +Subject: [PATCH] man: document Type=notify-reload + +(cherry picked from commit 81e19b6f6585d656e972efad73781e184ca0e7a0) + +Related: RHEL-6090 +--- + man/sd_notify.xml | 36 ++++-- + man/systemd.service.xml | 248 ++++++++++++++++++++++------------------ + 2 files changed, 162 insertions(+), 122 deletions(-) + +diff --git a/man/sd_notify.xml b/man/sd_notify.xml +index de402950bb..d2dba00004 100644 +--- a/man/sd_notify.xml ++++ b/man/sd_notify.xml +@@ -102,23 +102,35 @@ + + READY=1 + +- Tells the service manager that service startup is finished, or the service finished loading its +- configuration. This is only used by systemd if the service definition file has Type=notify +- set. Since there is little value in signaling non-readiness, the only value services should send is +- READY=1 (i.e. READY=0 is not defined). ++ Tells the service manager that service startup is finished, or the service finished ++ re-loading its configuration. This is only used by systemd if the service definition file has ++ Type=notify or Type=notify-reload set. Since there is little ++ value in signaling non-readiness, the only value services should send is READY=1 ++ (i.e. READY=0 is not defined). + + + + RELOADING=1 + +- Tells the service manager that the service is +- reloading its configuration. This is useful to allow the +- service manager to track the service's internal state, and +- present it to the user. Note that a service that sends this +- notification must also send a READY=1 +- notification when it completed reloading its +- configuration. Reloads are propagated in the same way as they +- are when initiated by the user. ++ Tells the service manager that the service is beginning to reload its ++ configuration. This is useful to allow the service manager to track the service's internal state, and ++ present it to the user. Note that a service that sends this notification must also send a ++ READY=1 notification when it completed reloading its configuration. Reloads the ++ service manager is notified about with this mechanisms are propagated in the same way as they are ++ when originally initiated through the service manager. This message is particularly relevant for ++ Type=notify-reload services, to inform the service manager that the request to ++ reload the service has been received and is now being processed. ++ ++ ++ ++ MONOTONIC_USEC=… ++ ++ A field carrying the monotonic timestamp (as per ++ CLOCK_MONOTONIC) formatted in decimal in µs, when the notification message was ++ generated by the client. This is typically used in combination with RELOADING=1, ++ to allow the service manager to properly synchronize reload cycles. See ++ systemd.service5 ++ for details, specifically Type=notify-reload. + + + +diff --git a/man/systemd.service.xml b/man/systemd.service.xml +index 1c9e59f722..ae54332440 100644 +--- a/man/systemd.service.xml ++++ b/man/systemd.service.xml +@@ -157,7 +157,7 @@ + + Configures the process start-up type for this service unit. One of , + , , , , +- or : ++ , or : + + + If set to (the default if ExecStart= is +@@ -216,14 +216,30 @@ + logic thus should be prepared to receive a SIGTERM (or whichever signal is + configured in KillSignal=) as result. + +- Behavior of is similar to ; however, it is +- expected that the service sends a notification message via +- sd_notify3 or an +- equivalent call when it has finished starting up. systemd will proceed with starting follow-up units after +- this notification message has been sent. If this option is used, NotifyAccess= (see +- below) should be set to open access to the notification socket provided by systemd. If +- NotifyAccess= is missing or set to , it will be forcibly set to +- . ++ Behavior of is similar to ; however, ++ it is expected that the service sends a READY=1 notification message via ++ sd_notify3 or ++ an equivalent call when it has finished starting up. systemd will proceed with starting follow-up ++ units after this notification message has been sent. If this option is used, ++ NotifyAccess= (see below) should be set to open access to the notification ++ socket provided by systemd. If NotifyAccess= is missing or set to ++ , it will be forcibly set to . ++ ++ Behavior of is identical to ++ . However, it extends the logic in one way: the ++ SIGHUP UNIX process signal is sent to the service's main process when the ++ service is asked to reload. (The signal to send can be tweaked via ++ ReloadSignal=, see below.). When ++ initiating the reload process the service is then expected to reply with a notification message ++ via sd_notify3 ++ that contains the RELOADING=1 field in combination with ++ MONOTONIC_USEC= set to the current monotonic time ++ (i.e. CLOCK_MONOTONIC in ++ clock_gettime2) ++ in µs, formatted as decimal string. Once reloading is complete another notification message must ++ be sent, containing READY=1. Using this service type and implementing this ++ reload protocol is an efficient alternative to providing an ExecReload= ++ command for reloading of the service's configuration. + + Behavior of is very similar to ; however, + actual execution of the service program is delayed until all active jobs are dispatched. This may be used +@@ -233,25 +249,27 @@ + anyway. + + +- It is generally recommended to use Type= for long-running +- services whenever possible, as it is the simplest and fastest option. However, as this service type won't +- propagate service start-up failures and doesn't allow ordering of other units against completion of +- initialization of the service (which for example is useful if clients need to connect to the service through +- some form of IPC, and the IPC channel is only established by the service itself — in contrast to doing this +- ahead of time through socket or bus activation or similar), it might not be sufficient for many cases. If so, +- or (the latter only in case the service provides a D-Bus +- interface) are the preferred options as they allow service program code to precisely schedule when to +- consider the service started up successfully and when to proceed with follow-up units. The +- service type requires explicit support in the service codebase (as +- sd_notify() or an equivalent API needs to be invoked by the service at the appropriate +- time) — if it's not supported, then is an alternative: it supports the traditional +- UNIX service start-up protocol. Finally, might be an option for cases where it is +- enough to ensure the service binary is invoked, and where the service binary itself executes no or little +- initialization on its own (and its initialization is unlikely to fail). Note that using any type other than +- possibly delays the boot process, as the service manager needs to wait for service +- initialization to complete. It is hence recommended not to needlessly use any types other than +- . (Also note it is generally not recommended to use or +- for long-running services.) ++ It is generally recommended to use Type= for ++ long-running services whenever possible, as it is the simplest and fastest option. However, as this ++ service type won't propagate service start-up failures and doesn't allow ordering of other units ++ against completion of initialization of the service (which for example is useful if clients need to ++ connect to the service through some form of IPC, and the IPC channel is only established by the ++ service itself — in contrast to doing this ahead of time through socket or bus activation or ++ similar), it might not be sufficient for many cases. If so, , ++ or (the latter only in case the service ++ provides a D-Bus interface) are the preferred options as they allow service program code to ++ precisely schedule when to consider the service started up successfully and when to proceed with ++ follow-up units. The / service types require ++ explicit support in the service codebase (as sd_notify() or an equivalent API ++ needs to be invoked by the service at the appropriate time) — if it's not supported, then ++ is an alternative: it supports the traditional UNIX service start-up ++ protocol. Finally, might be an option for cases where it is enough to ensure ++ the service binary is invoked, and where the service binary itself executes no or little ++ initialization on its own (and its initialization is unlikely to fail). Note that using any type ++ other than possibly delays the boot process, as the service manager needs ++ to wait for service initialization to complete. It is hence recommended not to needlessly use any ++ types other than . (Also note it is generally not recommended to use ++ or for long-running services.) + + + +@@ -319,9 +337,10 @@ + the file may not be a symlink to a file owned by a different user (neither directly nor indirectly), and the + PID file must refer to a process already belonging to the service. + +- Note that PID files should be avoided in modern projects. Use or +- where possible, which does not require use of PID files to determine the +- main process of a service and avoids needless forking. ++ Note that PID files should be avoided in modern projects. Use , ++ or where possible, which does not ++ require use of PID files to determine the main process of a service and avoids needless ++ forking. + + + +@@ -443,12 +462,13 @@ + with a - exit successfully. + + ExecStartPost= commands are only run after the commands specified in +- ExecStart= have been invoked successfully, as determined by Type= +- (i.e. the process has been started for Type=simple or Type=idle, the last +- ExecStart= process exited successfully for Type=oneshot, the initial +- process exited successfully for Type=forking, READY=1 is sent for +- Type=notify, or the BusName= has been taken for +- Type=dbus). ++ ExecStart= have been invoked successfully, as determined by ++ Type= (i.e. the process has been started for Type=simple or ++ Type=idle, the last ExecStart= process exited successfully for ++ Type=oneshot, the initial process exited successfully for ++ Type=forking, READY=1 is sent for ++ Type=notify/Type=notify-reload, or the ++ BusName= has been taken for Type=dbus). + + Note that ExecStartPre= may not be + used to start long-running processes. All processes forked +@@ -487,30 +507,26 @@ + + + ExecReload= +- Commands to execute to trigger a configuration +- reload in the service. This argument takes multiple command +- lines, following the same scheme as described for +- ExecStart= above. Use of this setting is +- optional. Specifier and environment variable substitution is +- supported here following the same scheme as for ++ ++ Commands to execute to trigger a configuration reload in the service. This argument ++ takes multiple command lines, following the same scheme as described for ++ ExecStart= above. Use of this setting is optional. Specifier and environment ++ variable substitution is supported here following the same scheme as for + ExecStart=. + +- One additional, special environment variable is set: if +- known, $MAINPID is set to the main process +- of the daemon, and may be used for command lines like the +- following: ++ One additional, special environment variable is set: if known, $MAINPID is ++ set to the main process of the daemon, and may be used for command lines like the following: + + ExecReload=kill -HUP $MAINPID + +- Note however that reloading a daemon by sending a signal +- (as with the example line above) is usually not a good choice, +- because this is an asynchronous operation and hence not +- suitable to order reloads of multiple services against each +- other. It is strongly recommended to set +- ExecReload= to a command that not only +- triggers a configuration reload of the daemon, but also +- synchronously waits for it to complete. For example, +- dbus-broker1 ++ Note however that reloading a daemon by enqueing a signal (as with the example line above) is ++ usually not a good choice, because this is an asynchronous operation and hence not suitable when ++ ordering reloads of multiple services against each other. It is thus strongly recommended to either ++ use Type= in place of ++ ExecReload=, or to set ExecReload= to a command that not only ++ triggers a configuration reload of the daemon, but also synchronously waits for it to complete. For ++ example, dbus-broker1 + uses the following: + + ExecReload=busctl call org.freedesktop.DBus \ +@@ -605,12 +621,13 @@ + systemd-system.conf5). + + +- If a service of Type=notify sends EXTEND_TIMEOUT_USEC=…, this may cause +- the start time to be extended beyond TimeoutStartSec=. The first receipt of this message +- must occur before TimeoutStartSec= is exceeded, and once the start time has extended beyond +- TimeoutStartSec=, the service manager will allow the service to continue to start, provided +- the service repeats EXTEND_TIMEOUT_USEC=… within the interval specified until the service +- startup status is finished by READY=1. (see ++ If a service of Type=notify/Type=notify-reload sends ++ EXTEND_TIMEOUT_USEC=…, this may cause the start time to be extended beyond ++ TimeoutStartSec=. The first receipt of this message must occur before ++ TimeoutStartSec= is exceeded, and once the start time has extended beyond ++ TimeoutStartSec=, the service manager will allow the service to continue to start, ++ provided the service repeats EXTEND_TIMEOUT_USEC=… within the interval specified ++ until the service startup status is finished by READY=1. (see + sd_notify3). + + +@@ -633,12 +650,14 @@ + systemd-system.conf5). + + +- If a service of Type=notify sends EXTEND_TIMEOUT_USEC=…, this may cause +- the stop time to be extended beyond TimeoutStopSec=. The first receipt of this message +- must occur before TimeoutStopSec= is exceeded, and once the stop time has extended beyond +- TimeoutStopSec=, the service manager will allow the service to continue to stop, provided +- the service repeats EXTEND_TIMEOUT_USEC=… within the interval specified, or terminates itself +- (see sd_notify3). ++ If a service of Type=notify/Type=notify-reload sends ++ EXTEND_TIMEOUT_USEC=…, this may cause the stop time to be extended beyond ++ TimeoutStopSec=. The first receipt of this message must occur before ++ TimeoutStopSec= is exceeded, and once the stop time has extended beyond ++ TimeoutStopSec=, the service manager will allow the service to continue to stop, ++ provided the service repeats EXTEND_TIMEOUT_USEC=… within the interval specified, ++ or terminates itself (see ++ sd_notify3). + + + +@@ -661,13 +680,15 @@ + systemd-system.conf5). + + +- If a service of Type=notify handles SIGABRT itself (instead of relying +- on the kernel to write a core dump) it can send EXTEND_TIMEOUT_USEC=… to +- extended the abort time beyond TimeoutAbortSec=. The first receipt of this message +- must occur before TimeoutAbortSec= is exceeded, and once the abort time has extended beyond +- TimeoutAbortSec=, the service manager will allow the service to continue to abort, provided +- the service repeats EXTEND_TIMEOUT_USEC=… within the interval specified, or terminates itself +- (see sd_notify3). ++ If a service of Type=notify/Type=notify-reload handles ++ SIGABRT itself (instead of relying on the kernel to write a core dump) it can ++ send EXTEND_TIMEOUT_USEC=… to extended the abort time beyond ++ TimeoutAbortSec=. The first receipt of this message must occur before ++ TimeoutAbortSec= is exceeded, and once the abort time has extended beyond ++ TimeoutAbortSec=, the service manager will allow the service to continue to abort, ++ provided the service repeats EXTEND_TIMEOUT_USEC=… within the interval specified, ++ or terminates itself (see ++ sd_notify3). + + + +@@ -710,12 +731,13 @@ + activation completed. Pass infinity (the default) to configure no runtime + limit. + +- If a service of Type=notify sends EXTEND_TIMEOUT_USEC=…, this may cause +- the runtime to be extended beyond RuntimeMaxSec=. The first receipt of this message +- must occur before RuntimeMaxSec= is exceeded, and once the runtime has extended beyond +- RuntimeMaxSec=, the service manager will allow the service to continue to run, provided +- the service repeats EXTEND_TIMEOUT_USEC=… within the interval specified until the service +- shutdown is achieved by STOPPING=1 (or termination). (see ++ If a service of Type=notify/Type=notify-reload sends ++ EXTEND_TIMEOUT_USEC=…, this may cause the runtime to be extended beyond ++ RuntimeMaxSec=. The first receipt of this message must occur before ++ RuntimeMaxSec= is exceeded, and once the runtime has extended beyond ++ RuntimeMaxSec=, the service manager will allow the service to continue to run, ++ provided the service repeats EXTEND_TIMEOUT_USEC=… within the interval specified ++ until the service shutdown is achieved by STOPPING=1 (or termination). (see + sd_notify3). + + +@@ -1023,16 +1045,19 @@ + + NotifyAccess= + Controls access to the service status notification socket, as accessible via the +- sd_notify3 call. Takes one +- of (the default), , or +- . If , no daemon status updates are accepted from the service +- processes, all status update messages are ignored. If , only service updates sent from the +- main process of the service are accepted. If , only service updates sent from any of the +- main or control processes originating from one of the Exec*= commands are accepted. If +- , all services updates from all members of the service's control group are accepted. This +- option should be set to open access to the notification socket when using Type=notify or +- WatchdogSec= (see above). If those options are used but NotifyAccess= is +- not configured, it will be implicitly set to . ++ sd_notify3 ++ call. Takes one of (the default), , ++ or . If , no daemon status updates are accepted from the ++ service processes, all status update messages are ignored. If , only service ++ updates sent from the main process of the service are accepted. If , only ++ service updates sent from any of the main or control processes originating from one of the ++ Exec*= commands are accepted. If , all services updates from ++ all members of the service's control group are accepted. This option should be set to open access to ++ the notification socket when using ++ Type=notify/Type=notify-reload or ++ WatchdogSec= (see above). If those options are used but ++ NotifyAccess= is not configured, it will be implicitly set to ++ . + + Note that sd_notify() notifications may be attributed to units correctly only if + either the sending process is still around at the time PID 1 processes the message, or if the sending process +@@ -1156,6 +1181,15 @@ + kills, this setting determines the state of the unit after systemd-oomd kills a + cgroup associated with it. + ++ ++ ++ ReloadSignal= ++ Configures the UNIX process signal to send to the service's main process when asked ++ to reload the service's configuration. Defaults to SIGHUP. This option has no ++ effect unless Type= is used, see ++ above. ++ ++ + + + Check +@@ -1319,16 +1353,13 @@ WantedBy=multi-user.target + systemd.kill5 + for details. + +- Note that this unit type does not include any type of +- notification when a service has completed initialization. For +- this, you should use other unit types, such as +- Type= if the service +- understands systemd's notification protocol, +- Type= if the service +- can background itself or +- Type= if the unit +- acquires a DBus name once initialization is complete. See +- below. ++ Note that this unit type does not include any type of notification when a service has completed ++ initialization. For this, you should use other unit types, such as ++ Type=/Type= ++ if the service understands systemd's notification protocol, ++ Type= if the service can background itself or ++ Type= if the unit acquires a DBus name once initialization is ++ complete. See below. + + + +@@ -1505,15 +1536,12 @@ SystemdService=simple-dbus-service.service + + Services that notify systemd about their initialization + +- Type= services +- are really easy to write, but have the major disadvantage of +- systemd not being able to tell when initialization of the given +- service is complete. For this reason, systemd supports a simple +- notification protocol that allows daemons to make systemd aware +- that they are done initializing. Use +- Type= for this. A +- typical service file for such a daemon would look like +- this: ++ Type= services are really easy to write, but have the ++ major disadvantage of systemd not being able to tell when initialization of the given service is ++ complete. For this reason, systemd supports a simple notification protocol that allows daemons to make ++ systemd aware that they are done initializing. Use Type= or ++ Type= for this. A typical service file for such a ++ daemon would look like this: + + [Unit] + Description=Simple notifying service diff --git a/SOURCES/0443-pid1-make-sure-we-send-our-calling-service-manager-R.patch b/SOURCES/0443-pid1-make-sure-we-send-our-calling-service-manager-R.patch new file mode 100644 index 0000000..61044dd --- /dev/null +++ b/SOURCES/0443-pid1-make-sure-we-send-our-calling-service-manager-R.patch @@ -0,0 +1,105 @@ +From 567b6dcd4ff8e4a9c9b0b1629fa8c015d5e6a724 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 2 Jan 2023 15:01:56 +0100 +Subject: [PATCH] pid1: make sure we send our calling service manager + RELOADING=1 when reloading + +And send READY=1 again when we are done with it. + +We do this not only for "daemon-reload" but also for "daemon-reexec" and +"switch-root", since from the perspective of an encapsulating service +manager these three operations are not that different. + +(cherry picked from commit dd0ab174c36492cdcb92cf46844fb0905b1d4a7e) + +Related: RHEL-6090 +--- + src/core/main.c | 10 ++++++++++ + src/core/manager.c | 12 ++++++++++++ + src/core/manager.h | 1 + + units/user@.service.in | 2 +- + 4 files changed, 24 insertions(+), 1 deletion(-) + +diff --git a/src/core/main.c b/src/core/main.c +index 0e2e5448bb..126a4bce8c 100644 +--- a/src/core/main.c ++++ b/src/core/main.c +@@ -1925,6 +1925,8 @@ static int invoke_main_loop( + LogTarget saved_log_target; + int saved_log_level; + ++ manager_send_reloading(m); ++ + log_info("Reloading."); + + /* First, save any overridden log level/target, then parse the configuration file, +@@ -1955,6 +1957,10 @@ static int invoke_main_loop( + } + + case MANAGER_REEXECUTE: ++ ++ manager_send_reloading(m); /* From the perspective of the manager calling us this is ++ * pretty much the same as a reload */ ++ + r = prepare_reexecute(m, &arg_serialization, ret_fds, false); + if (r < 0) { + *ret_error_message = "Failed to prepare for reexecution"; +@@ -1970,6 +1976,10 @@ static int invoke_main_loop( + return objective; + + case MANAGER_SWITCH_ROOT: ++ ++ manager_send_reloading(m); /* From the perspective of the manager calling us this is ++ * pretty much the same as a reload */ ++ + manager_set_switching_root(m, true); + + if (!m->switch_root_init) { +diff --git a/src/core/manager.c b/src/core/manager.c +index 6371810ce3..b34103d7d3 100644 +--- a/src/core/manager.c ++++ b/src/core/manager.c +@@ -3641,6 +3641,18 @@ void manager_check_finished(Manager *m) { + manager_invalidate_startup_units(m); + } + ++void manager_send_reloading(Manager *m) { ++ assert(m); ++ ++ /* Let whoever invoked us know that we are now reloading */ ++ (void) sd_notifyf(/* unset= */ false, ++ "RELOADING=1\n" ++ "MONOTONIC_USEC=" USEC_FMT "\n", now(CLOCK_MONOTONIC)); ++ ++ /* And ensure that we'll send READY=1 again as soon as we are ready again */ ++ m->ready_sent = false; ++} ++ + static bool generator_path_any(const char* const* paths) { + bool found = false; + +diff --git a/src/core/manager.h b/src/core/manager.h +index 75c16d6e26..87e63c3b68 100644 +--- a/src/core/manager.h ++++ b/src/core/manager.h +@@ -535,6 +535,7 @@ void manager_send_unit_plymouth(Manager *m, Unit *u); + bool manager_unit_inactive_or_pending(Manager *m, const char *name); + + void manager_check_finished(Manager *m); ++void manager_send_reloading(Manager *m); + + void disable_printk_ratelimit(void); + void manager_recheck_dbus(Manager *m); +diff --git a/units/user@.service.in b/units/user@.service.in +index efbd5dfbc8..2c99f50905 100644 +--- a/units/user@.service.in ++++ b/units/user@.service.in +@@ -17,7 +17,7 @@ IgnoreOnIsolate=yes + [Service] + User=%i + PAMName=systemd-user +-Type=notify ++Type=notify-reload + ExecStart={{ROOTLIBEXECDIR}}/systemd --user + Slice=user-%i.slice + KillMode=mixed diff --git a/SOURCES/0444-networkd-implement-Type-notify-reload-protocol.patch b/SOURCES/0444-networkd-implement-Type-notify-reload-protocol.patch new file mode 100644 index 0000000..75ba689 --- /dev/null +++ b/SOURCES/0444-networkd-implement-Type-notify-reload-protocol.patch @@ -0,0 +1,166 @@ +From fef242735b987c1870bcd0460cc0c802e78a3cde Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 2 Jan 2023 17:25:46 +0100 +Subject: [PATCH] networkd: implement Type=notify-reload protocol + +(cherry picked from commit 0e07cdb0e77d0322bc866b5e13abbe38e988059d) + +Related: RHEL-6090 +--- + src/network/networkd-manager-bus.c | 13 +------- + src/network/networkd-manager.c | 48 ++++++++++++++++++++++++++---- + src/network/networkd-manager.h | 2 ++ + src/network/networkd.c | 2 -- + units/systemd-networkd.service.in | 3 +- + 5 files changed, 47 insertions(+), 21 deletions(-) + +diff --git a/src/network/networkd-manager-bus.c b/src/network/networkd-manager-bus.c +index 2ab3aaadc2..67f951df69 100644 +--- a/src/network/networkd-manager-bus.c ++++ b/src/network/networkd-manager-bus.c +@@ -197,7 +197,6 @@ static int bus_method_reconfigure_link(sd_bus_message *message, void *userdata, + + static int bus_method_reload(sd_bus_message *message, void *userdata, sd_bus_error *error) { + Manager *manager = userdata; +- Link *link; + int r; + + r = bus_verify_polkit_async(message, CAP_NET_ADMIN, +@@ -209,20 +208,10 @@ static int bus_method_reload(sd_bus_message *message, void *userdata, sd_bus_err + if (r == 0) + return 1; /* Polkit will call us back */ + +- r = netdev_load(manager, true); ++ r = manager_reload(manager); + if (r < 0) + return r; + +- r = network_reload(manager); +- if (r < 0) +- return r; +- +- HASHMAP_FOREACH(link, manager->links_by_index) { +- r = link_reconfigure(link, /* force = */ false); +- if (r < 0) +- return r; +- } +- + return sd_bus_reply_method_return(message, NULL); + } + +diff --git a/src/network/networkd-manager.c b/src/network/networkd-manager.c +index cdfd29bc0e..362ee84b09 100644 +--- a/src/network/networkd-manager.c ++++ b/src/network/networkd-manager.c +@@ -483,6 +483,14 @@ static int signal_restart_callback(sd_event_source *s, const struct signalfd_sig + return sd_event_exit(sd_event_source_get_event(s), 0); + } + ++static int signal_reload_callback(sd_event_source *s, const struct signalfd_siginfo *si, void *userdata) { ++ Manager *m = ASSERT_PTR(userdata); ++ ++ manager_reload(m); ++ ++ return 0; ++} ++ + static int manager_set_keep_configuration(Manager *m) { + int r; + +@@ -517,12 +525,11 @@ int manager_setup(Manager *m) { + if (r < 0) + return r; + +- assert_se(sigprocmask_many(SIG_SETMASK, NULL, SIGINT, SIGTERM, SIGUSR2, -1) >= 0); +- + (void) sd_event_set_watchdog(m->event, true); +- (void) sd_event_add_signal(m->event, NULL, SIGTERM, signal_terminate_callback, m); +- (void) sd_event_add_signal(m->event, NULL, SIGINT, signal_terminate_callback, m); +- (void) sd_event_add_signal(m->event, NULL, SIGUSR2, signal_restart_callback, m); ++ (void) sd_event_add_signal(m->event, NULL, SIGTERM | SD_EVENT_SIGNAL_PROCMASK, signal_terminate_callback, m); ++ (void) sd_event_add_signal(m->event, NULL, SIGINT | SD_EVENT_SIGNAL_PROCMASK, signal_terminate_callback, m); ++ (void) sd_event_add_signal(m->event, NULL, SIGUSR2 | SD_EVENT_SIGNAL_PROCMASK, signal_restart_callback, m); ++ (void) sd_event_add_signal(m->event, NULL, SIGHUP | SD_EVENT_SIGNAL_PROCMASK, signal_reload_callback, m); + + r = sd_event_add_post(m->event, NULL, manager_dirty_handler, m); + if (r < 0) +@@ -1078,3 +1085,34 @@ int manager_set_timezone(Manager *m, const char *tz) { + + return 0; + } ++ ++int manager_reload(Manager *m) { ++ Link *link; ++ int r; ++ ++ assert(m); ++ ++ (void) sd_notifyf(/* unset= */ false, ++ "RELOADING=1\n" ++ "STATUS=Reloading configuration...\n" ++ "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); ++ ++ r = netdev_load(m, /* reload= */ true); ++ if (r < 0) ++ goto finish; ++ ++ r = network_reload(m); ++ if (r < 0) ++ goto finish; ++ ++ HASHMAP_FOREACH(link, m->links_by_index) { ++ r = link_reconfigure(link, /* force = */ false); ++ if (r < 0) ++ goto finish; ++ } ++ ++ r = 0; ++finish: ++ (void) sd_notify(/* unset= */ false, NOTIFY_READY); ++ return r; ++} +diff --git a/src/network/networkd-manager.h b/src/network/networkd-manager.h +index 40e6092f85..e6183af0e4 100644 +--- a/src/network/networkd-manager.h ++++ b/src/network/networkd-manager.h +@@ -115,4 +115,6 @@ int manager_enumerate(Manager *m); + int manager_set_hostname(Manager *m, const char *hostname); + int manager_set_timezone(Manager *m, const char *timezone); + ++int manager_reload(Manager *m); ++ + DEFINE_TRIVIAL_CLEANUP_FUNC(Manager*, manager_free); +diff --git a/src/network/networkd.c b/src/network/networkd.c +index d61769d9f3..68760e8ff4 100644 +--- a/src/network/networkd.c ++++ b/src/network/networkd.c +@@ -81,8 +81,6 @@ static int run(int argc, char *argv[]) { + if (r < 0) + log_warning_errno(r, "Could not create runtime directory 'lldp': %m"); + +- assert_se(sigprocmask_many(SIG_BLOCK, NULL, SIGTERM, SIGINT, -1) >= 0); +- + r = manager_new(&m, /* test_mode = */ false); + if (r < 0) + return log_error_errno(r, "Could not create manager: %m"); +diff --git a/units/systemd-networkd.service.in b/units/systemd-networkd.service.in +index d15129e7f0..d8b935a358 100644 +--- a/units/systemd-networkd.service.in ++++ b/units/systemd-networkd.service.in +@@ -24,7 +24,6 @@ BusName=org.freedesktop.network1 + CapabilityBoundingSet=CAP_NET_ADMIN CAP_NET_BIND_SERVICE CAP_NET_BROADCAST CAP_NET_RAW + DeviceAllow=char-* rw + ExecStart=!!{{ROOTLIBEXECDIR}}/systemd-networkd +-ExecReload=networkctl reload + FileDescriptorStoreMax=512 + LockPersonality=yes + MemoryDenyWriteExecute=yes +@@ -48,7 +47,7 @@ RuntimeDirectoryPreserve=yes + SystemCallArchitectures=native + SystemCallErrorNumber=EPERM + SystemCallFilter=@system-service +-Type=notify ++Type=notify-reload + User=systemd-network + {{SERVICE_WATCHDOG}} + diff --git a/SOURCES/0445-udevd-implement-the-full-Type-notify-reload-protocol.patch b/SOURCES/0445-udevd-implement-the-full-Type-notify-reload-protocol.patch new file mode 100644 index 0000000..86daa8c --- /dev/null +++ b/SOURCES/0445-udevd-implement-the-full-Type-notify-reload-protocol.patch @@ -0,0 +1,127 @@ +From daa0a0268e9ed03b8e3c39f003266d0b14cae120 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 2 Jan 2023 17:21:16 +0100 +Subject: [PATCH] udevd: implement the full Type=notify-reload protocol + +We are basically already there, just need to add MONOTONIC_USEC= to the +RELOADING=1 message, and make sure the message is generated in really +all cases. + +(cherry picked from commit f84331539deae28fbeb42d45ad0c8d583b3372a3) + +Related: RHEL-6090 +--- + src/udev/udevd.c | 47 +++++++++++++++++++--------------- + units/systemd-udevd.service.in | 3 +-- + 2 files changed, 28 insertions(+), 22 deletions(-) + +diff --git a/src/udev/udevd.c b/src/udev/udevd.c +index ccc3c0eece..6d82a6eff2 100644 +--- a/src/udev/udevd.c ++++ b/src/udev/udevd.c +@@ -32,6 +32,7 @@ + #include "cgroup-setup.h" + #include "cgroup-util.h" + #include "cpu-set-util.h" ++#include "daemon-util.h" + #include "dev-setup.h" + #include "device-monitor-private.h" + #include "device-private.h" +@@ -331,9 +332,7 @@ static void manager_exit(Manager *manager) { + + manager->exit = true; + +- sd_notify(false, +- "STOPPING=1\n" +- "STATUS=Starting shutdown..."); ++ (void) sd_notify(/* unset= */ false, NOTIFY_STOPPING); + + /* close sources of new events and discard buffered events */ + manager->ctrl = udev_ctrl_unref(manager->ctrl); +@@ -351,7 +350,7 @@ static void manager_exit(Manager *manager) { + static void notify_ready(void) { + int r; + +- r = sd_notifyf(false, ++ r = sd_notifyf(/* unset= */ false, + "READY=1\n" + "STATUS=Processing with %u children at max", arg_children_max); + if (r < 0) +@@ -376,23 +375,33 @@ static void manager_reload(Manager *manager, bool force) { + mac_selinux_maybe_reload(); + + /* Nothing changed. It is not necessary to reload. */ +- if (!udev_rules_should_reload(manager->rules) && !udev_builtin_should_reload()) +- return; ++ if (!udev_rules_should_reload(manager->rules) && !udev_builtin_should_reload()) { + +- sd_notify(false, +- "RELOADING=1\n" +- "STATUS=Flushing configuration..."); ++ if (!force) ++ return; + +- manager_kill_workers(manager, false); ++ /* If we eat this up, then tell our service manager to just continue */ ++ (void) sd_notifyf(/* unset= */ false, ++ "RELOADING=1\n" ++ "STATUS=Skipping configuration reloading, nothing changed.\n" ++ "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); ++ } else { ++ (void) sd_notifyf(/* unset= */ false, ++ "RELOADING=1\n" ++ "STATUS=Flushing configuration...\n" ++ "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); + +- udev_builtin_exit(); +- udev_builtin_init(); ++ manager_kill_workers(manager, false); + +- r = udev_rules_load(&rules, arg_resolve_name_timing); +- if (r < 0) +- log_warning_errno(r, "Failed to read udev rules, using the previously loaded rules, ignoring: %m"); +- else +- udev_rules_free_and_replace(manager->rules, rules); ++ udev_builtin_exit(); ++ udev_builtin_init(); ++ ++ r = udev_rules_load(&rules, arg_resolve_name_timing); ++ if (r < 0) ++ log_warning_errno(r, "Failed to read udev rules, using the previously loaded rules, ignoring: %m"); ++ else ++ udev_rules_free_and_replace(manager->rules, rules); ++ } + + notify_ready(); + } +@@ -1982,9 +1991,7 @@ static int main_loop(Manager *manager) { + if (r < 0) + log_error_errno(r, "Event loop failed: %m"); + +- sd_notify(false, +- "STOPPING=1\n" +- "STATUS=Shutting down..."); ++ (void) sd_notify(/* unset= */ false, NOTIFY_STOPPING); + return r; + } + +diff --git a/units/systemd-udevd.service.in b/units/systemd-udevd.service.in +index e9dbe85ef4..dfc2a0e341 100644 +--- a/units/systemd-udevd.service.in ++++ b/units/systemd-udevd.service.in +@@ -18,14 +18,13 @@ ConditionPathIsReadWrite=/sys + [Service] + CapabilityBoundingSet=~CAP_SYS_TIME CAP_WAKE_ALARM + Delegate=pids +-Type=notify ++Type=notify-reload + # Note that udev will reset the value internally for its workers + OOMScoreAdjust=-1000 + Sockets=systemd-udevd-control.socket systemd-udevd-kernel.socket + Restart=always + RestartSec=0 + ExecStart={{ROOTLIBEXECDIR}}/systemd-udevd +-ExecReload=udevadm control --reload --timeout 0 + KillMode=mixed + TasksMax=infinity + PrivateMounts=yes diff --git a/SOURCES/0446-logind-implement-Type-notify-reload-protocol-properl.patch b/SOURCES/0446-logind-implement-Type-notify-reload-protocol-properl.patch new file mode 100644 index 0000000..62c47af --- /dev/null +++ b/SOURCES/0446-logind-implement-Type-notify-reload-protocol-properl.patch @@ -0,0 +1,52 @@ +From dd9aa5ffe940ac6d5204a04fce5faafc3fc01924 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 2 Jan 2023 17:35:23 +0100 +Subject: [PATCH] logind: implement Type=notify-reload protocol properly + +So close already. Let's add the two missing notifications too. + +Fixes: #18484 +(cherry picked from commit 5d71e463f49518c7702467f6145484afa31bf8ba) + +Related: RHEL-6090 +--- + src/login/logind.c | 6 ++++++ + units/systemd-logind.service.in | 1 + + 2 files changed, 7 insertions(+) + +diff --git a/src/login/logind.c b/src/login/logind.c +index cdca5ca58c..0348b19c05 100644 +--- a/src/login/logind.c ++++ b/src/login/logind.c +@@ -1014,6 +1014,11 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa + Manager *m = userdata; + int r; + ++ (void) sd_notifyf(/* unset= */ false, ++ "RELOADING=1\n" ++ "STATUS=Reloading configuration...\n" ++ "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)); ++ + manager_reset_config(m); + r = manager_parse_config_file(m); + if (r < 0) +@@ -1021,6 +1026,7 @@ static int manager_dispatch_reload_signal(sd_event_source *s, const struct signa + else + log_info("Config file reloaded."); + ++ (void) sd_notify(/* unset= */ false, NOTIFY_READY); + return 0; + } + +diff --git a/units/systemd-logind.service.in b/units/systemd-logind.service.in +index 042ea75d7a..24f5ddaa17 100644 +--- a/units/systemd-logind.service.in ++++ b/units/systemd-logind.service.in +@@ -58,6 +58,7 @@ StateDirectory=systemd/linger + SystemCallArchitectures=native + SystemCallErrorNumber=EPERM + SystemCallFilter=@system-service ++Type=notify-reload + {{SERVICE_WATCHDOG}} + + # Increase the default a bit in order to allow many simultaneous logins since diff --git a/SOURCES/0447-notify-add-stopping-reloading-switches.patch b/SOURCES/0447-notify-add-stopping-reloading-switches.patch new file mode 100644 index 0000000..0486221 --- /dev/null +++ b/SOURCES/0447-notify-add-stopping-reloading-switches.patch @@ -0,0 +1,297 @@ +From 2b5c9fceaaa30ec9c2d031c9ca32b71c43f22f98 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 3 Jan 2023 12:55:50 +0100 +Subject: [PATCH] notify: add --stopping + --reloading switches + +These wrap RELOADING=1 and STOPPING=1 messages. The former is +particularly useful, since we want to insert the MONOTONIC_USEC= field +into the message automatically, which is easy from C but harder from +shell. + +(cherry picked from commit fd0f4da5457fbf7136f2d1888142d5fea75fd45a) + +Related: RHEL-6090 +--- + man/systemd-notify.xml | 107 ++++++++++++++++++++++++----------------- + src/notify/notify.c | 39 +++++++++++++-- + 2 files changed, 97 insertions(+), 49 deletions(-) + +diff --git a/man/systemd-notify.xml b/man/systemd-notify.xml +index 1327d23155..a275123d40 100644 +--- a/man/systemd-notify.xml ++++ b/man/systemd-notify.xml +@@ -30,34 +30,35 @@ + + Description + +- systemd-notify may be called by daemon +- scripts to notify the init system about status changes. It can be +- used to send arbitrary information, encoded in an +- environment-block-like list of strings. Most importantly, it can be +- used for start-up completion notification. +- +- This is mostly just a wrapper around +- sd_notify() and makes this functionality ++ systemd-notify may be called by service scripts to notify the invoking service ++ manager about status changes. It can be used to send arbitrary information, encoded in an ++ environment-block-like list of strings. Most importantly, it can be used for start-up completion ++ notification. ++ ++ This is mostly just a wrapper around sd_notify() and makes this functionality + available to shell scripts. For details see + sd_notify3. + + +- The command line may carry a list of environment variables +- to send as part of the status update. ++ The command line may carry a list of environment variables to send as part of the status ++ update. + + Note that systemd will refuse reception of status updates from this command unless + NotifyAccess= is set for the service unit this command is called from. + +- Note that sd_notify() notifications may be attributed to units correctly only if either +- the sending process is still around at the time PID 1 processes the message, or if the sending process is +- explicitly runtime-tracked by the service manager. The latter is the case if the service manager originally forked +- off the process, i.e. on all processes that match NotifyAccess= or +- NotifyAccess=. Conversely, if an auxiliary process of the unit sends an +- sd_notify() message and immediately exits, the service manager might not be able to properly +- attribute the message to the unit, and thus will ignore it, even if NotifyAccess= is set for it. When is used, all synchronization for reception of notifications +- is disabled, and hence the aforementioned race may occur if the invoking process is not the service manager or spawned +- by the service manager. ++ Note that sd_notify() notifications may be attributed to units correctly only ++ if either the sending process is still around at the time the service manager processes the message, or ++ if the sending process is explicitly runtime-tracked by the service manager. The latter is the case if ++ the service manager originally forked off the process, i.e. on all processes that match ++ NotifyAccess= or ++ NotifyAccess=. Conversely, if an auxiliary process of the unit ++ sends an sd_notify() message and immediately exits, the service manager might not be ++ able to properly attribute the message to the unit, and thus will ignore it, even if ++ NotifyAccess= is set for it. To address this ++ systemd-notify will wait until the notification message has been processed by the ++ service manager. When is used, this synchronization for reception of ++ notifications is disabled, and hence the aforementioned race may occur if the invoking process is not the ++ service manager or spawned by the service manager. + + Hence, systemd-notify will first attempt to invoke sd_notify() + pretending to have the PID of the invoking process. This will only succeed when invoked with sufficient privileges. +@@ -66,7 +67,6 @@ + — appears as sender of the message, which in turn is helpful if the shell process is the main process of a service, + due to the limitations of NotifyAccess=. Use the + switch to tweak this behaviour. +- + + + +@@ -78,22 +78,42 @@ + + + +- Inform the init system about service start-up +- completion. This is equivalent to systemd-notify +- READY=1. For details about the semantics of this +- option see ++ Inform the invoking service manager about service start-up or configuration reload ++ completion. This is equivalent to systemd-notify READY=1. For details about the ++ semantics of this option see ++ sd_notify3. ++ ++ ++ ++ ++ ++ Inform the invoking service manager about the beginning of a configuration reload ++ cycle. This is equivalent to systemd-notify RELOADING=1 (but implicitly also sets ++ a MONOTONIC_USEC= field as required for Type=notify-reload ++ services, see ++ systemd.service5, ++ for details). For details about the semantics of this option see ++ sd_notify3. ++ ++ ++ ++ ++ ++ Inform the invoking service manager about the beginning of the shutdown phase of the ++ service. This is equivalent to systemd-notify STOPPING=1. For details about the ++ semantics of this option see + sd_notify3. + + + + + +- Inform the service manager about the main PID of the daemon. Takes a PID as ++ Inform the service manager about the main PID of the service. Takes a PID as + argument. If the argument is specified as auto or omitted, the PID of the process + that invoked systemd-notify is used, except if that's the service manager. If the + argument is specified as self, the PID of the systemd-notify + command itself is used, and if parent is specified the calling process' PID is +- used — even if it is the service manager. This is equivalent to systemd-notify ++ used — even if it is the service manager. The latter is equivalent to systemd-notify + MAINPID=$PID. For details about the semantics of this option see + sd_notify3. + +@@ -110,27 +130,26 @@ + + + +- Send a free-form status string for the daemon +- to the init systemd. This option takes the status string as +- argument. This is equivalent to systemd-notify +- STATUS=…. For details about the semantics of this +- option see +- sd_notify3. ++ Send a free-form human readable status string for the daemon to the service ++ manager. This option takes the status string as argument. This is equivalent to ++ systemd-notify STATUS=…. For details about the semantics of this option see ++ sd_notify3. This ++ information is shown in ++ systemctl1's ++ status output, among other places. + + + + + +- Returns 0 if the system was booted up with +- systemd, non-zero otherwise. If this option is passed, no +- message is sent. This option is hence unrelated to the other +- options. For details about the semantics of this option, see ++ Returns 0 if the system was booted up with systemd, non-zero otherwise. If this ++ option is passed, no message is sent. This option is hence unrelated to the other options. For ++ details about the semantics of this option, see + sd_booted3. An + alternate way to check for this state is to call +- systemctl1 +- with the is-system-running command. It will +- return offline if the system was not booted +- with systemd. ++ systemctl1 with ++ the is-system-running command. It will return offline if the ++ system was not booted with systemd. + + + +@@ -162,9 +181,8 @@ + + Start-up Notification and Status Updates + +- A simple shell daemon that sends start-up notifications +- after having set up its communication channel. During runtime it +- sends further status updates to the init system: ++ A simple shell daemon that sends start-up notifications after having set up its communication ++ channel. During runtime it sends further status updates to the init system: + + #!/bin/sh + +@@ -192,5 +210,4 @@ done + sd_booted3 + + +- + +diff --git a/src/notify/notify.c b/src/notify/notify.c +index 7b23e7bdb0..2d4900a110 100644 +--- a/src/notify/notify.c ++++ b/src/notify/notify.c +@@ -23,6 +23,8 @@ + #include "util.h" + + static bool arg_ready = false; ++static bool arg_reloading = false; ++static bool arg_stopping = false; + static pid_t arg_pid = 0; + static const char *arg_status = NULL; + static bool arg_booted = false; +@@ -42,7 +44,10 @@ static int help(void) { + "\n%sNotify the init system about service status updates.%s\n\n" + " -h --help Show this help\n" + " --version Show package version\n" +- " --ready Inform the init system about service start-up completion\n" ++ " --ready Inform the service manager about service start-up/reload\n" ++ " completion\n" ++ " --reloading Inform the service manager about configuration reloading\n" ++ " --stopping Inform the service manager about service shutdown\n" + " --pid[=PID] Set main PID of daemon\n" + " --uid=USER Set user to send from\n" + " --status=TEXT Set status text\n" +@@ -81,6 +86,8 @@ static int parse_argv(int argc, char *argv[]) { + + enum { + ARG_READY = 0x100, ++ ARG_RELOADING, ++ ARG_STOPPING, + ARG_VERSION, + ARG_PID, + ARG_STATUS, +@@ -93,6 +100,8 @@ static int parse_argv(int argc, char *argv[]) { + { "help", no_argument, NULL, 'h' }, + { "version", no_argument, NULL, ARG_VERSION }, + { "ready", no_argument, NULL, ARG_READY }, ++ { "reloading", no_argument, NULL, ARG_RELOADING }, ++ { "stopping", no_argument, NULL, ARG_STOPPING }, + { "pid", optional_argument, NULL, ARG_PID }, + { "status", required_argument, NULL, ARG_STATUS }, + { "booted", no_argument, NULL, ARG_BOOTED }, +@@ -120,6 +129,14 @@ static int parse_argv(int argc, char *argv[]) { + arg_ready = true; + break; + ++ case ARG_RELOADING: ++ arg_reloading = true; ++ break; ++ ++ case ARG_STOPPING: ++ arg_stopping = true; ++ break; ++ + case ARG_PID: + if (isempty(optarg) || streq(optarg, "auto")) { + arg_pid = getppid(); +@@ -176,6 +193,8 @@ static int parse_argv(int argc, char *argv[]) { + + if (optind >= argc && + !arg_ready && ++ !arg_stopping && ++ !arg_reloading && + !arg_status && + !arg_pid && + !arg_booted) { +@@ -187,10 +206,10 @@ static int parse_argv(int argc, char *argv[]) { + } + + static int run(int argc, char* argv[]) { +- _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL; ++ _cleanup_free_ char *status = NULL, *cpid = NULL, *n = NULL, *monotonic_usec = NULL; + _cleanup_strv_free_ char **final_env = NULL; +- char* our_env[4]; +- unsigned i = 0; ++ char* our_env[7]; ++ size_t i = 0; + pid_t source_pid; + int r; + +@@ -212,9 +231,21 @@ static int run(int argc, char* argv[]) { + return r <= 0; + } + ++ if (arg_reloading) { ++ our_env[i++] = (char*) "RELOADING=1"; ++ ++ if (asprintf(&monotonic_usec, "MONOTONIC_USEC=" USEC_FMT, now(CLOCK_MONOTONIC)) < 0) ++ return log_oom(); ++ ++ our_env[i++] = monotonic_usec; ++ } ++ + if (arg_ready) + our_env[i++] = (char*) "READY=1"; + ++ if (arg_stopping) ++ our_env[i++] = (char*) "STOPPING=1"; ++ + if (arg_status) { + status = strjoin("STATUS=", arg_status); + if (!status) diff --git a/SOURCES/0448-test-add-Type-notify-reload-testcase.patch b/SOURCES/0448-test-add-Type-notify-reload-testcase.patch new file mode 100644 index 0000000..3d21257 --- /dev/null +++ b/SOURCES/0448-test-add-Type-notify-reload-testcase.patch @@ -0,0 +1,74 @@ +From 3a2cb37fdbe4e761ae649716f6f8f71feffdd608 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 3 Jan 2023 12:56:53 +0100 +Subject: [PATCH] test: add Type=notify-reload testcase + +(cherry picked from commit ee52bbc68f129cfed833990906c0a0a77ee12c42) + +Related: RHEL-6090 +--- + test/units/testsuite-59.sh | 51 ++++++++++++++++++++++++++++++++++++++ + 1 file changed, 51 insertions(+) + +diff --git a/test/units/testsuite-59.sh b/test/units/testsuite-59.sh +index 83db053107..475766a851 100755 +--- a/test/units/testsuite-59.sh ++++ b/test/units/testsuite-59.sh +@@ -83,6 +83,57 @@ systemctl start testservice-abort-restart-59.service + systemctl --signal=SIGABRT kill testservice-abort-restart-59.service + wait_on_state_or_fail "testservice-abort-restart-59.service" "failed" "30" + ++# Let's now test the notify-reload logic ++ ++cat >/run/notify-reload-test.sh </testok diff --git a/SOURCES/0449-update-TODO.patch b/SOURCES/0449-update-TODO.patch new file mode 100644 index 0000000..ec98acc --- /dev/null +++ b/SOURCES/0449-update-TODO.patch @@ -0,0 +1,45 @@ +From 3fa498dba2e67c1c97f25b093ec6c36e55023259 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 2 Jan 2023 16:48:51 +0100 +Subject: [PATCH] update TODO + +(cherry picked from commit 6fee784964b2763bd4307181335a433078ba977c) + +Related: RHEL-6090 +--- + TODO | 15 +++------------ + 1 file changed, 3 insertions(+), 12 deletions(-) + +diff --git a/TODO b/TODO +index 66c008bff3..8c67f93f35 100644 +--- a/TODO ++++ b/TODO +@@ -700,17 +700,9 @@ Features: + and synthesize initrd from it, and measure it. Signing is not necessary, as + microcode does that on its own. Pass as first initrd to kernel. + +-* Add a new service type very similar to Type=notify, that goes one step +- further and extends the protocol to cover reloads. Specifically, SIGHUP will +- become the official way to reload, and daemon has to respond with sd_notify() +- to report when it starts reloading, and when it is complete reloading. Care +- must be taken to remove races from this model. I.e. PID 1 needs to take +- CLOCK_MONOTONIC, then send SIGHUP, then wait for at least one RELOADING=1 +- message that comes with a newer timestamp, then wait for a READY=1 message. +- while we are at it, also maybe extend the logic to require handling of some +- specific SIGRT signal for setting debug log level, that carries the level via +- the sigqueue() data parameter. With that we extended with minimal logic the +- service runtime logic quite substantially. ++* Maybe extend the service protocol to support handling of some specific SIGRT ++ signal for setting service log level, that carries the level via the ++ sigqueue() data parameter. Enable this via unit file setting. + + * firstboot: maybe just default to C.UTF-8 locale if nothing is set, so that we + don't query this unnecessarily in entirely uninitialized +@@ -1738,7 +1730,6 @@ Features: + * unit files: + - allow port=0 in .socket units + - maybe introduce ExecRestartPre= +- - add ReloadSignal= for configuring a reload signal to use + - implement Register= switch in .socket units to enable registration + in Avahi, RPC and other socket registration services. + - allow Type=simple with PIDFile= diff --git a/SOURCES/0450-core-check-for-SERVICE_RELOAD_NOTIFY-in-manager_dbus.patch b/SOURCES/0450-core-check-for-SERVICE_RELOAD_NOTIFY-in-manager_dbus.patch new file mode 100644 index 0000000..6384d8a --- /dev/null +++ b/SOURCES/0450-core-check-for-SERVICE_RELOAD_NOTIFY-in-manager_dbus.patch @@ -0,0 +1,40 @@ +From 036f0593b33ddc0f40a333d790a276c3cffe862e Mon Sep 17 00:00:00 2001 +From: msizanoen1 +Date: Tue, 2 May 2023 16:59:07 +0700 +Subject: [PATCH] core: check for SERVICE_RELOAD_NOTIFY in + manager_dbus_is_running + +This ensures that systemd won't erronously disconnect from the system +bus in case a bus recheck is triggered immediately after the bus service +emits `RELOADING=1`. + +This fixes an issue where systemd-logind sometimes randomly stops +receiving `UnitRemoved` after a system update. + +This also handles SERVICE_RELOAD_SIGNAL just in case somebody ever +creates a D-Bus broker implementation that uses `Type=notify-reload`. + +(cherry picked from commit 845824acddf2e7e08c94afe7cfee6e50a682c947) + +Related: RHEL-6090 +--- + src/core/manager.c | 6 +++++- + 1 file changed, 5 insertions(+), 1 deletion(-) + +diff --git a/src/core/manager.c b/src/core/manager.c +index b34103d7d3..eeee395b90 100644 +--- a/src/core/manager.c ++++ b/src/core/manager.c +@@ -1696,7 +1696,11 @@ static bool manager_dbus_is_running(Manager *m, bool deserialized) { + u = manager_get_unit(m, SPECIAL_DBUS_SERVICE); + if (!u) + return false; +- if (!IN_SET((deserialized ? SERVICE(u)->deserialized_state : SERVICE(u)->state), SERVICE_RUNNING, SERVICE_RELOAD)) ++ if (!IN_SET((deserialized ? SERVICE(u)->deserialized_state : SERVICE(u)->state), ++ SERVICE_RUNNING, ++ SERVICE_RELOAD, ++ SERVICE_RELOAD_NOTIFY, ++ SERVICE_RELOAD_SIGNAL)) + return false; + + return true; diff --git a/SOURCES/0451-Revert-man-mention-System-Administrator-s-Guide-in-s.patch b/SOURCES/0451-Revert-man-mention-System-Administrator-s-Guide-in-s.patch new file mode 100644 index 0000000..cfff1fd --- /dev/null +++ b/SOURCES/0451-Revert-man-mention-System-Administrator-s-Guide-in-s.patch @@ -0,0 +1,37 @@ +From 4b2fb9adb3cd46cf6fe9b7e093d3f513a44f8e14 Mon Sep 17 00:00:00 2001 +From: Frantisek Sumsal +Date: Thu, 14 Dec 2023 10:54:38 +0100 +Subject: [PATCH] Revert "man: mention System Administrator's Guide in + systemctl manpage" + +This reverts commit 5b2c931fb85d79db5a369a46eaeaf4ba297cbeef. + +Related: RHEL-19436 + +rhel-only +--- + man/systemctl.xml | 11 ----------- + 1 file changed, 11 deletions(-) + +diff --git a/man/systemctl.xml b/man/systemctl.xml +index b73d4ac048..55310c974e 100644 +--- a/man/systemctl.xml ++++ b/man/systemctl.xml +@@ -2518,17 +2518,6 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err + + + +- +- Examples +- +- For examples how to use systemctl in comparsion +- with old service and chkconfig command please see: +- +- Managing System Services +- +- +- +- + + See Also + diff --git a/SOURCES/0452-man-mention-RHEL-documentation-in-systemctl-s-man-pa.patch b/SOURCES/0452-man-mention-RHEL-documentation-in-systemctl-s-man-pa.patch new file mode 100644 index 0000000..a258826 --- /dev/null +++ b/SOURCES/0452-man-mention-RHEL-documentation-in-systemctl-s-man-pa.patch @@ -0,0 +1,33 @@ +From 024e4e1989e8e1a8d67429ab7f36dcca5734f81b Mon Sep 17 00:00:00 2001 +From: Frantisek Sumsal +Date: Thu, 14 Dec 2023 10:56:51 +0100 +Subject: [PATCH] man: mention RHEL documentation in systemctl's man page + +Resolves: RHEL-19436 + +rhel-only +--- + man/systemctl.xml | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/man/systemctl.xml b/man/systemctl.xml +index 55310c974e..1df0b158bd 100644 +--- a/man/systemctl.xml ++++ b/man/systemctl.xml +@@ -2518,6 +2518,16 @@ Jan 12 10:46:45 example.com bluetoothd[8900]: gatt-time-server: Input/output err + + + ++ ++ Examples ++ ++ For examples how to use systemctl in comparison with old service and chkconfig commands please see: ++ ++ Managing System Services ++ ++ ++ ++ + + See Also + diff --git a/SOURCES/0453-resolved-actually-check-authenticated-flag-of-SOA-tr.patch b/SOURCES/0453-resolved-actually-check-authenticated-flag-of-SOA-tr.patch new file mode 100644 index 0000000..cade927 --- /dev/null +++ b/SOURCES/0453-resolved-actually-check-authenticated-flag-of-SOA-tr.patch @@ -0,0 +1,37 @@ +From 92ca40483db514bac34d8cd29438f48a794fae91 Mon Sep 17 00:00:00 2001 +From: Michal Sekletar +Date: Wed, 20 Dec 2023 16:44:14 +0100 +Subject: [PATCH] resolved: actually check authenticated flag of SOA + transaction + +Fixes #25676 + +(cherry picked from commit 3b4cc1437b51fcc0b08da8cc3f5d1175eed25eb1) + +Resolves: RHEL-6216 +--- + src/resolve/resolved-dns-transaction.c | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/src/resolve/resolved-dns-transaction.c b/src/resolve/resolved-dns-transaction.c +index 0212569fb0..0306af84a2 100644 +--- a/src/resolve/resolved-dns-transaction.c ++++ b/src/resolve/resolved-dns-transaction.c +@@ -2800,7 +2800,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * + if (r == 0) + continue; + +- return FLAGS_SET(t->answer_query_flags, SD_RESOLVED_AUTHENTICATED); ++ return FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED); + } + + return true; +@@ -2827,7 +2827,7 @@ static int dns_transaction_requires_rrsig(DnsTransaction *t, DnsResourceRecord * + /* We found the transaction that was supposed to find the SOA RR for us. It was + * successful, but found no RR for us. This means we are not at a zone cut. In this + * case, we require authentication if the SOA lookup was authenticated too. */ +- return FLAGS_SET(t->answer_query_flags, SD_RESOLVED_AUTHENTICATED); ++ return FLAGS_SET(dt->answer_query_flags, SD_RESOLVED_AUTHENTICATED); + } + + return true; diff --git a/SOURCES/0454-udev-allow-denylist-for-reading-sysfs-attributes-whe.patch b/SOURCES/0454-udev-allow-denylist-for-reading-sysfs-attributes-whe.patch new file mode 100644 index 0000000..ca3c05f --- /dev/null +++ b/SOURCES/0454-udev-allow-denylist-for-reading-sysfs-attributes-whe.patch @@ -0,0 +1,404 @@ +From 9cd7868bc7cb5bda25c0470a9b4e349d4f2004fe Mon Sep 17 00:00:00 2001 +From: Lukas Nykryn +Date: Fri, 8 Dec 2023 12:33:06 +0100 +Subject: [PATCH] udev: allow/denylist for reading sysfs attributes when + composing a NIC name + +Users can currently pick specific versions of NIC naming, but that +does not guarantee that NIC names won't change after the kernel adds +a new sysfs attribute. + +This patch allows for an allow/deny list of sysfs attributes +that could be used when composing the name. + +These lists can be supplied as an hwdb entry in the form of +/etc/udev/hwdb.d/50-net-naming-allowlist.hwdb +net:naming:drvirtio_net + ID_NET_NAME_ALLOW=0 + ID_NET_NAME_ALLOW_ACPI_INDEX=1 + ID_NET_NAME_ALLOW_ADDR_ASSIGN_TYPE=1 + ID_NET_NAME_ALLOW_ADDRESS=1 + ID_NET_NAME_ALLOW_ARI_ENABLED=1 + ID_NET_NAME_ALLOW_DEV_PORT=1 + ID_NET_NAME_ALLOW_FUNCTION_ID=1 + ID_NET_NAME_ALLOW_IFLINK=1 + ID_NET_NAME_ALLOW_INDEX=1 + ID_NET_NAME_ALLOW_LABEL=1 + ID_NET_NAME_ALLOW_PHYS_PORT_NAME=1 + ID_NET_NAME_ALLOW_TYPE=1 + +(cherry picked from commit 3b2e7dc5a285edbbb1bf6aed2d88b889d801613f) + +Resolves: RHEL-1317 +--- + man/systemd.net-naming-scheme.xml | 69 ++++++++++++++++++++++++++ + rules.d/75-net-description.rules | 2 + + src/shared/netif-naming-scheme.c | 81 +++++++++++++++++++++++++++++++ + src/shared/netif-naming-scheme.h | 7 +++ + src/udev/udev-builtin-net_id.c | 34 ++++++------- + 5 files changed, 176 insertions(+), 17 deletions(-) + +diff --git a/man/systemd.net-naming-scheme.xml b/man/systemd.net-naming-scheme.xml +index c6ab86906a..ec9f3da437 100644 +--- a/man/systemd.net-naming-scheme.xml ++++ b/man/systemd.net-naming-scheme.xml +@@ -588,6 +588,45 @@ + particular version of systemd). + + ++ ++ Limiting the use of specific sysfs attributes ++ ++ When creating names for network cards, some naming schemes use data from sysfs populated ++ by the kernel. This means that although a specific naming scheme in udev is picked, ++ the network card's name can still change when a new kernel version adds a new sysfs attribute. ++ For example if kernel starts setting the phys_port_name, udev will append the ++ "nphys_port_name" suffix to the device name. ++ ++ ++ ++ ID_NET_NAME_ALLOW=BOOL ++ ++ This evironment value sets a fallback policy for reading a sysfs attribute. ++ If set to 0 udev will not read any sysfs attribute by default, unless it is ++ explicitly allowlisted, see below. If set to 1 udev can use any sysfs attribute ++ unless it is explicitly forbidden. The default value is 1. ++ ++ ++ ++ ++ ++ ID_NET_NAME_ALLOW_sysfsattr=BOOL ++ ++ This evironment value explicitly states if udev shall use the specified ++ sysfsattr, when composing the device name. ++ ++ ++ ++ ++ ++ With these options, users can set an allowlist or denylist for sysfs attributes. To create ++ an allowlist, the user needs to set ID_NET_NAME_ALLOW=0 for the device and then list ++ the allowed attributes with the ++ ID_NET_NAME_ALLOW_sysfsattr=1 ++ options. In case of a denylist, the user needs to provide the list of denied attributes with ++ the ID_NET_NAME_ALLOW_sysfsattr=0 options. ++ ++ + + Examples + +@@ -674,6 +713,36 @@ ID_NET_NAME_PATH=enp0s29u1u2 + ID_NET_NAME_MAC=enx026d3c00000a + ID_NET_NAME_PATH=encf5f0 + ++ ++ ++ Set an allowlist for reading sysfs attributes for network card naming ++ ++ /etc/udev/hwdb.d/50-net-naming-allowlist.hwdb ++net:naming:drvirtio_net:* ++ ID_NET_NAME_ALLOW=0 ++ ID_NET_NAME_ALLOW_ACPI_INDEX=1 ++ ID_NET_NAME_ALLOW_ADDR_ASSIGN_TYPE=1 ++ ID_NET_NAME_ALLOW_ADDRESS=1 ++ ID_NET_NAME_ALLOW_ARI_ENABLED=1 ++ ID_NET_NAME_ALLOW_DEV_PORT=1 ++ ID_NET_NAME_ALLOW_FUNCTION_ID=1 ++ ID_NET_NAME_ALLOW_IFLINK=1 ++ ID_NET_NAME_ALLOW_INDEX=1 ++ ID_NET_NAME_ALLOW_LABEL=1 ++ ID_NET_NAME_ALLOW_PHYS_PORT_NAME=1 ++ ID_NET_NAME_ALLOW_TYPE=1 ++ ++ ++ ++ Set a denylist so that specified sysfs attribute are ignored ++ ++ /etc/udev/hwdb.d/50-net-naming-denylist.hwdb ++net:naming:drvirtio_net:* ++ ID_NET_NAME_ALLOW=1 ++ ID_NET_NAME_ALLOW_DEV_PORT=0 ++ ID_NET_NAME_ALLOW_PHYS_PORT_NAME=0 ++ ++ + + + +diff --git a/rules.d/75-net-description.rules b/rules.d/75-net-description.rules +index 7e62f8b26b..5ba70a6545 100644 +--- a/rules.d/75-net-description.rules ++++ b/rules.d/75-net-description.rules +@@ -3,6 +3,8 @@ + ACTION=="remove", GOTO="net_end" + SUBSYSTEM!="net", GOTO="net_end" + ++IMPORT{builtin}="hwdb 'net:naming:dr$env{ID_NET_DRIVER}:'" ++ + IMPORT{builtin}="net_id" + + SUBSYSTEMS=="usb", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb" +diff --git a/src/shared/netif-naming-scheme.c b/src/shared/netif-naming-scheme.c +index 9cfa5ca8e6..e73c265371 100644 +--- a/src/shared/netif-naming-scheme.c ++++ b/src/shared/netif-naming-scheme.c +@@ -1,6 +1,9 @@ + /* SPDX-License-Identifier: LGPL-2.1-or-later */ + ++#include "sd-device.h" ++ + #include "alloc-util.h" ++#include "device-private.h" + #include "netif-naming-scheme.h" + #include "proc-cmdline.h" + #include "string-util.h" +@@ -119,3 +122,81 @@ static const char* const alternative_names_policy_table[_NAMEPOLICY_MAX] = { + }; + + DEFINE_STRING_TABLE_LOOKUP(alternative_names_policy, NamePolicy); ++ ++static int naming_sysattr_allowed_by_default(sd_device *dev) { ++ int r; ++ ++ assert(dev); ++ ++ r = device_get_property_bool(dev, "ID_NET_NAME_ALLOW"); ++ if (r == -ENOENT) ++ return true; ++ ++ return r; ++} ++ ++static int naming_sysattr_allowed(sd_device *dev, const char *sysattr) { ++ char *sysattr_property; ++ int r; ++ ++ assert(dev); ++ assert(sysattr); ++ ++ sysattr_property = strjoina("ID_NET_NAME_ALLOW_", sysattr); ++ ascii_strupper(sysattr_property); ++ ++ r = device_get_property_bool(dev, sysattr_property); ++ if (r == -ENOENT) ++ /* If ID_NET_NAME_ALLOW is not set or set to 1 default is to allow */ ++ return naming_sysattr_allowed_by_default(dev); ++ ++ return r; ++} ++ ++int device_get_sysattr_int_filtered(sd_device *device, const char *sysattr, int *ret_value) { ++ int r; ++ ++ r = naming_sysattr_allowed(device, sysattr); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ return -ENOENT; ++ ++ return device_get_sysattr_int(device, sysattr, ret_value); ++} ++ ++int device_get_sysattr_unsigned_filtered(sd_device *device, const char *sysattr, unsigned *ret_value) { ++ int r; ++ ++ r = naming_sysattr_allowed(device, sysattr); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ return -ENOENT; ++ ++ return device_get_sysattr_unsigned(device, sysattr, ret_value); ++} ++ ++int device_get_sysattr_bool_filtered(sd_device *device, const char *sysattr) { ++ int r; ++ ++ r = naming_sysattr_allowed(device, sysattr); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ return -ENOENT; ++ ++ return device_get_sysattr_bool(device, sysattr); ++} ++ ++int device_get_sysattr_value_filtered(sd_device *device, const char *sysattr, const char **ret_value) { ++ int r; ++ ++ r = naming_sysattr_allowed(device, sysattr); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ return -ENOENT; ++ ++ return sd_device_get_sysattr_value(device, sysattr, ret_value); ++} +diff --git a/src/shared/netif-naming-scheme.h b/src/shared/netif-naming-scheme.h +index ed45536f65..3baa7d5e72 100644 +--- a/src/shared/netif-naming-scheme.h ++++ b/src/shared/netif-naming-scheme.h +@@ -3,6 +3,8 @@ + + #include + ++#include "sd-device.h" ++ + #include "macro.h" + + /* So here's the deal: net_id is supposed to be an exercise in providing stable names for network devices. However, we +@@ -103,3 +105,8 @@ NamePolicy name_policy_from_string(const char *p) _pure_; + + const char *alternative_names_policy_to_string(NamePolicy p) _const_; + NamePolicy alternative_names_policy_from_string(const char *p) _pure_; ++ ++int device_get_sysattr_int_filtered(sd_device *device, const char *sysattr, int *ret_value); ++int device_get_sysattr_unsigned_filtered(sd_device *device, const char *sysattr, unsigned *ret_value); ++int device_get_sysattr_bool_filtered(sd_device *device, const char *sysattr); ++int device_get_sysattr_value_filtered(sd_device *device, const char *sysattr, const char **ret_value); +diff --git a/src/udev/udev-builtin-net_id.c b/src/udev/udev-builtin-net_id.c +index cecf854b98..c20df41c37 100644 +--- a/src/udev/udev-builtin-net_id.c ++++ b/src/udev/udev-builtin-net_id.c +@@ -177,11 +177,11 @@ static int dev_pci_onboard(sd_device *dev, const LinkInfo *info, NetNames *names + assert(names); + + /* ACPI _DSM — device specific method for naming a PCI or PCI Express device */ +- if (sd_device_get_sysattr_value(names->pcidev, "acpi_index", &attr) >= 0) ++ if (device_get_sysattr_value_filtered(names->pcidev, "acpi_index", &attr) >= 0) + log_device_debug(names->pcidev, "acpi_index=%s", attr); + else { + /* SMBIOS type 41 — Onboard Devices Extended Information */ +- r = sd_device_get_sysattr_value(names->pcidev, "index", &attr); ++ r = device_get_sysattr_value_filtered(names->pcidev, "index", &attr); + if (r < 0) + return r; + log_device_debug(names->pcidev, "index=%s", attr); +@@ -199,7 +199,7 @@ static int dev_pci_onboard(sd_device *dev, const LinkInfo *info, NetNames *names + "Not a valid onboard index: %lu", idx); + + /* kernel provided port index for multiple ports on a single PCI function */ +- if (sd_device_get_sysattr_value(dev, "dev_port", &attr) >= 0) { ++ if (device_get_sysattr_value_filtered(dev, "dev_port", &attr) >= 0) { + r = safe_atolu_full(attr, 10, &dev_port); + if (r < 0) + log_device_debug_errno(dev, r, "Failed to parse dev_port, ignoring: %m"); +@@ -223,7 +223,7 @@ static int dev_pci_onboard(sd_device *dev, const LinkInfo *info, NetNames *names + idx, strempty(info->phys_port_name), dev_port, + special_glyph(SPECIAL_GLYPH_ARROW_RIGHT), empty_to_na(names->pci_onboard)); + +- if (sd_device_get_sysattr_value(names->pcidev, "label", &names->pci_onboard_label) >= 0) ++ if (device_get_sysattr_value_filtered(names->pcidev, "label", &names->pci_onboard_label) >= 0) + log_device_debug(dev, "Onboard label from PCI device: %s", names->pci_onboard_label); + else + names->pci_onboard_label = NULL; +@@ -260,7 +260,7 @@ static int is_pci_multifunction(sd_device *dev) { + static bool is_pci_ari_enabled(sd_device *dev) { + const char *a; + +- if (sd_device_get_sysattr_value(dev, "ari_enabled", &a) < 0) ++ if (device_get_sysattr_value_filtered(dev, "ari_enabled", &a) < 0) + return false; + + return streq(a, "1"); +@@ -269,7 +269,7 @@ static bool is_pci_ari_enabled(sd_device *dev) { + static bool is_pci_bridge(sd_device *dev) { + const char *v, *p; + +- if (sd_device_get_sysattr_value(dev, "modalias", &v) < 0) ++ if (device_get_sysattr_value_filtered(dev, "modalias", &v) < 0) + return false; + + if (!startswith(v, "pci:")) +@@ -309,7 +309,7 @@ static int parse_hotplug_slot_from_function_id(sd_device *dev, int slots_dirfd, + if (!naming_scheme_has(NAMING_SLOT_FUNCTION_ID)) + return 0; + +- if (sd_device_get_sysattr_value(dev, "function_id", &attr) < 0) ++ if (device_get_sysattr_value_filtered(dev, "function_id", &attr) < 0) + return 0; + + r = safe_atou64(attr, &function_id); +@@ -366,7 +366,7 @@ static int dev_pci_slot(sd_device *dev, const LinkInfo *info, NetNames *names) { + func += slot * 8; + + /* kernel provided port index for multiple ports on a single PCI function */ +- if (sd_device_get_sysattr_value(dev, "dev_port", &attr) >= 0) { ++ if (device_get_sysattr_value_filtered(dev, "dev_port", &attr) >= 0) { + log_device_debug(dev, "dev_port=%s", attr); + + r = safe_atolu_full(attr, 10, &dev_port); +@@ -378,7 +378,7 @@ static int dev_pci_slot(sd_device *dev, const LinkInfo *info, NetNames *names) { + * which thus stays initialized as 0. */ + if (dev_port == 0 && + info->iftype == ARPHRD_INFINIBAND && +- sd_device_get_sysattr_value(dev, "dev_id", &attr) >= 0) { ++ device_get_sysattr_value_filtered(dev, "dev_id", &attr) >= 0) { + log_device_debug(dev, "dev_id=%s", attr); + + r = safe_atolu_full(attr, 10, &dev_port); +@@ -449,7 +449,7 @@ static int dev_pci_slot(sd_device *dev, const LinkInfo *info, NetNames *names) { + if (!path) + return -ENOMEM; + +- if (sd_device_get_sysattr_value(pci, path, &address) < 0) ++ if (device_get_sysattr_value_filtered(pci, path, &address) < 0) + continue; + + /* match slot address with device by stripping the function */ +@@ -674,7 +674,7 @@ static int dev_devicetree_onboard(sd_device *dev, NetNames *names) { + if (!alias_index) + continue; + +- if (sd_device_get_sysattr_value(aliases_dev, alias, &alias_path) < 0) ++ if (device_get_sysattr_value_filtered(aliases_dev, alias, &alias_path) < 0) + continue; + + if (!path_equal(ofnode_path, alias_path)) +@@ -693,7 +693,7 @@ static int dev_devicetree_onboard(sd_device *dev, NetNames *names) { + } + + /* ...but make sure we don't have an alias conflict */ +- if (i == 0 && sd_device_get_sysattr_value(aliases_dev, conflict, NULL) >= 0) ++ if (i == 0 && device_get_sysattr_value_filtered(aliases_dev, conflict, NULL) >= 0) + return log_device_debug_errno(dev, SYNTHETIC_ERRNO(EEXIST), + "Ethernet alias conflict: ethernet and ethernet0 both exist"); + +@@ -944,7 +944,7 @@ static int names_mac(sd_device *dev, const LinkInfo *info) { + info->hw_addr.length); + + /* check for NET_ADDR_PERM, skip random MAC addresses */ +- r = sd_device_get_sysattr_value(dev, "addr_assign_type", &s); ++ r = device_get_sysattr_value_filtered(dev, "addr_assign_type", &s); + if (r < 0) + return log_device_debug_errno(dev, r, "Failed to read addr_assign_type: %m"); + r = safe_atou(s, &i); +@@ -1080,11 +1080,11 @@ static int get_link_info(sd_device *dev, LinkInfo *info) { + if (r < 0) + return r; + +- r = device_get_sysattr_int(dev, "iflink", &info->iflink); ++ r = device_get_sysattr_int_filtered(dev, "iflink", &info->iflink); + if (r < 0) + return r; + +- r = device_get_sysattr_int(dev, "type", &info->iftype); ++ r = device_get_sysattr_int_filtered(dev, "type", &info->iftype); + if (r < 0) + return r; + +@@ -1092,12 +1092,12 @@ static int get_link_info(sd_device *dev, LinkInfo *info) { + if (r < 0 && r != -ENOENT) + return r; + +- r = sd_device_get_sysattr_value(dev, "phys_port_name", &info->phys_port_name); ++ r = device_get_sysattr_value_filtered(dev, "phys_port_name", &info->phys_port_name); + if (r >= 0) + /* Check if phys_port_name indicates virtual device representor */ + (void) sscanf(info->phys_port_name, "pf%*uvf%d", &info->vf_representor_id); + +- r = sd_device_get_sysattr_value(dev, "address", &s); ++ r = device_get_sysattr_value_filtered(dev, "address", &s); + if (r < 0 && r != -ENOENT) + return r; + if (r >= 0) { diff --git a/SOURCES/0455-man-environment-value-udev-property.patch b/SOURCES/0455-man-environment-value-udev-property.patch new file mode 100644 index 0000000..738059c --- /dev/null +++ b/SOURCES/0455-man-environment-value-udev-property.patch @@ -0,0 +1,38 @@ +From c5e8c8163c5063d7cc0a376022380f46a9d18ab0 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Wed, 20 Dec 2023 15:08:49 +0900 +Subject: [PATCH] man: environment value -> udev property + +These are not environment variables, but udev properties. + +Follow-up for 3b2e7dc5a285edbbb1bf6aed2d88b889d801613f. + +(cherry picked from commit 044149e6152db7a8bb293aac19e84b3b06566d63) + +Resolves: RHEL-1317 +--- + man/systemd.net-naming-scheme.xml | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/man/systemd.net-naming-scheme.xml b/man/systemd.net-naming-scheme.xml +index ec9f3da437..639c03262f 100644 +--- a/man/systemd.net-naming-scheme.xml ++++ b/man/systemd.net-naming-scheme.xml +@@ -601,7 +601,7 @@ + + ID_NET_NAME_ALLOW=BOOL + +- This evironment value sets a fallback policy for reading a sysfs attribute. ++ This udev property sets a fallback policy for reading a sysfs attribute. + If set to 0 udev will not read any sysfs attribute by default, unless it is + explicitly allowlisted, see below. If set to 1 udev can use any sysfs attribute + unless it is explicitly forbidden. The default value is 1. +@@ -612,7 +612,7 @@ + + ID_NET_NAME_ALLOW_sysfsattr=BOOL + +- This evironment value explicitly states if udev shall use the specified ++ This udev property explicitly states if udev shall use the specified + sysfsattr, when composing the device name. + + diff --git a/SOURCES/0456-logind-don-t-setup-idle-session-watch-for-lock-scree.patch b/SOURCES/0456-logind-don-t-setup-idle-session-watch-for-lock-scree.patch new file mode 100644 index 0000000..5f40274 --- /dev/null +++ b/SOURCES/0456-logind-don-t-setup-idle-session-watch-for-lock-scree.patch @@ -0,0 +1,50 @@ +From 58b968fc319f227fde22725f862063010c1c4138 Mon Sep 17 00:00:00 2001 +From: Michal Sekletar +Date: Tue, 12 Dec 2023 19:03:39 +0100 +Subject: [PATCH] logind: don't setup idle session watch for lock-screen and + greeter + +Reason to skip the idle session logic for these session classes is that +they are idle by default. + +(cherry picked from commit 508b4786e8592e82eb4832549f74aaa54335d14c) + +Related: RHEL-20757 +--- + man/logind.conf.xml | 9 +++++---- + src/login/logind-session.c | 2 +- + 2 files changed, 6 insertions(+), 5 deletions(-) + +diff --git a/man/logind.conf.xml b/man/logind.conf.xml +index 1a87cf6baf..55cbabaafb 100644 +--- a/man/logind.conf.xml ++++ b/man/logind.conf.xml +@@ -348,10 +348,11 @@ + StopIdleSessionSec= + + Specifies a timeout in seconds, or a time span value after which +- systemd-logind checks the idle state of all sessions. Every session that is idle for +- longer then the timeout will be stopped. Defaults to infinity +- (systemd-logind is not checking the idle state of sessions). For details about the syntax +- of time spans, see ++ systemd-logind checks the idle state of all sessions. Every session that is idle ++ for longer than the timeout will be stopped. Note that this option doesn't apply to ++ greeter or lock-screen sessions. Defaults to ++ infinity (systemd-logind is not checking the idle state ++ of sessions). For details about the syntax of time spans, see + systemd.time7. + + +diff --git a/src/login/logind-session.c b/src/login/logind-session.c +index 709a585013..68c2aa9670 100644 +--- a/src/login/logind-session.c ++++ b/src/login/logind-session.c +@@ -735,7 +735,7 @@ static int session_setup_stop_on_idle_timer(Session *s) { + + assert(s); + +- if (s->manager->stop_idle_session_usec == USEC_INFINITY) ++ if (s->manager->stop_idle_session_usec == USEC_INFINITY || IN_SET(s->class, SESSION_GREETER, SESSION_LOCK_SCREEN)) + return 0; + + r = sd_event_add_time_relative( diff --git a/SOURCES/0457-logind-don-t-make-idle-action-timer-accuracy-more-co.patch b/SOURCES/0457-logind-don-t-make-idle-action-timer-accuracy-more-co.patch new file mode 100644 index 0000000..9329ba8 --- /dev/null +++ b/SOURCES/0457-logind-don-t-make-idle-action-timer-accuracy-more-co.patch @@ -0,0 +1,33 @@ +From 51dba4b4c93298e32442c88cd0bce7715eea289d Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Wed, 29 Nov 2023 11:07:08 +0100 +Subject: [PATCH] logind: don't make idle action timer accuracy more coarse + than timeout + +If we allow the timer accuracy to grow larger then the timeout itself +things are very confusing, because people might set a 1s time-out and we +turn that into 30s. + +Hence, let's just cut off the 30s accuracy to the time-out itself, so +that we stay close to what users configured. + +(cherry picked from commit e20bfa5005ab5458837bb62cb35bc1687f25124f) + +Related: RHEL-20757 +--- + src/login/logind.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/login/logind.c b/src/login/logind.c +index 0348b19c05..70f72387c5 100644 +--- a/src/login/logind.c ++++ b/src/login/logind.c +@@ -989,7 +989,7 @@ static int manager_dispatch_idle_action(sd_event_source *s, uint64_t t, void *us + m->event, + &m->idle_action_event_source, + CLOCK_MONOTONIC, +- elapse, USEC_PER_SEC*30, ++ elapse, MIN(USEC_PER_SEC*30, m->idle_action_usec), /* accuracy of 30s, but don't have an accuracy lower than the idle action timeout */ + manager_dispatch_idle_action, m); + if (r < 0) + return log_error_errno(r, "Failed to add idle event source: %m"); diff --git a/SOURCES/0458-logind-do-TTY-idle-logic-only-for-sessions-marked-as.patch b/SOURCES/0458-logind-do-TTY-idle-logic-only-for-sessions-marked-as.patch new file mode 100644 index 0000000..2aecd64 --- /dev/null +++ b/SOURCES/0458-logind-do-TTY-idle-logic-only-for-sessions-marked-as.patch @@ -0,0 +1,58 @@ +From 468acdce5f4c6e5eaca7f348280e2491130e8d6d Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Wed, 29 Nov 2023 11:09:20 +0100 +Subject: [PATCH] logind: do TTY idle logic only for sessions marked as "tty" + +Otherwise things might be weird, because background sessions might +become "idle", wich doesn#t really make much sense. + +This shouldn't change much in 99% of the cases, but slightly corrects +behaviour as it ensures only "primary"/"foreground" sessions get the +idle logic, i.e. where a user exists that could actually make it +non-idle. + +(cherry picked from commit 20604ff219cf4027f4ee9ca9ba7c0b9e72aec448) + +Related: RHEL-20757 +--- + src/login/logind-session.c | 26 ++++++++++++++------------ + 1 file changed, 14 insertions(+), 12 deletions(-) + +diff --git a/src/login/logind-session.c b/src/login/logind-session.c +index 68c2aa9670..af5817e2b6 100644 +--- a/src/login/logind-session.c ++++ b/src/login/logind-session.c +@@ -1029,19 +1029,21 @@ int session_get_idle_hint(Session *s, dual_timestamp *t) { + return s->idle_hint; + } + +- /* For sessions with an explicitly configured tty, let's check its atime */ +- if (s->tty) { +- r = get_tty_atime(s->tty, &atime); +- if (r >= 0) +- goto found_atime; +- } ++ if (s->type == SESSION_TTY) { ++ /* For sessions with an explicitly configured tty, let's check its atime */ ++ if (s->tty) { ++ r = get_tty_atime(s->tty, &atime); ++ if (r >= 0) ++ goto found_atime; ++ } + +- /* For sessions with a leader but no explicitly configured tty, let's check the controlling tty of +- * the leader */ +- if (pid_is_valid(s->leader)) { +- r = get_process_ctty_atime(s->leader, &atime); +- if (r >= 0) +- goto found_atime; ++ /* For sessions with a leader but no explicitly configured tty, let's check the controlling tty of ++ * the leader */ ++ if (pid_is_valid(s->leader)) { ++ r = get_process_ctty_atime(s->leader, &atime); ++ if (r >= 0) ++ goto found_atime; ++ } + } + + if (t) diff --git a/SOURCES/0459-meson-Properly-install-90-uki-copy.install.patch b/SOURCES/0459-meson-Properly-install-90-uki-copy.install.patch new file mode 100644 index 0000000..40123f8 --- /dev/null +++ b/SOURCES/0459-meson-Properly-install-90-uki-copy.install.patch @@ -0,0 +1,32 @@ +From f36dfb7ec1c780bdb74a4879fcce4be63adbaa6e Mon Sep 17 00:00:00 2001 +From: Jan Janssen +Date: Fri, 27 Jan 2023 14:28:58 +0100 +Subject: [PATCH] meson: Properly install 90-uki-copy.install + +(cherry picked from commit 4c181c1a33ef4de0130a131a2b332348dda672ed) + +Resolves: RHEL-16354 +--- + src/kernel-install/meson.build | 7 +++++-- + 1 file changed, 5 insertions(+), 2 deletions(-) + +diff --git a/src/kernel-install/meson.build b/src/kernel-install/meson.build +index 68a4d43862..2ff62d5935 100644 +--- a/src/kernel-install/meson.build ++++ b/src/kernel-install/meson.build +@@ -3,10 +3,13 @@ + kernel_install_in = files('kernel-install.in') + loaderentry_install = files('90-loaderentry.install') + +-uki_copy_install = files('90-uki-copy.install') ++kernel_install_files = files( ++ '50-depmod.install', ++ '90-uki-copy.install', ++) + + if want_kernel_install +- install_data('50-depmod.install', ++ install_data(kernel_install_files, + loaderentry_install, + install_mode : 'rwxr-xr-x', + install_dir : kernelinstalldir) diff --git a/SOURCES/0460-ci-use-source-git-automation-composite-Action.patch b/SOURCES/0460-ci-use-source-git-automation-composite-Action.patch new file mode 100644 index 0000000..0c8a98e --- /dev/null +++ b/SOURCES/0460-ci-use-source-git-automation-composite-Action.patch @@ -0,0 +1,189 @@ +From bf287f49fab60f47dd2547cdc3653fed53af3b21 Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Fri, 12 Jan 2024 15:25:14 +0100 +Subject: [PATCH] ci: use source-git-automation composite Action + +This will allow us maintain the source-git automation in separate repo +and reduce the duplication of the code and noise in the systemd repo. + +rhel-only + +Related: RHEL-1086 +--- + .../source-git-automation-on-demand.yml | 61 ++-------------- + .github/workflows/source-git-automation.yml | 72 ++----------------- + 2 files changed, 12 insertions(+), 121 deletions(-) + +diff --git a/.github/workflows/source-git-automation-on-demand.yml b/.github/workflows/source-git-automation-on-demand.yml +index 3f3da959c4..90149e74bb 100644 +--- a/.github/workflows/source-git-automation-on-demand.yml ++++ b/.github/workflows/source-git-automation-on-demand.yml +@@ -1,5 +1,3 @@ +---- +- + name: Source git Automation Scheduled/On Demand + on: + schedule: +@@ -59,62 +57,17 @@ jobs: + matrix: + pr-number: ${{ inputs.pr-number == 0 && fromJSON(needs.gather-pull-requests.outputs.pr-numbers) || fromJSON(needs.gather-pull-requests.outputs.pr-numbers-manual) }} + +- permissions: write-all +- # contents: write +- # statuses: write +- # checks: write +- # pull-requests: write ++ permissions: ++ # required for merging PRs ++ contents: write ++ # required for PR comments and setting labels ++ pull-requests: write + + steps: +- - name: Repository checkout +- uses: actions/checkout@v3 +- +- - id: metadata +- name: Gather Pull Request Metadata +- uses: redhat-plumbers-in-action/gather-pull-request-metadata@v1 ++ - name: Source-git Automation ++ uses: redhat-plumbers-in-action/source-git-automation@v1 + with: + pr-number: ${{ matrix.pr-number }} +- +- - if: ${{ !cancelled() }} +- id: commit-linter +- name: Lint Commits +- uses: redhat-plumbers-in-action/advanced-commit-linter@v2 +- with: +- pr-metadata: ${{ steps.metadata.outputs.metadata }} +- token: ${{ secrets.GITHUB_TOKEN }} +- +- # Validates tracker, changes tracker status, updates PR title +- - if: ${{ !cancelled() }} +- id: tracker-validator +- name: Validate Tracker +- uses: redhat-plumbers-in-action/tracker-validator@v1 +- with: +- pr-metadata: ${{ steps.metadata.outputs.metadata }} +- component: systemd +- tracker: ${{ fromJSON(steps.commit-linter.outputs.validated-pr-metadata).validation.tracker.id }} +- tracker-type: ${{ fromJSON(steps.commit-linter.outputs.validated-pr-metadata).validation.tracker.type }} +- bugzilla-instance: https://bugzilla.redhat.com +- bugzilla-api-token: ${{ secrets.BUGZILLA_API_TOKEN }} +- jira-instance: https://issues.redhat.com +- jira-api-token: ${{ secrets.JIRA_API_TOKEN }} +- token: ${{ secrets.GITHUB_TOKEN }} +- +- - if: ${{ !cancelled() }} +- name: Pull Request Validator +- uses: redhat-plumbers-in-action/pull-request-validator@v1 +- with: +- pr-metadata: ${{ steps.metadata.outputs.metadata }} +- token: ${{ secrets.GITHUB_TOKEN }} +- +- - id: auto-merge +- name: Auto Merge +- uses: redhat-plumbers-in-action/auto-merge@v1 +- with: +- pr-metadata: ${{ steps.metadata.outputs.metadata }} +- tracker: ${{ fromJSON(steps.commit-linter.outputs.validated-pr-metadata).validation.tracker.id }} +- tracker-type: ${{ fromJSON(steps.commit-linter.outputs.validated-pr-metadata).validation.tracker.type }} +- bugzilla-instance: https://bugzilla.redhat.com + bugzilla-api-token: ${{ secrets.BUGZILLA_API_TOKEN }} +- jira-instance: https://issues.redhat.com + jira-api-token: ${{ secrets.JIRA_API_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} +diff --git a/.github/workflows/source-git-automation.yml b/.github/workflows/source-git-automation.yml +index 17135b590f..776ac5b237 100644 +--- a/.github/workflows/source-git-automation.yml ++++ b/.github/workflows/source-git-automation.yml +@@ -26,83 +26,21 @@ jobs: + with: + name: pr-metadata + +- commit-linter: ++ source-git-automation: + needs: [ download-metadata ] + runs-on: ubuntu-latest + +- outputs: +- validated-pr-metadata: ${{ steps.commit-linter.outputs.validated-pr-metadata }} +- +- permissions: +- statuses: write +- checks: write +- pull-requests: write +- +- steps: +- - id: commit-linter +- name: Lint Commits +- uses: redhat-plumbers-in-action/advanced-commit-linter@v2 +- with: +- pr-metadata: ${{ needs.download-metadata.outputs.pr-metadata }} +- token: ${{ secrets.GITHUB_TOKEN }} +- +- # Validates tracker, changes tracker status, updates PR title +- tracker-validator: +- if: ${{ !cancelled() }} +- needs: [ download-metadata, commit-linter ] +- runs-on: ubuntu-latest +- +- permissions: +- checks: write +- pull-requests: write +- +- steps: +- - name: Validate Tracker +- uses: redhat-plumbers-in-action/tracker-validator@v1 +- with: +- pr-metadata: ${{ needs.download-metadata.outputs.pr-metadata }} +- component: systemd +- tracker: ${{ fromJSON(needs.commit-linter.outputs.validated-pr-metadata).validation.tracker.id }} +- tracker-type: ${{ fromJSON(needs.commit-linter.outputs.validated-pr-metadata).validation.tracker.type }} +- bugzilla-instance: https://bugzilla.redhat.com +- bugzilla-api-token: ${{ secrets.BUGZILLA_API_TOKEN }} +- jira-instance: https://issues.redhat.com +- jira-api-token: ${{ secrets.JIRA_API_TOKEN }} +- token: ${{ secrets.GITHUB_TOKEN }} +- +- pull-request-validator: +- needs: [ download-metadata ] +- runs-on: ubuntu-latest +- +- permissions: +- checks: write +- pull-requests: write +- +- steps: +- - name: Pull Request Validator +- uses: redhat-plumbers-in-action/pull-request-validator@v1 +- with: +- pr-metadata: ${{ needs.download-metadata.outputs.pr-metadata }} +- token: ${{ secrets.GITHUB_TOKEN }} +- +- auto-merge: +- needs: [ download-metadata, commit-linter, tracker-validator, pull-request-validator ] +- runs-on: ubuntu-latest +- + permissions: ++ # required for merging PRs + contents: write +- checks: write ++ # required for PR comments and setting labels + pull-requests: write + + steps: +- - name: Auto Merge +- uses: redhat-plumbers-in-action/auto-merge@v1 ++ - name: Source-git Automation ++ uses: redhat-plumbers-in-action/source-git-automation@v1 + with: + pr-metadata: ${{ needs.download-metadata.outputs.pr-metadata }} +- tracker: ${{ fromJSON(needs.commit-linter.outputs.validated-pr-metadata).validation.tracker.id }} +- tracker-type: ${{ fromJSON(needs.commit-linter.outputs.validated-pr-metadata).validation.tracker.type }} +- bugzilla-instance: https://bugzilla.redhat.com + bugzilla-api-token: ${{ secrets.BUGZILLA_API_TOKEN }} +- jira-instance: https://issues.redhat.com + jira-api-token: ${{ secrets.JIRA_API_TOKEN }} + token: ${{ secrets.GITHUB_TOKEN }} diff --git a/SOURCES/0461-ci-increase-the-cron-interval-to-45-minutes.patch b/SOURCES/0461-ci-increase-the-cron-interval-to-45-minutes.patch new file mode 100644 index 0000000..ebc1397 --- /dev/null +++ b/SOURCES/0461-ci-increase-the-cron-interval-to-45-minutes.patch @@ -0,0 +1,29 @@ +From 5f98f309ccc71db57b93392c4f6427df620b8f53 Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Fri, 12 Jan 2024 15:27:56 +0100 +Subject: [PATCH] ci: increase the cron interval to 45 minutes + +This should help us to avoid hitting the rate limit on the GitHub API. + +rhel-only + +Related: RHEL-1086 +--- + .github/workflows/source-git-automation-on-demand.yml | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/.github/workflows/source-git-automation-on-demand.yml b/.github/workflows/source-git-automation-on-demand.yml +index 90149e74bb..b5ccb891d1 100644 +--- a/.github/workflows/source-git-automation-on-demand.yml ++++ b/.github/workflows/source-git-automation-on-demand.yml +@@ -1,8 +1,8 @@ + name: Source git Automation Scheduled/On Demand + on: + schedule: +- # Workflow runs every 15 minutes +- - cron: '*/15 * * * *' ++ # Workflow runs every 45 minutes ++ - cron: '*/45 * * * *' + workflow_dispatch: + inputs: + pr-number: diff --git a/SOURCES/0462-ci-add-all-Z-Stream-versions-to-array-of-allowed-ver.patch b/SOURCES/0462-ci-add-all-Z-Stream-versions-to-array-of-allowed-ver.patch new file mode 100644 index 0000000..5f46659 --- /dev/null +++ b/SOURCES/0462-ci-add-all-Z-Stream-versions-to-array-of-allowed-ver.patch @@ -0,0 +1,39 @@ +From 5701905d024afc00a650ff2f2461570497694edb Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Fri, 12 Jan 2024 15:32:27 +0100 +Subject: [PATCH] ci: add all Z-Stream versions to array of allowed versions + +rhel-only + +Related: RHEL-1086 +--- + .github/tracker-validator.yml | 10 ++++++++++ + 1 file changed, 10 insertions(+) + +diff --git a/.github/tracker-validator.yml b/.github/tracker-validator.yml +index 9e43e4e7d5..f88cc0a572 100644 +--- a/.github/tracker-validator.yml ++++ b/.github/tracker-validator.yml +@@ -7,12 +7,22 @@ products: + - Red Hat Enterprise Linux 9 + - CentOS Stream 9 + - rhel-9.0.0 ++ - rhel-9.0.0.z + - rhel-9.2.0 ++ - rhel-9.2.0.z + - rhel-9.3.0 ++ - rhel-9.3.0.z + - rhel-9.4.0 ++ - rhel-9.4.0.z + - rhel-9.5.0 ++ - rhel-9.5.0.z + - rhel-9.6.0 ++ - rhel-9.6.0.z + - rhel-9.7.0 ++ - rhel-9.7.0.z + - rhel-9.8.0 ++ - rhel-9.8.0.z + - rhel-9.9.0 ++ - rhel-9.9.0.z + - rhel-9.10.0 ++ - rhel-9.10.0.z diff --git a/SOURCES/0463-udev-net_id-introduce-naming-scheme-for-RHEL-9.4.patch b/SOURCES/0463-udev-net_id-introduce-naming-scheme-for-RHEL-9.4.patch new file mode 100644 index 0000000..b353eef --- /dev/null +++ b/SOURCES/0463-udev-net_id-introduce-naming-scheme-for-RHEL-9.4.patch @@ -0,0 +1,55 @@ +From 63b7060ef28895ce56bb058912e8e81bd00b8395 Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Tue, 23 Jan 2024 15:23:05 +0100 +Subject: [PATCH] udev/net_id: introduce naming scheme for RHEL-9.4 + +rhel-only + +Resolves: RHEL-22427 +--- + man/systemd.net-naming-scheme.xml | 6 ++++++ + src/shared/netif-naming-scheme.c | 1 + + src/shared/netif-naming-scheme.h | 1 + + 3 files changed, 8 insertions(+) + +diff --git a/man/systemd.net-naming-scheme.xml b/man/systemd.net-naming-scheme.xml +index 639c03262f..4f06587ec9 100644 +--- a/man/systemd.net-naming-scheme.xml ++++ b/man/systemd.net-naming-scheme.xml +@@ -582,6 +582,12 @@ + + + ++ ++ rhel-9.4 ++ ++ Same as naming scheme rhel-9.3. ++ ++ + + + Note that latest may be used to denote the latest scheme known (to this +diff --git a/src/shared/netif-naming-scheme.c b/src/shared/netif-naming-scheme.c +index e73c265371..bf27f5571b 100644 +--- a/src/shared/netif-naming-scheme.c ++++ b/src/shared/netif-naming-scheme.c +@@ -42,6 +42,7 @@ static const NamingScheme naming_schemes[] = { + { "rhel-9.1", NAMING_RHEL_9_1 }, + { "rhel-9.2", NAMING_RHEL_9_2 }, + { "rhel-9.3", NAMING_RHEL_9_3 }, ++ { "rhel-9.4", NAMING_RHEL_9_4 }, + /* … add more schemes here, as the logic to name devices is updated … */ + + EXTRA_NET_NAMING_MAP +diff --git a/src/shared/netif-naming-scheme.h b/src/shared/netif-naming-scheme.h +index 3baa7d5e72..f39c75c64e 100644 +--- a/src/shared/netif-naming-scheme.h ++++ b/src/shared/netif-naming-scheme.h +@@ -70,6 +70,7 @@ typedef enum NamingSchemeFlags { + NAMING_RHEL_9_1 = NAMING_RHEL_9_0, + NAMING_RHEL_9_2 = NAMING_RHEL_9_0, + NAMING_RHEL_9_3 = NAMING_RHEL_9_0 | NAMING_SR_IOV_R, ++ NAMING_RHEL_9_4 = NAMING_RHEL_9_3, + + EXTRA_NET_NAMING_SCHEMES + diff --git a/SOURCES/0464-basic-errno-util-add-wrappers-which-only-accept-nega.patch b/SOURCES/0464-basic-errno-util-add-wrappers-which-only-accept-nega.patch new file mode 100644 index 0000000..e5c0e66 --- /dev/null +++ b/SOURCES/0464-basic-errno-util-add-wrappers-which-only-accept-nega.patch @@ -0,0 +1,204 @@ +From 690c7cdadd1033bfb47e8de5cc9db781a6055e2a Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Wed, 9 Aug 2023 16:36:38 +0200 +Subject: [PATCH] basic/errno-util: add wrappers which only accept negative + errno + +We do 'IN_SET(r, -CONST1, -CONST2)', instead of 'IN_SET(-r, CONST1, CONST2)' +because -r is undefined if r is the minimum value (i.e. INT_MIN). But we know +that the constants are small, so their negative values are fine. + +(cherry picked from commit b0be985cdd5e51f5f16d6bf541435c225f7c0633) + +Related: RHEL-22443 +--- + src/basic/errno-util.h | 129 +++++++++++++++++++++---------------- + src/test/test-errno-util.c | 7 ++ + 2 files changed, 80 insertions(+), 56 deletions(-) + +diff --git a/src/basic/errno-util.h b/src/basic/errno-util.h +index 091f99c590..be5c04e285 100644 +--- a/src/basic/errno-util.h ++++ b/src/basic/errno-util.h +@@ -84,12 +84,21 @@ static inline int errno_or_else(int fallback) { + return -abs(fallback); + } + ++/* abs(3) says: Trying to take the absolute value of the most negative integer is not defined. */ ++#define _DEFINE_ABS_WRAPPER(name) \ ++ static inline bool ERRNO_IS_##name(int r) { \ ++ if (r == INT_MIN) \ ++ return false; \ ++ return ERRNO_IS_NEG_##name(-abs(r)); \ ++ } ++ + /* For send()/recv() or read()/write(). */ +-static inline bool ERRNO_IS_TRANSIENT(int r) { +- return IN_SET(abs(r), +- EAGAIN, +- EINTR); ++static inline bool ERRNO_IS_NEG_TRANSIENT(int r) { ++ return IN_SET(r, ++ -EAGAIN, ++ -EINTR); + } ++_DEFINE_ABS_WRAPPER(TRANSIENT); + + /* Hint #1: ENETUNREACH happens if we try to connect to "non-existing" special IP addresses, such as ::5. + * +@@ -98,79 +107,87 @@ static inline bool ERRNO_IS_TRANSIENT(int r) { + * + * Hint #3: When asynchronous connect() on TCP fails because the host never acknowledges a single packet, + * kernel tells us that with ETIMEDOUT, see tcp(7). */ +-static inline bool ERRNO_IS_DISCONNECT(int r) { +- return IN_SET(abs(r), +- ECONNABORTED, +- ECONNREFUSED, +- ECONNRESET, +- EHOSTDOWN, +- EHOSTUNREACH, +- ENETDOWN, +- ENETRESET, +- ENETUNREACH, +- ENONET, +- ENOPROTOOPT, +- ENOTCONN, +- EPIPE, +- EPROTO, +- ESHUTDOWN, +- ETIMEDOUT); ++static inline bool ERRNO_IS_NEG_DISCONNECT(int r) { ++ return IN_SET(r, ++ -ECONNABORTED, ++ -ECONNREFUSED, ++ -ECONNRESET, ++ -EHOSTDOWN, ++ -EHOSTUNREACH, ++ -ENETDOWN, ++ -ENETRESET, ++ -ENETUNREACH, ++ -ENONET, ++ -ENOPROTOOPT, ++ -ENOTCONN, ++ -EPIPE, ++ -EPROTO, ++ -ESHUTDOWN, ++ -ETIMEDOUT); + } ++_DEFINE_ABS_WRAPPER(DISCONNECT); + + /* Transient errors we might get on accept() that we should ignore. As per error handling comment in + * the accept(2) man page. */ +-static inline bool ERRNO_IS_ACCEPT_AGAIN(int r) { +- return ERRNO_IS_DISCONNECT(r) || +- ERRNO_IS_TRANSIENT(r) || +- abs(r) == EOPNOTSUPP; ++static inline bool ERRNO_IS_NEG_ACCEPT_AGAIN(int r) { ++ return ERRNO_IS_NEG_DISCONNECT(r) || ++ ERRNO_IS_NEG_TRANSIENT(r) || ++ r == -EOPNOTSUPP; + } ++_DEFINE_ABS_WRAPPER(ACCEPT_AGAIN); + + /* Resource exhaustion, could be our fault or general system trouble */ +-static inline bool ERRNO_IS_RESOURCE(int r) { +- return IN_SET(abs(r), +- EMFILE, +- ENFILE, +- ENOMEM); ++static inline bool ERRNO_IS_NEG_RESOURCE(int r) { ++ return IN_SET(r, ++ -EMFILE, ++ -ENFILE, ++ -ENOMEM); + } ++_DEFINE_ABS_WRAPPER(RESOURCE); + + /* Seven different errors for "operation/system call/ioctl/socket feature not supported" */ +-static inline bool ERRNO_IS_NOT_SUPPORTED(int r) { +- return IN_SET(abs(r), +- EOPNOTSUPP, +- ENOTTY, +- ENOSYS, +- EAFNOSUPPORT, +- EPFNOSUPPORT, +- EPROTONOSUPPORT, +- ESOCKTNOSUPPORT); ++static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(int r) { ++ return IN_SET(r, ++ -EOPNOTSUPP, ++ -ENOTTY, ++ -ENOSYS, ++ -EAFNOSUPPORT, ++ -EPFNOSUPPORT, ++ -EPROTONOSUPPORT, ++ -ESOCKTNOSUPPORT); + } ++_DEFINE_ABS_WRAPPER(NOT_SUPPORTED); + + /* Two different errors for access problems */ +-static inline bool ERRNO_IS_PRIVILEGE(int r) { +- return IN_SET(abs(r), +- EACCES, +- EPERM); ++static inline bool ERRNO_IS_NEG_PRIVILEGE(int r) { ++ return IN_SET(r, ++ -EACCES, ++ -EPERM); + } ++_DEFINE_ABS_WRAPPER(PRIVILEGE); + + /* Three different errors for "not enough disk space" */ +-static inline bool ERRNO_IS_DISK_SPACE(int r) { +- return IN_SET(abs(r), +- ENOSPC, +- EDQUOT, +- EFBIG); ++static inline bool ERRNO_IS_NEG_DISK_SPACE(int r) { ++ return IN_SET(r, ++ -ENOSPC, ++ -EDQUOT, ++ -EFBIG); + } ++_DEFINE_ABS_WRAPPER(DISK_SPACE); + + /* Three different errors for "this device does not quite exist" */ +-static inline bool ERRNO_IS_DEVICE_ABSENT(int r) { +- return IN_SET(abs(r), +- ENODEV, +- ENXIO, +- ENOENT); ++static inline bool ERRNO_IS_NEG_DEVICE_ABSENT(int r) { ++ return IN_SET(r, ++ -ENODEV, ++ -ENXIO, ++ -ENOENT); + } ++_DEFINE_ABS_WRAPPER(DEVICE_ABSENT); + + /* Quite often we want to handle cases where the backing FS doesn't support extended attributes at all and + * where it simply doesn't have the requested xattr the same way */ +-static inline bool ERRNO_IS_XATTR_ABSENT(int r) { +- return abs(r) == ENODATA || +- ERRNO_IS_NOT_SUPPORTED(r); ++static inline bool ERRNO_IS_NEG_XATTR_ABSENT(int r) { ++ return r == -ENODATA || ++ ERRNO_IS_NEG_NOT_SUPPORTED(r); + } ++_DEFINE_ABS_WRAPPER(XATTR_ABSENT); +diff --git a/src/test/test-errno-util.c b/src/test/test-errno-util.c +index f858927c92..507d53df7a 100644 +--- a/src/test/test-errno-util.c ++++ b/src/test/test-errno-util.c +@@ -47,4 +47,11 @@ TEST(STRERROR_OR_ELSE) { + log_info("STRERROR_OR_ELSE(-EPERM, \"EOF\") → %s", STRERROR_OR_EOF(-EPERM)); + } + ++TEST(ERRNO_IS_TRANSIENT) { ++ assert_se( ERRNO_IS_NEG_TRANSIENT(-EINTR)); ++ assert_se(!ERRNO_IS_NEG_TRANSIENT(EINTR)); ++ assert_se( ERRNO_IS_TRANSIENT(-EINTR)); ++ assert_se( ERRNO_IS_TRANSIENT(EINTR)); ++} ++ + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/SOURCES/0465-errno-util-allow-ERRNO_IS_-to-accept-types-wider-tha.patch b/SOURCES/0465-errno-util-allow-ERRNO_IS_-to-accept-types-wider-tha.patch new file mode 100644 index 0000000..638560f --- /dev/null +++ b/SOURCES/0465-errno-util-allow-ERRNO_IS_-to-accept-types-wider-tha.patch @@ -0,0 +1,148 @@ +From 7c5ece0b649ebea23ebb28eb3cafdb28ba49a9d0 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Wed, 9 Aug 2023 18:21:13 +0200 +Subject: [PATCH] errno-util: allow ERRNO_IS_* to accept types wider than int + +This is useful if the variable is ssize_t and we don't want to trigger a +warning or truncation. + +With gcc (gcc-13.2.1-1.fc38.x86_64), the resulting systemd binary is identical, +so I assume that the compiler is able to completely optimize away the type. + +(cherry picked from commit fe0feacb9e9641874fde459af4067d9b7e9d7462) + +Related: RHEL-22443 +--- + src/basic/errno-util.h | 27 +++++++++++++++------------ + src/test/test-errno-util.c | 13 +++++++++++++ + 2 files changed, 28 insertions(+), 12 deletions(-) + +diff --git a/src/basic/errno-util.h b/src/basic/errno-util.h +index be5c04e285..b10dd755c9 100644 +--- a/src/basic/errno-util.h ++++ b/src/basic/errno-util.h +@@ -1,6 +1,7 @@ + /* SPDX-License-Identifier: LGPL-2.1-or-later */ + #pragma once + ++#include + #include + #include + +@@ -86,14 +87,16 @@ static inline int errno_or_else(int fallback) { + + /* abs(3) says: Trying to take the absolute value of the most negative integer is not defined. */ + #define _DEFINE_ABS_WRAPPER(name) \ +- static inline bool ERRNO_IS_##name(int r) { \ +- if (r == INT_MIN) \ ++ static inline bool ERRNO_IS_##name(intmax_t r) { \ ++ if (r == INTMAX_MIN) \ + return false; \ +- return ERRNO_IS_NEG_##name(-abs(r)); \ ++ return ERRNO_IS_NEG_##name(-imaxabs(r)); \ + } + ++assert_cc(INT_MAX <= INTMAX_MAX); ++ + /* For send()/recv() or read()/write(). */ +-static inline bool ERRNO_IS_NEG_TRANSIENT(int r) { ++static inline bool ERRNO_IS_NEG_TRANSIENT(intmax_t r) { + return IN_SET(r, + -EAGAIN, + -EINTR); +@@ -107,7 +110,7 @@ _DEFINE_ABS_WRAPPER(TRANSIENT); + * + * Hint #3: When asynchronous connect() on TCP fails because the host never acknowledges a single packet, + * kernel tells us that with ETIMEDOUT, see tcp(7). */ +-static inline bool ERRNO_IS_NEG_DISCONNECT(int r) { ++static inline bool ERRNO_IS_NEG_DISCONNECT(intmax_t r) { + return IN_SET(r, + -ECONNABORTED, + -ECONNREFUSED, +@@ -129,7 +132,7 @@ _DEFINE_ABS_WRAPPER(DISCONNECT); + + /* Transient errors we might get on accept() that we should ignore. As per error handling comment in + * the accept(2) man page. */ +-static inline bool ERRNO_IS_NEG_ACCEPT_AGAIN(int r) { ++static inline bool ERRNO_IS_NEG_ACCEPT_AGAIN(intmax_t r) { + return ERRNO_IS_NEG_DISCONNECT(r) || + ERRNO_IS_NEG_TRANSIENT(r) || + r == -EOPNOTSUPP; +@@ -137,7 +140,7 @@ static inline bool ERRNO_IS_NEG_ACCEPT_AGAIN(int r) { + _DEFINE_ABS_WRAPPER(ACCEPT_AGAIN); + + /* Resource exhaustion, could be our fault or general system trouble */ +-static inline bool ERRNO_IS_NEG_RESOURCE(int r) { ++static inline bool ERRNO_IS_NEG_RESOURCE(intmax_t r) { + return IN_SET(r, + -EMFILE, + -ENFILE, +@@ -146,7 +149,7 @@ static inline bool ERRNO_IS_NEG_RESOURCE(int r) { + _DEFINE_ABS_WRAPPER(RESOURCE); + + /* Seven different errors for "operation/system call/ioctl/socket feature not supported" */ +-static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(int r) { ++static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(intmax_t r) { + return IN_SET(r, + -EOPNOTSUPP, + -ENOTTY, +@@ -159,7 +162,7 @@ static inline bool ERRNO_IS_NEG_NOT_SUPPORTED(int r) { + _DEFINE_ABS_WRAPPER(NOT_SUPPORTED); + + /* Two different errors for access problems */ +-static inline bool ERRNO_IS_NEG_PRIVILEGE(int r) { ++static inline bool ERRNO_IS_NEG_PRIVILEGE(intmax_t r) { + return IN_SET(r, + -EACCES, + -EPERM); +@@ -167,7 +170,7 @@ static inline bool ERRNO_IS_NEG_PRIVILEGE(int r) { + _DEFINE_ABS_WRAPPER(PRIVILEGE); + + /* Three different errors for "not enough disk space" */ +-static inline bool ERRNO_IS_NEG_DISK_SPACE(int r) { ++static inline bool ERRNO_IS_NEG_DISK_SPACE(intmax_t r) { + return IN_SET(r, + -ENOSPC, + -EDQUOT, +@@ -176,7 +179,7 @@ static inline bool ERRNO_IS_NEG_DISK_SPACE(int r) { + _DEFINE_ABS_WRAPPER(DISK_SPACE); + + /* Three different errors for "this device does not quite exist" */ +-static inline bool ERRNO_IS_NEG_DEVICE_ABSENT(int r) { ++static inline bool ERRNO_IS_NEG_DEVICE_ABSENT(intmax_t r) { + return IN_SET(r, + -ENODEV, + -ENXIO, +@@ -186,7 +189,7 @@ _DEFINE_ABS_WRAPPER(DEVICE_ABSENT); + + /* Quite often we want to handle cases where the backing FS doesn't support extended attributes at all and + * where it simply doesn't have the requested xattr the same way */ +-static inline bool ERRNO_IS_NEG_XATTR_ABSENT(int r) { ++static inline bool ERRNO_IS_NEG_XATTR_ABSENT(intmax_t r) { + return r == -ENODATA || + ERRNO_IS_NEG_NOT_SUPPORTED(r); + } +diff --git a/src/test/test-errno-util.c b/src/test/test-errno-util.c +index 507d53df7a..cac0d5402b 100644 +--- a/src/test/test-errno-util.c ++++ b/src/test/test-errno-util.c +@@ -52,6 +52,19 @@ TEST(ERRNO_IS_TRANSIENT) { + assert_se(!ERRNO_IS_NEG_TRANSIENT(EINTR)); + assert_se( ERRNO_IS_TRANSIENT(-EINTR)); + assert_se( ERRNO_IS_TRANSIENT(EINTR)); ++ ++ /* Test with type wider than int */ ++ ssize_t r = -EAGAIN; ++ assert_se( ERRNO_IS_NEG_TRANSIENT(r)); ++ ++ /* On 64-bit arches, now (int) r == EAGAIN */ ++ r = SSIZE_MAX - EAGAIN + 1; ++ assert_se(!ERRNO_IS_NEG_TRANSIENT(r)); ++ ++ assert_se(!ERRNO_IS_NEG_TRANSIENT(INT_MAX)); ++ assert_se(!ERRNO_IS_NEG_TRANSIENT(INT_MIN)); ++ assert_se(!ERRNO_IS_NEG_TRANSIENT(INTMAX_MAX)); ++ assert_se(!ERRNO_IS_NEG_TRANSIENT(INTMAX_MIN)); + } + + DEFINE_TEST_MAIN(LOG_INFO); diff --git a/SOURCES/0466-udev-add-new-builtin-net_driver.patch b/SOURCES/0466-udev-add-new-builtin-net_driver.patch new file mode 100644 index 0000000..156930d --- /dev/null +++ b/SOURCES/0466-udev-add-new-builtin-net_driver.patch @@ -0,0 +1,184 @@ +From f2bf171137c348f6f976276504c8e8a54e33ff78 Mon Sep 17 00:00:00 2001 +From: Lukas Nykryn +Date: Thu, 19 Oct 2023 10:38:06 +0200 +Subject: [PATCH] udev: add new builtin net_driver + +Currently the ID_NET_DRIVER is set in net_setup_link builtin. +But this is called pretty late in the udev processing chain. + +Right now in some custom rules it was workarounded by calling ethtool +binary directly, which is ugly. + +So let's split this code to a separate builtin. + +(cherry picked from commit 2b5b25f123ceb89b3ff45b2380db1c8a88b046d9) + +Resolves: RHEL-22443 +--- + rules.d/50-udev-default.rules.in | 2 ++ + src/udev/meson.build | 1 + + src/udev/net/link-config.c | 5 ++- + src/udev/net/link-config.h | 2 +- + src/udev/udev-builtin-net_driver.c | 43 ++++++++++++++++++++++++++ + src/udev/udev-builtin-net_setup_link.c | 3 -- + src/udev/udev-builtin.c | 1 + + src/udev/udev-builtin.h | 2 ++ + 8 files changed, 52 insertions(+), 7 deletions(-) + create mode 100644 src/udev/udev-builtin-net_driver.c + +diff --git a/rules.d/50-udev-default.rules.in b/rules.d/50-udev-default.rules.in +index 843bdaf9ce..f670b51987 100644 +--- a/rules.d/50-udev-default.rules.in ++++ b/rules.d/50-udev-default.rules.in +@@ -17,6 +17,8 @@ SUBSYSTEM=="rtc", KERNEL=="rtc0", SYMLINK+="rtc", OPTIONS+="link_priority=-100" + SUBSYSTEM=="usb", ENV{DEVTYPE}=="usb_device", IMPORT{builtin}="usb_id", IMPORT{builtin}="hwdb --subsystem=usb" + ENV{MODALIAS}!="", IMPORT{builtin}="hwdb --subsystem=$env{SUBSYSTEM}" + ++SUBSYSTEM=="net", IMPORT{builtin}="net_driver" ++ + ACTION!="add", GOTO="default_end" + + SUBSYSTEM=="tty", KERNEL=="ptmx", GROUP="tty", MODE="0666" +diff --git a/src/udev/meson.build b/src/udev/meson.build +index 08a1d97e81..564aa6de1b 100644 +--- a/src/udev/meson.build ++++ b/src/udev/meson.build +@@ -35,6 +35,7 @@ libudevd_core_sources = files( + 'udev-builtin-hwdb.c', + 'udev-builtin-input_id.c', + 'udev-builtin-keyboard.c', ++ 'udev-builtin-net_driver.c', + 'udev-builtin-net_id.c', + 'udev-builtin-net_setup_link.c', + 'udev-builtin-path_id.c', +diff --git a/src/udev/net/link-config.c b/src/udev/net/link-config.c +index 2d8c902fd3..80c64519ab 100644 +--- a/src/udev/net/link-config.c ++++ b/src/udev/net/link-config.c +@@ -362,7 +362,6 @@ Link *link_free(Link *link) { + + sd_device_unref(link->device); + free(link->kind); +- free(link->driver); + strv_free(link->altnames); + return mfree(link); + } +@@ -415,8 +414,8 @@ int link_new(LinkConfigContext *ctx, sd_netlink **rtnl, sd_device *device, Link + log_link_debug_errno(link, r, "Failed to get permanent hardware address, ignoring: %m"); + } + +- r = ethtool_get_driver(&ctx->ethtool_fd, link->ifname, &link->driver); +- if (r < 0) ++ r = sd_device_get_property_value(link->device, "ID_NET_DRIVER", &link->driver); ++ if (r < 0 && r != -ENOENT) + log_link_debug_errno(link, r, "Failed to get driver, ignoring: %m"); + + *ret = TAKE_PTR(link); +diff --git a/src/udev/net/link-config.h b/src/udev/net/link-config.h +index 874a391543..8343783caf 100644 +--- a/src/udev/net/link-config.h ++++ b/src/udev/net/link-config.h +@@ -34,7 +34,7 @@ typedef struct Link { + sd_device_action_t action; + + char *kind; +- char *driver; ++ const char *driver; + uint16_t iftype; + uint32_t flags; + struct hw_addr_data hw_addr; +diff --git a/src/udev/udev-builtin-net_driver.c b/src/udev/udev-builtin-net_driver.c +new file mode 100644 +index 0000000000..f1642a491d +--- /dev/null ++++ b/src/udev/udev-builtin-net_driver.c +@@ -0,0 +1,43 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++#include "alloc-util.h" ++#include "device-util.h" ++#include "errno-util.h" ++#include "ethtool-util.h" ++#include "fd-util.h" ++#include "log.h" ++#include "string-util.h" ++#include "udev-builtin.h" ++ ++static int builtin_net_driver_set_driver(UdevEvent *event, int argc, char **argv, bool test) { ++ sd_device *dev = ASSERT_PTR(ASSERT_PTR(event)->dev); ++ _cleanup_close_ int ethtool_fd = -EBADF; ++ _cleanup_free_ char *driver = NULL; ++ const char *sysname; ++ int r; ++ ++ r = sd_device_get_sysname(dev, &sysname); ++ if (r < 0) ++ return log_device_warning_errno(dev, r, "Failed to get sysname: %m"); ++ ++ r = ethtool_get_driver(ðtool_fd, sysname, &driver); ++ if (ERRNO_IS_NEG_NOT_SUPPORTED(r)) { ++ log_device_debug_errno(dev, r, "Querying driver name via ethtool API is not supported by device '%s', ignoring: %m", sysname); ++ return 0; ++ } ++ if (r == -ENODEV) { ++ log_device_debug_errno(dev, r, "Device already vanished, ignoring."); ++ return 0; ++ } ++ if (r < 0) ++ return log_device_warning_errno(dev, r, "Failed to get driver for '%s': %m", sysname); ++ ++ return udev_builtin_add_property(event->dev, test, "ID_NET_DRIVER", driver); ++} ++ ++const UdevBuiltin udev_builtin_net_driver = { ++ .name = "net_driver", ++ .cmd = builtin_net_driver_set_driver, ++ .help = "Set driver for network device", ++ .run_once = true, ++}; +diff --git a/src/udev/udev-builtin-net_setup_link.c b/src/udev/udev-builtin-net_setup_link.c +index e964bf7bf4..b0279a1814 100644 +--- a/src/udev/udev-builtin-net_setup_link.c ++++ b/src/udev/udev-builtin-net_setup_link.c +@@ -26,9 +26,6 @@ static int builtin_net_setup_link(UdevEvent *event, int argc, char **argv, bool + if (r < 0) + return log_device_warning_errno(dev, r, "Failed to get link information: %m"); + +- if (link->driver) +- udev_builtin_add_property(dev, test, "ID_NET_DRIVER", link->driver); +- + r = link_get_config(ctx, link); + if (r < 0) { + if (r == -ENOENT) { +diff --git a/src/udev/udev-builtin.c b/src/udev/udev-builtin.c +index c84db8855c..d55dc3337d 100644 +--- a/src/udev/udev-builtin.c ++++ b/src/udev/udev-builtin.c +@@ -22,6 +22,7 @@ static const UdevBuiltin *const builtins[_UDEV_BUILTIN_MAX] = { + #if HAVE_KMOD + [UDEV_BUILTIN_KMOD] = &udev_builtin_kmod, + #endif ++ [UDEV_BUILTIN_NET_DRIVER] = &udev_builtin_net_driver, + [UDEV_BUILTIN_NET_ID] = &udev_builtin_net_id, + [UDEV_BUILTIN_NET_LINK] = &udev_builtin_net_setup_link, + [UDEV_BUILTIN_PATH_ID] = &udev_builtin_path_id, +diff --git a/src/udev/udev-builtin.h b/src/udev/udev-builtin.h +index 919d51e798..c23f2d1613 100644 +--- a/src/udev/udev-builtin.h ++++ b/src/udev/udev-builtin.h +@@ -19,6 +19,7 @@ typedef enum UdevBuiltinCommand { + #if HAVE_KMOD + UDEV_BUILTIN_KMOD, + #endif ++ UDEV_BUILTIN_NET_DRIVER, + UDEV_BUILTIN_NET_ID, + UDEV_BUILTIN_NET_LINK, + UDEV_BUILTIN_PATH_ID, +@@ -63,6 +64,7 @@ extern const UdevBuiltin udev_builtin_keyboard; + #if HAVE_KMOD + extern const UdevBuiltin udev_builtin_kmod; + #endif ++extern const UdevBuiltin udev_builtin_net_driver; + extern const UdevBuiltin udev_builtin_net_id; + extern const UdevBuiltin udev_builtin_net_setup_link; + extern const UdevBuiltin udev_builtin_path_id; diff --git a/SOURCES/0467-udev-net_id-introduce-naming-scheme-for-RHEL-8.10.patch b/SOURCES/0467-udev-net_id-introduce-naming-scheme-for-RHEL-8.10.patch new file mode 100644 index 0000000..ea5366f --- /dev/null +++ b/SOURCES/0467-udev-net_id-introduce-naming-scheme-for-RHEL-8.10.patch @@ -0,0 +1,55 @@ +From 9ff108d83a19557593c1d0f1687878377e898a54 Mon Sep 17 00:00:00 2001 +From: Lukas Nykryn +Date: Wed, 24 Jan 2024 13:49:21 +0100 +Subject: [PATCH] udev/net_id: introduce naming scheme for RHEL-8.10 + +rhel-only + +Resolves: RHEL-22427 +--- + man/systemd.net-naming-scheme.xml | 6 ++++++ + src/shared/netif-naming-scheme.c | 1 + + src/shared/netif-naming-scheme.h | 1 + + 3 files changed, 8 insertions(+) + +diff --git a/man/systemd.net-naming-scheme.xml b/man/systemd.net-naming-scheme.xml +index 4f06587ec9..3bab402e98 100644 +--- a/man/systemd.net-naming-scheme.xml ++++ b/man/systemd.net-naming-scheme.xml +@@ -547,6 +547,12 @@ + Same as naming scheme rhel-8.7. + + ++ ++ rhel-8.10 ++ ++ Same as naming scheme rhel-8.7. ++ ++ + + rhel-9.0 + +diff --git a/src/shared/netif-naming-scheme.c b/src/shared/netif-naming-scheme.c +index bf27f5571b..536ed44b21 100644 +--- a/src/shared/netif-naming-scheme.c ++++ b/src/shared/netif-naming-scheme.c +@@ -38,6 +38,7 @@ static const NamingScheme naming_schemes[] = { + { "rhel-8.7", NAMING_RHEL_8_7 }, + { "rhel-8.8", NAMING_RHEL_8_8 }, + { "rhel-8.9", NAMING_RHEL_8_9 }, ++ { "rhel-8.10", NAMING_RHEL_8_10 }, + { "rhel-9.0", NAMING_RHEL_9_0 }, + { "rhel-9.1", NAMING_RHEL_9_1 }, + { "rhel-9.2", NAMING_RHEL_9_2 }, +diff --git a/src/shared/netif-naming-scheme.h b/src/shared/netif-naming-scheme.h +index f39c75c64e..5f49157aaa 100644 +--- a/src/shared/netif-naming-scheme.h ++++ b/src/shared/netif-naming-scheme.h +@@ -65,6 +65,7 @@ typedef enum NamingSchemeFlags { + NAMING_RHEL_8_7 = NAMING_RHEL_8_4 | NAMING_SLOT_FUNCTION_ID | NAMING_16BIT_INDEX, + NAMING_RHEL_8_8 = NAMING_RHEL_8_7, + NAMING_RHEL_8_9 = NAMING_RHEL_8_7, ++ NAMING_RHEL_8_10 = NAMING_RHEL_8_7, + + NAMING_RHEL_9_0 = NAMING_V250 | NAMING_BRIDGE_MULTIFUNCTION_SLOT, + NAMING_RHEL_9_1 = NAMING_RHEL_9_0, diff --git a/SOURCES/0468-test-merge-TEST-20-MAINPIDGAMES-into-TEST-07-PID1-fi.patch b/SOURCES/0468-test-merge-TEST-20-MAINPIDGAMES-into-TEST-07-PID1-fi.patch new file mode 100644 index 0000000..2dd7567 --- /dev/null +++ b/SOURCES/0468-test-merge-TEST-20-MAINPIDGAMES-into-TEST-07-PID1-fi.patch @@ -0,0 +1,28 @@ +From 02db660b590df6e281468ff078904a83c6d52f8c Mon Sep 17 00:00:00 2001 +From: Frantisek Sumsal +Date: Thu, 25 Jan 2024 12:33:29 +0100 +Subject: [PATCH] test: merge TEST-20-MAINPIDGAMES into TEST-07-PID1 (fixup) + +Forgotten snippet from the original commit, complements commit +77827462f17ba6de2c56c7e242d1468f9c112cb3. + +(cherry picked from commit 3a4b86264eef6bd51e880386388e8b3f95cbaa33) + +Related: RHEL-1086 +--- + test/units/testsuite-07.main-PID-change.sh | 2 ++ + 1 file changed, 2 insertions(+) + +diff --git a/test/units/testsuite-07.main-PID-change.sh b/test/units/testsuite-07.main-PID-change.sh +index be4631f10d..bd5a63a272 100755 +--- a/test/units/testsuite-07.main-PID-change.sh ++++ b/test/units/testsuite-07.main-PID-change.sh +@@ -151,6 +151,8 @@ systemd-run --unit=test-mainpidsh3.service \ + -p RuntimeDirectory=mainpidsh3 \ + -p PIDFile=/run/mainpidsh3/pid \ + -p DynamicUser=1 \ ++ `# Make sanitizers happy when DynamicUser=1 pulls in instrumented systemd NSS modules` \ ++ -p EnvironmentFile=-/usr/lib/systemd/systemd-asan-env \ + -p TimeoutStartSec=2s \ + /dev/shm/test-mainpid3.sh \ + && { echo 'unexpected success'; exit 1; } diff --git a/SOURCES/0469-test-use-the-default-nsec3-iterations-value.patch b/SOURCES/0469-test-use-the-default-nsec3-iterations-value.patch new file mode 100644 index 0000000..19e61dc --- /dev/null +++ b/SOURCES/0469-test-use-the-default-nsec3-iterations-value.patch @@ -0,0 +1,28 @@ +From dc476081db6f76458b9fe78a1ba70505c8fa1e4f Mon Sep 17 00:00:00 2001 +From: Frantisek Sumsal +Date: Wed, 24 Jan 2024 19:19:29 +0100 +Subject: [PATCH] test: use the default nsec3-iterations value + +In Knot 3.2 the nsec3-iterations default was changed to 0 and Knot now +issues a warning if the value is > 0. Let's just use the default value, +since it's not something that's important for our tests. + +(cherry picked from commit 0652cf8e7b08c97a52a0995eb8f0dc6bb20a4de0) + +Related: RHEL-1086 +--- + test/knot-data/knot.conf | 1 - + 1 file changed, 1 deletion(-) + +diff --git a/test/knot-data/knot.conf b/test/knot-data/knot.conf +index 6ea0cca3db..cfe478fe1c 100644 +--- a/test/knot-data/knot.conf ++++ b/test/knot-data/knot.conf +@@ -52,7 +52,6 @@ policy: + ksk-lifetime: 365d + ksk-submission: parent_zone_sbm + nsec3: on +- nsec3-iterations: 10 + propagation-delay: 1s + signing-threads: 4 + zone-max-ttl: 1s diff --git a/SOURCES/0470-test-explicitly-set-nsec3-iterations-to-0.patch b/SOURCES/0470-test-explicitly-set-nsec3-iterations-to-0.patch new file mode 100644 index 0000000..fdfcf4a --- /dev/null +++ b/SOURCES/0470-test-explicitly-set-nsec3-iterations-to-0.patch @@ -0,0 +1,40 @@ +From 101069af4b1ccee4f8c9723edef17e6213926fec Mon Sep 17 00:00:00 2001 +From: Frantisek Sumsal +Date: Tue, 30 Jan 2024 16:27:58 +0100 +Subject: [PATCH] test: explicitly set nsec3-iterations to 0 + +knot v3.2 and later does this by default. knot v3.1 still has the default set to +10, but it also introduced a warning that the default will be changed to 0 in +later versions, so it effectively complains about its own default, which then +fails the config check. Let's just set the value explicitly to zero to avoid +that. + +~# knotc --version +knotc (Knot DNS), version 3.1.6 +~# grep nsec3-iterations test/knot-data/knot.conf || echo nope +nope +~# knotc -c /build/test/knot-data/knot.conf conf-check +warning: config, policy[auto_rollover_nsec3].nsec3-iterations defaults to 10, since version 3.2 the default becomes 0 +Configuration is valid + +Follow-up to 0652cf8e7b. + +(cherry picked from commit cb3244c0dcea80ad35e5bcaf7a07bd449ac65325) + +Related: RHEL-1086 +--- + test/knot-data/knot.conf | 1 + + 1 file changed, 1 insertion(+) + +diff --git a/test/knot-data/knot.conf b/test/knot-data/knot.conf +index cfe478fe1c..b925812312 100644 +--- a/test/knot-data/knot.conf ++++ b/test/knot-data/knot.conf +@@ -51,6 +51,7 @@ policy: + ds-push: parent_zone_server + ksk-lifetime: 365d + ksk-submission: parent_zone_sbm ++ nsec3-iterations: 0 + nsec3: on + propagation-delay: 1s + signing-threads: 4 diff --git a/SOURCES/0471-core-mount-namespaces-Remove-auxiliary-bind-mounts-d.patch b/SOURCES/0471-core-mount-namespaces-Remove-auxiliary-bind-mounts-d.patch new file mode 100644 index 0000000..0a87674 --- /dev/null +++ b/SOURCES/0471-core-mount-namespaces-Remove-auxiliary-bind-mounts-d.patch @@ -0,0 +1,75 @@ +From d83713ea522032bb135bf7f442779a431840a6db Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Michal=20Koutn=C3=BD?= +Date: Wed, 18 Jan 2023 23:20:31 +0100 +Subject: [PATCH] core: mount namespaces: Remove auxiliary bind mounts + directory after unit termination + +Unit that requires its own mount namespace creates a temporary directory +to implement dynamic bind mounts (org.freedesktop.systemd1.Manager.BindMountUnit). +However, this directory is never removed and they will accumulate for +each unique unit (e.g. templated units of systemd-coredump@). + +Attach the auxiliary runtime directory existence to lifetime of other +"runtime" only per-unit directories. + +(cherry picked from commit b9f976fb45635e09cd709dbedd0afb03d4b73c05) + +Resolves: RHEL-19483 +--- + src/core/execute.c | 17 +++++++++++++++++ + src/core/execute.h | 1 + + src/core/unit.c | 1 + + 3 files changed, 19 insertions(+) + +diff --git a/src/core/execute.c b/src/core/execute.c +index b5b7de6d2a..ea36254241 100644 +--- a/src/core/execute.c ++++ b/src/core/execute.c +@@ -5478,6 +5478,23 @@ int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_p + return 0; + } + ++int exec_context_destroy_mount_ns_dir(Unit *u) { ++ _cleanup_free_ char *p = NULL; ++ ++ if (!u || !MANAGER_IS_SYSTEM(u->manager)) ++ return 0; ++ ++ p = path_join("/run/systemd/propagate/", u->id); ++ if (!p) ++ return -ENOMEM; ++ ++ /* This is only filled transiently (see mount_in_namespace()), should be empty or even non-existent*/ ++ if (rmdir(p) < 0 && errno != ENOENT) ++ log_unit_debug_errno(u, errno, "Unable to remove propagation dir '%s', ignoring: %m", p); ++ ++ return 0; ++} ++ + static void exec_command_done(ExecCommand *c) { + assert(c); + +diff --git a/src/core/execute.h b/src/core/execute.h +index a2cf22806b..4c54422073 100644 +--- a/src/core/execute.h ++++ b/src/core/execute.h +@@ -453,6 +453,7 @@ void exec_context_dump(const ExecContext *c, FILE* f, const char *prefix); + + int exec_context_destroy_runtime_directory(const ExecContext *c, const char *runtime_root); + int exec_context_destroy_credentials(const ExecContext *c, const char *runtime_root, const char *unit); ++int exec_context_destroy_mount_ns_dir(Unit *u); + + const char* exec_context_fdname(const ExecContext *c, int fd_index); + +diff --git a/src/core/unit.c b/src/core/unit.c +index 438213a47a..c9a42ee3d7 100644 +--- a/src/core/unit.c ++++ b/src/core/unit.c +@@ -5778,6 +5778,7 @@ void unit_destroy_runtime_data(Unit *u, const ExecContext *context) { + exec_context_destroy_runtime_directory(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME]); + + exec_context_destroy_credentials(context, u->manager->prefix[EXEC_DIRECTORY_RUNTIME], u->id); ++ exec_context_destroy_mount_ns_dir(u); + } + + int unit_clean(Unit *u, ExecCleanMask mask) { diff --git a/SOURCES/0472-ci-deploy-systemd-man-to-GitHub-Pages.patch b/SOURCES/0472-ci-deploy-systemd-man-to-GitHub-Pages.patch new file mode 100644 index 0000000..838fba2 --- /dev/null +++ b/SOURCES/0472-ci-deploy-systemd-man-to-GitHub-Pages.patch @@ -0,0 +1,126 @@ +From d13e882c482ecda4c726d3f48577deb01fbfc605 Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Fri, 9 Feb 2024 13:17:35 +0100 +Subject: [PATCH] ci: deploy systemd man to GitHub Pages + +rhel-only + +Related: RHEL-1086 +--- + .github/workflows/deploy-man-pages.yml | 107 +++++++++++++++++++++++++ + 1 file changed, 107 insertions(+) + create mode 100644 .github/workflows/deploy-man-pages.yml + +diff --git a/.github/workflows/deploy-man-pages.yml b/.github/workflows/deploy-man-pages.yml +new file mode 100644 +index 0000000000..08c3d6e322 +--- /dev/null ++++ b/.github/workflows/deploy-man-pages.yml +@@ -0,0 +1,107 @@ ++name: Deploy systemd man to Pages ++ ++on: ++ push: ++ branches: [ main ] ++ paths: ++ - man/* ++ - .github/workflows/deploy-man-pages.yml ++ schedule: ++ # Run every Monday at 4:00 AM UTC ++ - cron: 0 4 * * 1 ++ workflow_dispatch: ++ ++permissions: ++ contents: read ++ ++# Allow only one concurrent deployment, skipping runs queued between the run in-progress and latest queued. ++# However, do NOT cancel in-progress runs as we want to allow these production deployments to complete. ++concurrency: ++ group: pages ++ cancel-in-progress: false ++ ++jobs: ++ # Single deploy job since we're just deploying ++ deploy: ++ environment: ++ name: github-pages ++ url: ${{ steps.deployment.outputs.page_url }} ++ runs-on: ubuntu-latest ++ ++ permissions: ++ pages: write ++ id-token: write ++ ++ steps: ++ - uses: actions/checkout@v4 ++ ++ - name: Install dependencies ++ run: | ++ RELEASE="$(lsb_release -cs)" ++ sudo bash -c "echo 'deb-src http://archive.ubuntu.com/ubuntu/ $RELEASE main restricted universe multiverse' >>/etc/apt/sources.list" ++ sudo add-apt-repository -y ppa:upstream-systemd-ci/systemd-ci ++ sudo apt-get -y update ++ sudo apt-get -y build-dep systemd ++ sudo apt-get install -y \ ++ cryptsetup-bin \ ++ expect \ ++ fdisk \ ++ gettext \ ++ iputils-ping \ ++ isc-dhcp-client \ ++ itstool \ ++ kbd \ ++ libblkid-dev \ ++ libbpf-dev \ ++ libc6-dev-i386 \ ++ libcap-dev \ ++ libcurl4-gnutls-dev \ ++ libfdisk-dev \ ++ libfido2-dev \ ++ libgpg-error-dev \ ++ liblz4-dev \ ++ liblzma-dev \ ++ libmicrohttpd-dev \ ++ libmount-dev \ ++ libp11-kit-dev \ ++ libpwquality-dev \ ++ libqrencode-dev \ ++ libssl-dev \ ++ libtss2-dev \ ++ libxkbcommon-dev \ ++ libxtables-dev \ ++ libzstd-dev \ ++ meson \ ++ mold \ ++ mount \ ++ net-tools \ ++ ninja-build \ ++ perl \ ++ python3-evdev \ ++ python3-jinja2 \ ++ python3-lxml \ ++ python3-pip \ ++ python3-pyparsing \ ++ python3-setuptools \ ++ quota \ ++ strace \ ++ unifont \ ++ util-linux \ ++ zstd \ ++ ++ - name: Build HTML man pages ++ run: | ++ meson build ++ ninja -C build man/html ++ ++ - name: Setup Pages ++ uses: actions/configure-pages@v4 ++ ++ - name: Upload artifact ++ uses: actions/upload-pages-artifact@v3 ++ with: ++ path: ./build/man ++ ++ - name: Deploy to GitHub Pages ++ id: deployment ++ uses: actions/deploy-pages@v4 diff --git a/SOURCES/0473-doc-add-missing-listitem-to-systemd.net-naming-schem.patch b/SOURCES/0473-doc-add-missing-listitem-to-systemd.net-naming-schem.patch new file mode 100644 index 0000000..ae87748 --- /dev/null +++ b/SOURCES/0473-doc-add-missing-listitem-to-systemd.net-naming-schem.patch @@ -0,0 +1,81 @@ +From ce51ce80e2f0c42600d9a157db121d9aa63459e5 Mon Sep 17 00:00:00 2001 +From: Jan Macku +Date: Fri, 9 Feb 2024 14:04:02 +0100 +Subject: [PATCH] doc: add missing `` to + `systemd.net-naming-scheme.xml` + +follow-up to: dcc59dffa5116bf96618065cd60742cb660224b8 + +rhel-only + +Related: RHEL-7026 +--- + man/systemd.net-naming-scheme.xml | 16 ++++++++-------- + 1 file changed, 8 insertions(+), 8 deletions(-) + +diff --git a/man/systemd.net-naming-scheme.xml b/man/systemd.net-naming-scheme.xml +index 3bab402e98..532d6ddb15 100644 +--- a/man/systemd.net-naming-scheme.xml ++++ b/man/systemd.net-naming-scheme.xml +@@ -483,19 +483,19 @@ + + rhel-8.1 + +- Same as naming scheme rhel-8.0. ++ Same as naming scheme rhel-8.0. + + + + rhel-8.2 + +- Same as naming scheme rhel-8.0. ++ Same as naming scheme rhel-8.0. + + + + rhel-8.3 + +- Same as naming scheme rhel-8.0. ++ Same as naming scheme rhel-8.0. + + + +@@ -511,13 +511,13 @@ + + rhel-8.5 + +- Same as naming scheme rhel-8.4. ++ Same as naming scheme rhel-8.4. + + + + rhel-8.6 + +- Same as naming scheme rhel-8.4. ++ Same as naming scheme rhel-8.4. + + + +@@ -538,19 +538,19 @@ + + rhel-8.8 + +- Same as naming scheme rhel-8.7. ++ Same as naming scheme rhel-8.7. + + + + rhel-8.9 + +- Same as naming scheme rhel-8.7. ++ Same as naming scheme rhel-8.7. + + + + rhel-8.10 + +- Same as naming scheme rhel-8.7. ++ Same as naming scheme rhel-8.7. + + + diff --git a/SOURCES/0474-man-reorder-the-list-of-supported-naming-schemes.patch b/SOURCES/0474-man-reorder-the-list-of-supported-naming-schemes.patch new file mode 100644 index 0000000..3c3954c --- /dev/null +++ b/SOURCES/0474-man-reorder-the-list-of-supported-naming-schemes.patch @@ -0,0 +1,145 @@ +From 9fc363f03fb69db8bf25a6c854489d58eb11617e Mon Sep 17 00:00:00 2001 +From: Lukas Nykryn +Date: Fri, 9 Feb 2024 15:51:36 +0100 +Subject: [PATCH] man: reorder the list of supported naming schemes + +Let's put the upstream version first, followed by the RHEL9 backports +and move RHEL8 ones to separate section. + +rhel-only + +Related: RHEL-7026 +--- + man/systemd.net-naming-scheme.xml | 104 +++++++++++++----------------- + 1 file changed, 46 insertions(+), 58 deletions(-) + +diff --git a/man/systemd.net-naming-scheme.xml b/man/systemd.net-naming-scheme.xml +index 532d6ddb15..a5903c6d04 100644 +--- a/man/systemd.net-naming-scheme.xml ++++ b/man/systemd.net-naming-scheme.xml +@@ -460,26 +460,58 @@ + + + +- rhel-8.0 ++ rhel-9.0 + +- Naming was changed for virtual network interfaces created with SR-IOV and NPAR and +- for devices where the PCI network controller device does not have a slot number associated. ++ Since version v247 we no longer set ++ ID_NET_NAME_SLOT if we detect that a PCI device associated with a slot is a PCI ++ bridge as that would create naming conflict when there are more child devices on that bridge. Now, ++ this is relaxed and we will use slot information to generate the name based on it but only if ++ the PCI device has multiple functions. This is safe because distinct function number is a part of ++ the device name for multifunction devices. ++ ++ + +- SR-IOV virtual devices are named based on the name of the parent interface, with a suffix of +- vport, where port is the +- virtual device number. Previously those virtual devices were named as if completely independent. +- ++ ++ rhel-9.1 + +- The ninth and later NPAR virtual devices are named following the scheme used for the first +- eight NPAR partitions. Previously those devices were not renamed and the kernel default +- ("ethN") was used. ++ Same as naming scheme rhel-9.0. ++ + +- Names are also generated for PCI devices where the PCI network controller device does not +- have an associated slot number itself, but one of its parents does. Previously those devices were +- not renamed and the kernel default was used. ++ ++ rhel-9.2 ++ ++ Same as naming scheme rhel-9.0. ++ ++ ++ ++ rhel-9.3 ++ ++ Naming was changed for SR-IOV virtual device representors. ++ ++ The rslot suffix was added to differentiate SR-IOV ++ virtual device representors attached to a single physical device interface. ++ + + + ++ ++ rhel-9.4 ++ ++ Same as naming scheme rhel-9.3. ++ ++ ++ ++ By default rhel-9.0 is used. ++ ++ RHEL-8 schemes ++ It is also possible to pick a scheme from RHEL-8 ++ ++ ++ rhel-8.0 ++ ++ Same as naming scheme v239. ++ ++ + + rhel-8.1 + +@@ -552,52 +584,8 @@ + + Same as naming scheme rhel-8.7. + +- +- +- rhel-9.0 +- +- Since version v247 we no longer set +- ID_NET_NAME_SLOT if we detect that a PCI device associated with a slot is a PCI +- bridge as that would create naming conflict when there are more child devices on that bridge. Now, +- this is relaxed and we will use slot information to generate the name based on it but only if +- the PCI device has multiple functions. This is safe because distinct function number is a part of +- the device name for multifunction devices. +- +- +- +- +- rhel-9.1 +- +- Same as naming scheme rhel-9.0. +- +- +- +- rhel-9.2 +- +- Same as naming scheme rhel-9.0. +- +- +- +- rhel-9.3 +- +- Naming was changed for SR-IOV virtual device representors. +- +- The rslot suffix was added to differentiate SR-IOV +- virtual device representors attached to a single physical device interface. +- +- +- +- +- +- rhel-9.4 +- +- Same as naming scheme rhel-9.3. +- +- + +- +- Note that latest may be used to denote the latest scheme known (to this +- particular version of systemd). ++ + + + diff --git a/SOURCES/0475-tree-wide-fix-return-value-handling-of-base64mem.patch b/SOURCES/0475-tree-wide-fix-return-value-handling-of-base64mem.patch new file mode 100644 index 0000000..39c12a8 --- /dev/null +++ b/SOURCES/0475-tree-wide-fix-return-value-handling-of-base64mem.patch @@ -0,0 +1,440 @@ +From 841222c6f5b94ee4dfa53ce257cf021dc1ffeac3 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Wed, 11 Jan 2023 10:42:05 +0100 +Subject: [PATCH] tree-wide: fix return value handling of base64mem() + +This returns an ssize_t, not an int. On populare archs that's the +difference between 64bit and 32bit. hence, let's be more careful here, +and not silently drop half the bits on the ground by assigning the +return value to "int". + +As noticed by @malikabhi05: + +https://github.com/systemd/systemd/pull/24754#discussion_r1062903159 +(cherry picked from commit 5e476b851251dd5addd39f06ebdf05bb3efb0be7) + +Related: RHEL-16182 +--- + src/cryptenroll/cryptenroll-fido2.c | 9 ++++---- + src/cryptenroll/cryptenroll-pkcs11.c | 9 ++++---- + src/cryptenroll/cryptenroll-tpm2.c | 9 ++++---- + .../cryptsetup-token-systemd-tpm2.c | 9 ++++---- + .../cryptsetup-tokens/luks2-fido2.c | 9 ++++---- + .../cryptsetup-tokens/luks2-pkcs11.c | 9 ++++---- + src/cryptsetup/cryptsetup.c | 21 +++++++++++-------- + src/home/homectl-fido2.c | 14 +++++++------ + src/home/homectl-pkcs11.c | 7 ++++--- + src/home/homework-fido2.c | 7 ++++--- + src/home/homework-fscrypt.c | 9 ++++---- + src/partition/repart.c | 9 ++++---- + 12 files changed, 68 insertions(+), 53 deletions(-) + +diff --git a/src/cryptenroll/cryptenroll-fido2.c b/src/cryptenroll/cryptenroll-fido2.c +index 80adaefa17..87432e0d5e 100644 +--- a/src/cryptenroll/cryptenroll-fido2.c ++++ b/src/cryptenroll/cryptenroll-fido2.c +@@ -21,6 +21,7 @@ int enroll_fido2( + _cleanup_free_ char *keyslot_as_string = NULL; + size_t cid_size, salt_size, secret_size; + _cleanup_free_ void *cid = NULL; ++ ssize_t base64_encoded_size; + const char *node, *un; + int r, keyslot; + +@@ -53,9 +54,9 @@ int enroll_fido2( + return r; + + /* Before we use the secret, we base64 encode it, for compat with homed, and to make it easier to type in manually */ +- r = base64mem(secret, secret_size, &base64_encoded); +- if (r < 0) +- return log_error_errno(r, "Failed to base64 encode secret key: %m"); ++ base64_encoded_size = base64mem(secret, secret_size, &base64_encoded); ++ if (base64_encoded_size < 0) ++ return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); + + r = cryptsetup_set_minimal_pbkdf(cd); + if (r < 0) +@@ -67,7 +68,7 @@ int enroll_fido2( + volume_key, + volume_key_size, + base64_encoded, +- strlen(base64_encoded)); ++ base64_encoded_size); + if (keyslot < 0) + return log_error_errno(keyslot, "Failed to add new FIDO2 key to %s: %m", node); + +diff --git a/src/cryptenroll/cryptenroll-pkcs11.c b/src/cryptenroll/cryptenroll-pkcs11.c +index 9f07a2e01d..54b6b86242 100644 +--- a/src/cryptenroll/cryptenroll-pkcs11.c ++++ b/src/cryptenroll/cryptenroll-pkcs11.c +@@ -21,6 +21,7 @@ int enroll_pkcs11( + size_t decrypted_key_size, encrypted_key_size; + _cleanup_free_ void *encrypted_key = NULL; + _cleanup_(X509_freep) X509 *cert = NULL; ++ ssize_t base64_encoded_size; + const char *node; + EVP_PKEY *pkey; + int keyslot, r; +@@ -60,9 +61,9 @@ int enroll_pkcs11( + + /* Let's base64 encode the key to use, for compat with homed (and it's easier to type it in by + * keyboard, if that might ever end up being necessary.) */ +- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); +- if (r < 0) +- return log_error_errno(r, "Failed to base64 encode secret key: %m"); ++ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); ++ if (base64_encoded_size < 0) ++ return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); + + r = cryptsetup_set_minimal_pbkdf(cd); + if (r < 0) +@@ -74,7 +75,7 @@ int enroll_pkcs11( + volume_key, + volume_key_size, + base64_encoded, +- strlen(base64_encoded)); ++ base64_encoded_size); + if (keyslot < 0) + return log_error_errno(keyslot, "Failed to add new PKCS#11 key to %s: %m", node); + +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index 5c902908c4..96d5fc0695 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -145,6 +145,7 @@ int enroll_tpm2(struct crypt_device *cd, + uint16_t pcr_bank, primary_alg; + const char *node; + _cleanup_(erase_and_freep) char *pin_str = NULL; ++ ssize_t base64_encoded_size; + int r, keyslot; + TPM2Flags flags = 0; + +@@ -230,9 +231,9 @@ int enroll_tpm2(struct crypt_device *cd, + } + + /* let's base64 encode the key to use, for compat with homed (and it's easier to every type it in by keyboard, if that might end up being necessary. */ +- r = base64mem(secret, secret_size, &base64_encoded); +- if (r < 0) +- return log_error_errno(r, "Failed to base64 encode secret key: %m"); ++ base64_encoded_size = base64mem(secret, secret_size, &base64_encoded); ++ if (base64_encoded_size < 0) ++ return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); + + r = cryptsetup_set_minimal_pbkdf(cd); + if (r < 0) +@@ -244,7 +245,7 @@ int enroll_tpm2(struct crypt_device *cd, + volume_key, + volume_key_size, + base64_encoded, +- strlen(base64_encoded)); ++ base64_encoded_size); + if (keyslot < 0) + return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node); + +diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +index abe80720af..1eb924529c 100644 +--- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +@@ -51,6 +51,7 @@ _public_ int cryptsetup_token_open_pin( + .search_pcr_mask = UINT32_MAX + }; + uint16_t pcr_bank, primary_alg; ++ ssize_t base64_encoded_size; + TPM2Flags flags = 0; + const char *json; + int r; +@@ -116,13 +117,13 @@ _public_ int cryptsetup_token_open_pin( + return log_debug_open_error(cd, r); + + /* Before using this key as passphrase we base64 encode it, for compat with homed */ +- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); +- if (r < 0) +- return log_debug_open_error(cd, r); ++ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); ++ if (base64_encoded_size < 0) ++ return log_debug_open_error(cd, base64_encoded_size); + + /* free'd automatically by libcryptsetup */ +- *ret_password_len = strlen(base64_encoded); + *ret_password = TAKE_PTR(base64_encoded); ++ *ret_password_len = base64_encoded_size; + + return 0; + } +diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c +index a0e1ccbeeb..a1c85e600c 100644 +--- a/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c ++++ b/src/cryptsetup/cryptsetup-tokens/luks2-fido2.c +@@ -25,6 +25,7 @@ int acquire_luks2_key( + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + _cleanup_strv_free_erase_ char **pins = NULL; ++ ssize_t base64_encoded_size; + + assert(ret_keyslot_passphrase); + assert(ret_keyslot_passphrase_size); +@@ -58,12 +59,12 @@ int acquire_luks2_key( + return r; + + /* Before using this key as passphrase we base64 encode it, for compat with homed */ +- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); +- if (r < 0) +- return crypt_log_error_errno(cd, r, "Failed to base64 encode key: %m"); ++ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); ++ if (base64_encoded_size < 0) ++ return crypt_log_error_errno(cd, (int) base64_encoded_size, "Failed to base64 encode key: %m"); + + *ret_keyslot_passphrase = TAKE_PTR(base64_encoded); +- *ret_keyslot_passphrase_size = strlen(*ret_keyslot_passphrase); ++ *ret_keyslot_passphrase_size = base64_encoded_size; + + return 0; + } +diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c +index 2e0450aa5b..1ed9e2bb86 100644 +--- a/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c ++++ b/src/cryptsetup/cryptsetup-tokens/luks2-pkcs11.c +@@ -187,6 +187,7 @@ int acquire_luks2_key( + _cleanup_free_ char *pkcs11_uri = NULL; + _cleanup_free_ void *encrypted_key = NULL; + systemd_pkcs11_plugin_params *pkcs11_params = userdata; ++ ssize_t base64_encoded_size; + + assert(json); + assert(ret_password); +@@ -213,12 +214,12 @@ int acquire_luks2_key( + if (r < 0) + return r; + +- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); +- if (r < 0) +- return crypt_log_error_errno(cd, r, "Can not base64 encode key: %m"); ++ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); ++ if (base64_encoded_size < 0) ++ return crypt_log_error_errno(cd, (int) base64_encoded_size, "Can not base64 encode key: %m"); + + *ret_password = TAKE_PTR(base64_encoded); +- *ret_password_size = strlen(*ret_password); ++ *ret_password_size = base64_encoded_size; + + return 0; + } +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index 7aa36b4b03..a25fb28948 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -1176,14 +1176,15 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( + r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + else { + _cleanup_(erase_and_freep) char *base64_encoded = NULL; ++ ssize_t base64_encoded_size; + + /* Before using this key as passphrase we base64 encode it, for compat with homed */ + +- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); +- if (r < 0) ++ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); ++ if (base64_encoded_size < 0) + return log_oom(); + +- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, strlen(base64_encoded), flags); ++ r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); + } + if (r == -EPERM) { + log_error_errno(r, "Failed to activate with FIDO2 decrypted key. (Key incorrect?)"); +@@ -1323,6 +1324,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( + r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + else { + _cleanup_(erase_and_freep) char *base64_encoded = NULL; ++ ssize_t base64_encoded_size; + + /* Before using this key as passphrase we base64 encode it. Why? For compatibility + * with homed's PKCS#11 hookup: there we want to use the key we acquired through +@@ -1332,11 +1334,11 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( + * without embedded NUL here too, and that's easiest to generate from a binary blob + * via base64 encoding. */ + +- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); +- if (r < 0) ++ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); ++ if (base64_encoded_size < 0) + return log_oom(); + +- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, strlen(base64_encoded), flags); ++ r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); + } + if (r == -EPERM) { + log_error_errno(r, "Failed to activate with PKCS#11 decrypted key. (Key incorrect?)"); +@@ -1611,14 +1613,15 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( + r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + else { + _cleanup_(erase_and_freep) char *base64_encoded = NULL; ++ ssize_t base64_encoded_size; + + /* Before using this key as passphrase we base64 encode it, for compat with homed */ + +- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); +- if (r < 0) ++ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); ++ if (base64_encoded_size < 0) + return log_oom(); + +- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, strlen(base64_encoded), flags); ++ r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); + } + if (r == -EPERM) { + log_error_errno(r, "Failed to activate with TPM2 decrypted key. (Key incorrect?)"); +diff --git a/src/home/homectl-fido2.c b/src/home/homectl-fido2.c +index 61f0d081a3..3cbdf912aa 100644 +--- a/src/home/homectl-fido2.c ++++ b/src/home/homectl-fido2.c +@@ -26,14 +26,15 @@ static int add_fido2_credential_id( + _cleanup_(json_variant_unrefp) JsonVariant *w = NULL; + _cleanup_strv_free_ char **l = NULL; + _cleanup_free_ char *escaped = NULL; ++ ssize_t escaped_size; + int r; + + assert(v); + assert(cid); + +- r = base64mem(cid, cid_size, &escaped); +- if (r < 0) +- return log_error_errno(r, "Failed to base64 encode FIDO2 credential ID: %m"); ++ escaped_size = base64mem(cid, cid_size, &escaped); ++ if (escaped_size < 0) ++ return log_error_errno(escaped_size, "Failed to base64 encode FIDO2 credential ID: %m"); + + w = json_variant_ref(json_variant_by_key(*v, "fido2HmacCredential")); + if (w) { +@@ -73,13 +74,14 @@ static int add_fido2_salt( + + _cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL, *hashed = NULL; ++ ssize_t base64_encoded_size; + int r; + + /* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends + * expect a NUL terminated string, and we use a binary key */ +- r = base64mem(secret, secret_size, &base64_encoded); +- if (r < 0) +- return log_error_errno(r, "Failed to base64 encode secret key: %m"); ++ base64_encoded_size = base64mem(secret, secret_size, &base64_encoded); ++ if (base64_encoded_size < 0) ++ return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); + + r = hash_password(base64_encoded, &hashed); + if (r < 0) +diff --git a/src/home/homectl-pkcs11.c b/src/home/homectl-pkcs11.c +index 69c9d97aca..dc6ecf1665 100644 +--- a/src/home/homectl-pkcs11.c ++++ b/src/home/homectl-pkcs11.c +@@ -19,6 +19,7 @@ static int add_pkcs11_encrypted_key( + + _cleanup_(json_variant_unrefp) JsonVariant *l = NULL, *w = NULL, *e = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL, *hashed = NULL; ++ ssize_t base64_encoded_size; + int r; + + assert(v); +@@ -30,9 +31,9 @@ static int add_pkcs11_encrypted_key( + + /* Before using UNIX hashing on the supplied key we base64 encode it, since crypt_r() and friends + * expect a NUL terminated string, and we use a binary key */ +- r = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); +- if (r < 0) +- return log_error_errno(r, "Failed to base64 encode secret key: %m"); ++ base64_encoded_size = base64mem(decrypted_key, decrypted_key_size, &base64_encoded); ++ if (base64_encoded_size < 0) ++ return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); + + r = hash_password(base64_encoded, &hashed); + if (r < 0) +diff --git a/src/home/homework-fido2.c b/src/home/homework-fido2.c +index 23fda4a355..5c7cd52e1b 100644 +--- a/src/home/homework-fido2.c ++++ b/src/home/homework-fido2.c +@@ -17,6 +17,7 @@ int fido2_use_token( + _cleanup_(erase_and_freep) void *hmac = NULL; + size_t hmac_size; + Fido2EnrollFlags flags = 0; ++ ssize_t ss; + int r; + + assert(h); +@@ -65,9 +66,9 @@ int fido2_use_token( + if (r < 0) + return r; + +- r = base64mem(hmac, hmac_size, ret); +- if (r < 0) +- return log_error_errno(r, "Failed to base64 encode HMAC secret: %m"); ++ ss = base64mem(hmac, hmac_size, ret); ++ if (ss < 0) ++ return log_error_errno(ss, "Failed to base64 encode HMAC secret: %m"); + + return 0; + } +diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c +index 5106961f38..bd32393d93 100644 +--- a/src/home/homework-fscrypt.c ++++ b/src/home/homework-fscrypt.c +@@ -408,6 +408,7 @@ static int fscrypt_slot_set( + _cleanup_free_ void *encrypted = NULL; + const EVP_CIPHER *cc; + size_t encrypted_size; ++ ssize_t ss; + + r = crypto_random_bytes(salt, sizeof(salt)); + if (r < 0) +@@ -458,12 +459,12 @@ static int fscrypt_slot_set( + assert((size_t) encrypted_size_out1 + (size_t) encrypted_size_out2 < encrypted_size); + encrypted_size = (size_t) encrypted_size_out1 + (size_t) encrypted_size_out2; + +- r = base64mem(salt, sizeof(salt), &salt_base64); +- if (r < 0) ++ ss = base64mem(salt, sizeof(salt), &salt_base64); ++ if (ss < 0) + return log_oom(); + +- r = base64mem(encrypted, encrypted_size, &encrypted_base64); +- if (r < 0) ++ ss = base64mem(encrypted, encrypted_size, &encrypted_base64); ++ if (ss < 0) + return log_oom(); + + joined = strjoin(salt_base64, ":", encrypted_base64); +diff --git a/src/partition/repart.c b/src/partition/repart.c +index c4ca9840c8..8875e09389 100644 +--- a/src/partition/repart.c ++++ b/src/partition/repart.c +@@ -3022,6 +3022,7 @@ static int partition_encrypt( + _cleanup_free_ void *pubkey = NULL; + _cleanup_free_ void *blob = NULL, *hash = NULL; + size_t secret_size, blob_size, hash_size, pubkey_size = 0; ++ ssize_t base64_encoded_size; + uint16_t pcr_bank, primary_alg; + int keyslot; + +@@ -3049,9 +3050,9 @@ static int partition_encrypt( + if (r < 0) + return log_error_errno(r, "Failed to seal to TPM2: %m"); + +- r = base64mem(secret, secret_size, &base64_encoded); +- if (r < 0) +- return log_error_errno(r, "Failed to base64 encode secret key: %m"); ++ base64_encoded_size = base64mem(secret, secret_size, &base64_encoded); ++ if (base64_encoded_size < 0) ++ return log_error_errno(base64_encoded_size, "Failed to base64 encode secret key: %m"); + + r = cryptsetup_set_minimal_pbkdf(cd); + if (r < 0) +@@ -3063,7 +3064,7 @@ static int partition_encrypt( + volume_key, + volume_key_size, + base64_encoded, +- strlen(base64_encoded)); ++ base64_encoded_size); + if (keyslot < 0) + return log_error_errno(keyslot, "Failed to add new TPM2 key to %s: %m", node); + diff --git a/SOURCES/0476-Consolidate-various-TAKE_-into-TAKE_GENERIC-add-TAKE.patch b/SOURCES/0476-Consolidate-various-TAKE_-into-TAKE_GENERIC-add-TAKE.patch new file mode 100644 index 0000000..f8af45c --- /dev/null +++ b/SOURCES/0476-Consolidate-various-TAKE_-into-TAKE_GENERIC-add-TAKE.patch @@ -0,0 +1,105 @@ +From faa2d81c6c5733d2eaa28d142f88917ed9c8300b Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 6 Dec 2022 13:07:34 -0500 +Subject: [PATCH] Consolidate various TAKE_* into TAKE_GENERIC(), add + TAKE_STRUCT() + +(cherry picked from commit 40c5cc2b214fd47ebfe85786a2a220bd3e9f275a) + +Related: RHEL-16182 +--- + src/basic/fd-util.h | 8 +------- + src/basic/process-util.h | 10 ++-------- + src/fundamental/macro-fundamental.h | 17 +++++++++++------ + src/shared/keyring-util.h | 10 ++-------- + 4 files changed, 16 insertions(+), 29 deletions(-) + +diff --git a/src/basic/fd-util.h b/src/basic/fd-util.h +index 29c7d86f27..1fdb8c8fcb 100644 +--- a/src/basic/fd-util.h ++++ b/src/basic/fd-util.h +@@ -90,13 +90,7 @@ static inline int make_null_stdio(void) { + } + + /* Like TAKE_PTR() but for file descriptors, resetting them to -1 */ +-#define TAKE_FD(fd) \ +- ({ \ +- int *_fd_ = &(fd); \ +- int _ret_ = *_fd_; \ +- *_fd_ = -1; \ +- _ret_; \ +- }) ++#define TAKE_FD(fd) TAKE_GENERIC(fd, int, -1) + + /* Like free_and_replace(), but for file descriptors */ + #define close_and_replace(a, b) \ +diff --git a/src/basic/process-util.h b/src/basic/process-util.h +index ed2f73673e..e49bc47b2c 100644 +--- a/src/basic/process-util.h ++++ b/src/basic/process-util.h +@@ -176,14 +176,8 @@ int get_oom_score_adjust(int *ret); + + assert_cc(TASKS_MAX <= (unsigned long) PID_T_MAX); + +-/* Like TAKE_PTR() but for child PIDs, resetting them to 0 */ +-#define TAKE_PID(pid) \ +- ({ \ +- pid_t *_ppid_ = &(pid); \ +- pid_t _pid_ = *_ppid_; \ +- *_ppid_ = 0; \ +- _pid_; \ +- }) ++/* Like TAKE_PTR() but for pid_t, resetting them to 0 */ ++#define TAKE_PID(pid) TAKE_GENERIC(pid, pid_t, 0) + + int pidfd_get_pid(int fd, pid_t *ret); + +diff --git a/src/fundamental/macro-fundamental.h b/src/fundamental/macro-fundamental.h +index faab16ab31..e0665d9dcb 100644 +--- a/src/fundamental/macro-fundamental.h ++++ b/src/fundamental/macro-fundamental.h +@@ -299,13 +299,18 @@ + + /* Takes inspiration from Rust's Option::take() method: reads and returns a pointer, but at the same time + * resets it to NULL. See: https://doc.rust-lang.org/std/option/enum.Option.html#method.take */ +-#define TAKE_PTR(ptr) \ +- ({ \ +- typeof(ptr) *_pptr_ = &(ptr); \ +- typeof(ptr) _ptr_ = *_pptr_; \ +- *_pptr_ = NULL; \ +- _ptr_; \ ++#define TAKE_GENERIC(var, type, nullvalue) \ ++ ({ \ ++ type *_pvar_ = &(var); \ ++ type _var_ = *_pvar_; \ ++ type _nullvalue_ = nullvalue; \ ++ *_pvar_ = _nullvalue_; \ ++ _var_; \ + }) ++#define TAKE_PTR_TYPE(ptr, type) TAKE_GENERIC(ptr, type, NULL) ++#define TAKE_PTR(ptr) TAKE_PTR_TYPE(ptr, typeof(ptr)) ++#define TAKE_STRUCT_TYPE(s, type) TAKE_GENERIC(s, type, {}) ++#define TAKE_STRUCT(s) TAKE_STRUCT_TYPE(s, typeof(s)) + + /* + * STRLEN - return the length of a string literal, minus the trailing NUL byte. +diff --git a/src/shared/keyring-util.h b/src/shared/keyring-util.h +index 838e990b80..c8c53f1be1 100644 +--- a/src/shared/keyring-util.h ++++ b/src/shared/keyring-util.h +@@ -5,13 +5,7 @@ + + #include "missing_keyctl.h" + +-/* TAKE_FD but for key_serial_t instead of fds */ +-#define TAKE_KEY_SERIAL(key_serial) \ +- ({ \ +- key_serial_t *_key_serialp_ = &(key_serial); \ +- key_serial_t _key_serial_ = *_key_serialp_; \ +- *_key_serialp_ = -1; \ +- _key_serial_; \ +- }) ++/* Like TAKE_PTR() but for key_serial_t, resetting them to -1 */ ++#define TAKE_KEY_SERIAL(key_serial) TAKE_GENERIC(key_serial, key_serial_t, -1) + + int keyring_read(key_serial_t serial, void **ret, size_t *ret_size); diff --git a/SOURCES/0477-pcrphase-add-SYSTEMD_PCRPHASE_STUB_VERIFY-env-var-fo.patch b/SOURCES/0477-pcrphase-add-SYSTEMD_PCRPHASE_STUB_VERIFY-env-var-fo.patch new file mode 100644 index 0000000..5526caf --- /dev/null +++ b/SOURCES/0477-pcrphase-add-SYSTEMD_PCRPHASE_STUB_VERIFY-env-var-fo.patch @@ -0,0 +1,93 @@ +From 49586386d23c0aef7e40ab6922a484b2ed64edc6 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 14 Nov 2022 17:26:45 +0100 +Subject: [PATCH] pcrphase: add $SYSTEMD_PCRPHASE_STUB_VERIFY env var for + overriding stub check + +(cherry picked from commit 6337be0a4ec2d3cf3268b51aa705ee58cfb2b394) + +Related: RHEL-16182 +--- + docs/ENVIRONMENT.md | 7 ++++++- + src/boot/pcrphase.c | 35 ++++++++++++++++++++++++----------- + 2 files changed, 30 insertions(+), 12 deletions(-) + +diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md +index f1a4692b59..7b2dd13673 100644 +--- a/docs/ENVIRONMENT.md ++++ b/docs/ENVIRONMENT.md +@@ -471,7 +471,7 @@ SYSTEMD_HOME_DEBUG_SUFFIX=foo \ + + `systemd-journald`: + +-* `$SYSTEMD_JOURNAL_COMPACT` - Takes a boolean. If enabled, journal files are written ++* `$SYSTEMD_JOURNAL_COMPACT` – Takes a boolean. If enabled, journal files are written + in a more compact format that reduces the amount of disk space required by the + journal. Note that journal files in compact mode are limited to 4G to allow use of + 32-bit offsets. Enabled by default. +@@ -483,3 +483,8 @@ SYSTEMD_HOME_DEBUG_SUFFIX=foo \ + compression mode of existing journal files are not changed. To make the + specified algorithm takes an effect immediately, you need to explicitly run + `journalctl --rotate`. ++ ++`systemd-pcrphase`: ++ ++* `$SYSTEMD_PCRPHASE_STUB_VERIFY` – Takes a boolean. If false the requested ++ measurement is done even if no EFI stub usage was reported via EFI variables. +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index 267f66767c..f57d628e84 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -5,6 +5,7 @@ + #include + + #include "efivars.h" ++#include "env-util.h" + #include "main-func.h" + #include "openssl-util.h" + #include "parse-util.h" +@@ -174,21 +175,33 @@ static int run(int argc, char *argv[]) { + + length = strlen(word); + ++ int b = getenv_bool("SYSTEMD_PCRPHASE_STUB_VERIFY"); ++ if (b < 0 && b != -ENXIO) ++ log_warning_errno(b, "Unable to parse $SYSTEMD_PCRPHASE_STUB_VERIFY value, ignoring."); ++ + /* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */ + r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubPcrKernelImage), &pcr_string); + if (r == -ENOENT) { +- log_info("Kernel stub did not measure kernel image into PCR %u, skipping measurement.", TPM_PCR_INDEX_KERNEL_IMAGE); +- return EXIT_SUCCESS; +- } +- if (r < 0) ++ if (b != 0) { ++ log_info("Kernel stub did not measure kernel image into PCR %u, skipping measurement.", TPM_PCR_INDEX_KERNEL_IMAGE); ++ return EXIT_SUCCESS; ++ } else ++ log_notice("Kernel stub did not measure kernel image into PCR %u, but told to measure anyway, hence proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE); ++ } else if (r < 0) + return log_error_errno(r, "Failed to read StubPcrKernelImage EFI variable: %m"); +- +- /* Let's validate that the stub announced PCR 11 as we expected. */ +- r = safe_atou(pcr_string, &pcr_nr); +- if (r < 0) +- return log_error_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string); +- if (pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE) +- return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); ++ else { ++ /* Let's validate that the stub announced PCR 11 as we expected. */ ++ r = safe_atou(pcr_string, &pcr_nr); ++ if (r < 0) ++ return log_error_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string); ++ if (pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE) { ++ if (b != 0) ++ return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); ++ else ++ log_notice("Kernel stub measured kernel image into PCR %u, which is different than expected %u, but told to measure anyway, hence proceeding.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); ++ } else ++ log_debug("Kernel stub reported same PCR %u as we want to use, proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE); ++ } + + r = dlopen_tpm2(); + if (r < 0) diff --git a/SOURCES/0478-pcrphase-gracefully-exit-if-TPM2-support-is-incomple.patch b/SOURCES/0478-pcrphase-gracefully-exit-if-TPM2-support-is-incomple.patch new file mode 100644 index 0000000..925590a --- /dev/null +++ b/SOURCES/0478-pcrphase-gracefully-exit-if-TPM2-support-is-incomple.patch @@ -0,0 +1,135 @@ +From 6b14fa7bcf40ba6dd289fbf7cda835d37d243dc5 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Thu, 15 Dec 2022 18:07:20 +0100 +Subject: [PATCH] pcrphase: gracefully exit if TPM2 support is incomplete + +If everything points to the fact that TPM2 should work, but then the +driver fails to initialize we should handle this gracefully and not +cause failing services all over the place. + +Fixes: #25700 +(cherry picked from commit 0318d54539fe168822447889ac0e858a10c55f74) + +Related: RHEL-16182 +--- + man/systemd-pcrphase.service.xml | 8 ++++++++ + src/boot/pcrphase.c | 13 +++++++++++++ + units/systemd-pcrphase-initrd.service.in | 4 ++-- + units/systemd-pcrphase-sysinit.service.in | 4 ++-- + units/systemd-pcrphase.service.in | 4 ++-- + 5 files changed, 27 insertions(+), 6 deletions(-) + +diff --git a/man/systemd-pcrphase.service.xml b/man/systemd-pcrphase.service.xml +index 9eda503e4c..9b7cc80b3a 100644 +--- a/man/systemd-pcrphase.service.xml ++++ b/man/systemd-pcrphase.service.xml +@@ -131,6 +131,14 @@ + all suitable TPM2 devices currently discovered. + + ++ ++ ++ ++ If no TPM2 firmware, kernel subsystem, kernel driver or device support is found, exit ++ with exit status 0 (i.e. indicate success). If this is not specified any attempt to measure without a ++ TPM2 device will cause the invocation to fail. ++ ++ + + + +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index f57d628e84..8e91e80e22 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -13,6 +13,7 @@ + #include "tpm-pcr.h" + #include "tpm2-util.h" + ++static bool arg_graceful = false; + static char *arg_tpm2_device = NULL; + static char **arg_banks = NULL; + +@@ -34,6 +35,7 @@ static int help(int argc, char *argv[], void *userdata) { + " --version Print version\n" + " --bank=DIGEST Select TPM bank (SHA1, SHA256)\n" + " --tpm2-device=PATH Use specified TPM2 device\n" ++ " --graceful Exit gracefully if no TPM2 device is found\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, +@@ -50,6 +52,7 @@ static int parse_argv(int argc, char *argv[]) { + ARG_VERSION = 0x100, + ARG_BANK, + ARG_TPM2_DEVICE, ++ ARG_GRACEFUL, + }; + + static const struct option options[] = { +@@ -57,6 +60,7 @@ static int parse_argv(int argc, char *argv[]) { + { "version", no_argument, NULL, ARG_VERSION }, + { "bank", required_argument, NULL, ARG_BANK }, + { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, ++ { "graceful", no_argument, NULL, ARG_GRACEFUL }, + {} + }; + +@@ -104,6 +108,10 @@ static int parse_argv(int argc, char *argv[]) { + break; + } + ++ case ARG_GRACEFUL: ++ arg_graceful = true; ++ break; ++ + case '?': + return -EINVAL; + +@@ -173,6 +181,11 @@ static int run(int argc, char *argv[]) { + if (isempty(word)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing."); + ++ if (arg_graceful && tpm2_support() != TPM2_SUPPORT_FULL) { ++ log_notice("No complete TPM2 support detected, exiting gracefully."); ++ return EXIT_SUCCESS; ++ } ++ + length = strlen(word); + + int b = getenv_bool("SYSTEMD_PCRPHASE_STUB_VERIFY"); +diff --git a/units/systemd-pcrphase-initrd.service.in b/units/systemd-pcrphase-initrd.service.in +index c1ad5ef844..e437c7e1ce 100644 +--- a/units/systemd-pcrphase-initrd.service.in ++++ b/units/systemd-pcrphase-initrd.service.in +@@ -20,5 +20,5 @@ ConditionPathExists=/sys/firmware/efi/efivars/StubPcrKernelImage-4a67b082-0a4c-4 + [Service] + Type=oneshot + RemainAfterExit=yes +-ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase enter-initrd +-ExecStop={{ROOTLIBEXECDIR}}/systemd-pcrphase leave-initrd ++ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase --graceful enter-initrd ++ExecStop={{ROOTLIBEXECDIR}}/systemd-pcrphase --graceful leave-initrd +diff --git a/units/systemd-pcrphase-sysinit.service.in b/units/systemd-pcrphase-sysinit.service.in +index 6b5ba7d878..a22fbbe935 100644 +--- a/units/systemd-pcrphase-sysinit.service.in ++++ b/units/systemd-pcrphase-sysinit.service.in +@@ -21,5 +21,5 @@ ConditionPathExists=/sys/firmware/efi/efivars/StubPcrKernelImage-4a67b082-0a4c-4 + [Service] + Type=oneshot + RemainAfterExit=yes +-ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase sysinit +-ExecStop={{ROOTLIBEXECDIR}}/systemd-pcrphase final ++ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase --graceful sysinit ++ExecStop={{ROOTLIBEXECDIR}}/systemd-pcrphase --graceful final +diff --git a/units/systemd-pcrphase.service.in b/units/systemd-pcrphase.service.in +index ce469befa8..5ba437e5b1 100644 +--- a/units/systemd-pcrphase.service.in ++++ b/units/systemd-pcrphase.service.in +@@ -19,5 +19,5 @@ ConditionPathExists=/sys/firmware/efi/efivars/StubPcrKernelImage-4a67b082-0a4c-4 + [Service] + Type=oneshot + RemainAfterExit=yes +-ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase ready +-ExecStop={{ROOTLIBEXECDIR}}/systemd-pcrphase shutdown ++ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase --graceful ready ++ExecStop={{ROOTLIBEXECDIR}}/systemd-pcrphase --graceful shutdown diff --git a/SOURCES/0479-tpm2-util-split-out-code-that-derives-good-TPM2-bank.patch b/SOURCES/0479-tpm2-util-split-out-code-that-derives-good-TPM2-bank.patch new file mode 100644 index 0000000..b24f952 --- /dev/null +++ b/SOURCES/0479-tpm2-util-split-out-code-that-derives-good-TPM2-bank.patch @@ -0,0 +1,129 @@ +From 62b9997afb850843f8fa52c66c3320f0f969d400 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 11 Oct 2022 18:07:46 +0200 +Subject: [PATCH] tpm2-util: split out code that derives "good" TPM2 banks into + an strv from pcrphase and generalize it in tpm2-util.c + +That way we can reuse it later from different places. + +(cherry picked from commit e4481cc512f48d423115b10d4ae1c8e1381ff84b) + +Related: RHEL-16182 +--- + src/boot/pcrphase.c | 28 ++++++--------------------- + src/shared/tpm2-util.c | 43 ++++++++++++++++++++++++++++++++++++++++++ + src/shared/tpm2-util.h | 1 + + 3 files changed, 50 insertions(+), 22 deletions(-) + +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index 8e91e80e22..6e3a564f35 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -123,35 +123,19 @@ static int parse_argv(int argc, char *argv[]) { + } + + static int determine_banks(struct tpm2_context *c) { +- _cleanup_free_ TPMI_ALG_HASH *algs = NULL; +- int n_algs, r; ++ _cleanup_strv_free_ char **l = NULL; ++ int r; + + assert(c); + + if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */ + return 0; + +- n_algs = tpm2_get_good_pcr_banks(c->esys_context, UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &algs); +- if (n_algs <= 0) +- return n_algs; +- +- for (int i = 0; i < n_algs; i++) { +- const EVP_MD *implementation; +- const char *salg; +- +- salg = tpm2_pcr_bank_to_string(algs[i]); +- if (!salg) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure."); +- +- implementation = EVP_get_digestbyname(salg); +- if (!implementation) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unsupported PCR algorithm, can't measure."); +- +- r = strv_extend(&arg_banks, EVP_MD_name(implementation)); +- if (r < 0) +- return log_oom(); +- } ++ r = tpm2_get_good_pcr_banks_strv(c->esys_context, UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &l); ++ if (r < 0) ++ return r; + ++ strv_free_and_replace(arg_banks, l); + return 0; + } + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 8171b3e9e9..45ece9d1a6 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -730,6 +730,49 @@ int tpm2_get_good_pcr_banks( + return 0; + } + ++int tpm2_get_good_pcr_banks_strv( ++ ESYS_CONTEXT *c, ++ uint32_t pcr_mask, ++ char ***ret) { ++ ++ _cleanup_free_ TPMI_ALG_HASH *algs = NULL; ++ _cleanup_strv_free_ char **l = NULL; ++ int n_algs; ++ ++ assert(c); ++ assert(ret); ++ ++ n_algs = tpm2_get_good_pcr_banks(c, pcr_mask, &algs); ++ if (n_algs < 0) ++ return n_algs; ++ ++ for (int i = 0; i < n_algs; i++) { ++ _cleanup_free_ char *n = NULL; ++ const EVP_MD *implementation; ++ const char *salg; ++ ++ salg = tpm2_pcr_bank_to_string(algs[i]); ++ if (!salg) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure."); ++ ++ implementation = EVP_get_digestbyname(salg); ++ if (!implementation) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unsupported PCR algorithm, can't measure."); ++ ++ n = strdup(ASSERT_PTR(EVP_MD_name(implementation))); ++ if (!n) ++ return log_oom(); ++ ++ ascii_strlower(n); /* OpenSSL uses uppercase digest names, we prefer them lower case. */ ++ ++ if (strv_consume(&l, TAKE_PTR(n)) < 0) ++ return log_oom(); ++ } ++ ++ *ret = TAKE_PTR(l); ++ return 0; ++} ++ + static void hash_pin(const char *pin, size_t len, TPM2B_AUTH *auth) { + struct sha256_ctx hash; + +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index c240335ae6..6d83281be0 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -68,6 +68,7 @@ static inline void Esys_Freep(void *p) { + } + + int tpm2_get_good_pcr_banks(ESYS_CONTEXT *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks); ++int tpm2_get_good_pcr_banks_strv(ESYS_CONTEXT *c, uint32_t pcr_mask, char ***ret); + + #else + struct tpm2_context; diff --git a/SOURCES/0480-tpm2-util-split-out-code-that-extends-a-PCR-from-pcr.patch b/SOURCES/0480-tpm2-util-split-out-code-that-extends-a-PCR-from-pcr.patch new file mode 100644 index 0000000..a167402 --- /dev/null +++ b/SOURCES/0480-tpm2-util-split-out-code-that-extends-a-PCR-from-pcr.patch @@ -0,0 +1,173 @@ +From bbb9be16572c4fcc31387c3d3bfd628644db4723 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 11 Oct 2022 18:20:14 +0200 +Subject: [PATCH] tpm2-util: split out code that extends a PCR from pcrphase + +This way we can reuse it later outside of pcrphase + +(cherry picked from commit 15c591d1e2b555070f540cafb1b3d1e564e3410a) + +Related: RHEL-16182 +--- + src/boot/pcrphase.c | 42 ++------------------------ + src/shared/tpm2-util.c | 67 ++++++++++++++++++++++++++++++++++++++++++ + src/shared/tpm2-util.h | 2 ++ + 3 files changed, 72 insertions(+), 39 deletions(-) + +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index 6e3a564f35..62bdf0ad29 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -145,7 +145,6 @@ static int run(int argc, char *argv[]) { + const char *word; + unsigned pcr_nr; + size_t length; +- TSS2_RC rc; + int r; + + log_setup(); +@@ -214,50 +213,15 @@ static int run(int argc, char *argv[]) { + if (strv_isempty(arg_banks)) /* Still none? */ + return log_error_errno(SYNTHETIC_ERRNO(ENOENT), "Found a TPM2 without enabled PCR banks. Can't operate."); + +- TPML_DIGEST_VALUES values = {}; +- STRV_FOREACH(bank, arg_banks) { +- const EVP_MD *implementation; +- int id; +- +- assert_se(implementation = EVP_get_digestbyname(*bank)); +- +- if (values.count >= ELEMENTSOF(values.digests)) +- return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many banks selected."); +- +- if ((size_t) EVP_MD_size(implementation) > sizeof(values.digests[values.count].digest)) +- return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2."); +- +- id = tpm2_pcr_bank_from_string(EVP_MD_name(implementation)); +- if (id < 0) +- return log_error_errno(id, "Can't map hash name to TPM2."); +- +- values.digests[values.count].hashAlg = id; +- +- if (EVP_Digest(word, length, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word."); +- +- values.count++; +- } +- + joined = strv_join(arg_banks, ", "); + if (!joined) + return log_oom(); + + log_debug("Measuring '%s' into PCR index %u, banks %s.", word, TPM_PCR_INDEX_KERNEL_IMAGE, joined); + +- rc = sym_Esys_PCR_Extend( +- c.esys_context, +- ESYS_TR_PCR0 + TPM_PCR_INDEX_KERNEL_IMAGE, /* → PCR 11 */ +- ESYS_TR_PASSWORD, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &values); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno( +- SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to measure '%s': %s", +- word, +- sym_Tss2_RC_Decode(rc)); ++ r = tpm2_extend_bytes(c.esys_context, arg_banks, TPM_PCR_INDEX_KERNEL_IMAGE, word, length); /* → PCR 11 */ ++ if (r < 0) ++ return r; + + log_struct(LOG_INFO, + "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR, +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 45ece9d1a6..336c681c71 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1907,6 +1907,73 @@ int tpm2_find_device_auto( + #endif + } + ++#if HAVE_TPM2 ++int tpm2_extend_bytes( ++ ESYS_CONTEXT *c, ++ char **banks, ++ unsigned pcr_index, ++ const void *data, ++ size_t sz) { ++ ++#if HAVE_OPENSSL ++ TPML_DIGEST_VALUES values = {}; ++ TSS2_RC rc; ++ ++ assert(c); ++ assert(data || sz == 0); ++ ++ if (pcr_index >= TPM2_PCRS_MAX) ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Can't measure into unsupported PCR %u, refusing.", pcr_index); ++ ++ if (strv_isempty(banks)) ++ return 0; ++ ++ STRV_FOREACH(bank, banks) { ++ const EVP_MD *implementation; ++ int id; ++ ++ assert_se(implementation = EVP_get_digestbyname(*bank)); ++ ++ if (values.count >= ELEMENTSOF(values.digests)) ++ return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many banks selected."); ++ ++ if ((size_t) EVP_MD_size(implementation) > sizeof(values.digests[values.count].digest)) ++ return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2."); ++ ++ id = tpm2_pcr_bank_from_string(EVP_MD_name(implementation)); ++ if (id < 0) ++ return log_error_errno(id, "Can't map hash name to TPM2."); ++ ++ values.digests[values.count].hashAlg = id; ++ ++ if (EVP_Digest(data, sz, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word."); ++ ++ values.count++; ++ } ++ ++ rc = sym_Esys_PCR_Extend( ++ c, ++ ESYS_TR_PCR0 + pcr_index, ++ ESYS_TR_PASSWORD, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ &values); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno( ++ SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to measure into PCR %u: %s", ++ pcr_index, ++ sym_Tss2_RC_Decode(rc)); ++ ++ return 0; ++#else ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "OpenSSL not supported on this build."); ++#endif ++} ++#endif ++ + int tpm2_parse_pcrs(const char *s, uint32_t *ret) { + const char *p = ASSERT_PTR(s); + uint32_t mask = 0; +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 6d83281be0..4cab52a949 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -70,6 +70,8 @@ static inline void Esys_Freep(void *p) { + int tpm2_get_good_pcr_banks(ESYS_CONTEXT *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks); + int tpm2_get_good_pcr_banks_strv(ESYS_CONTEXT *c, uint32_t pcr_mask, char ***ret); + ++int tpm2_extend_bytes(ESYS_CONTEXT *c, char **banks, unsigned pcr_index, const void *data, size_t sz); ++ + #else + struct tpm2_context; + #endif diff --git a/SOURCES/0481-tpm2-util-optionally-do-HMAC-in-tpm2_extend_bytes-in.patch b/SOURCES/0481-tpm2-util-optionally-do-HMAC-in-tpm2_extend_bytes-in.patch new file mode 100644 index 0000000..92e5621 --- /dev/null +++ b/SOURCES/0481-tpm2-util-optionally-do-HMAC-in-tpm2_extend_bytes-in.patch @@ -0,0 +1,100 @@ +From 8bc4975bcffdefd46b1fd95ccf4edf7287d2c3d3 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 14 Oct 2022 14:38:35 +0200 +Subject: [PATCH] tpm2-util: optionally do HMAC in tpm2_extend_bytes() in case + we process sensitive data + +When measuring data into a PCR we are supposed to hash the data on the +CPU and then pass the hash value over the wire to the TPM2. That's all +good as long as the data we intend to measure is not sensitive. + +Let's be extra careful though if we want to measure sensitive data, for +example the root file system volume key. Instead of just hashing that +and passing it over the wire to the TPM2, let's do a HMAC signature +instead. It's also a hash operation, but should protect our secret +reasonably well and not leak direct information about it to wiretappers. + +(cherry picked from commit 9885c8745d313588350325e8e2110887bf78c442) + +Related: RHEL-16182 +--- + src/boot/pcrphase.c | 2 +- + src/shared/tpm2-util.c | 25 +++++++++++++++++++++---- + src/shared/tpm2-util.h | 2 +- + 3 files changed, 23 insertions(+), 6 deletions(-) + +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index 62bdf0ad29..1f3dc4ab3a 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -219,7 +219,7 @@ static int run(int argc, char *argv[]) { + + log_debug("Measuring '%s' into PCR index %u, banks %s.", word, TPM_PCR_INDEX_KERNEL_IMAGE, joined); + +- r = tpm2_extend_bytes(c.esys_context, arg_banks, TPM_PCR_INDEX_KERNEL_IMAGE, word, length); /* → PCR 11 */ ++ r = tpm2_extend_bytes(c.esys_context, arg_banks, TPM_PCR_INDEX_KERNEL_IMAGE, word, length, NULL, 0); /* → PCR 11 */ + if (r < 0) + return r; + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 336c681c71..aca7f22e54 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1913,14 +1913,22 @@ int tpm2_extend_bytes( + char **banks, + unsigned pcr_index, + const void *data, +- size_t sz) { ++ size_t data_size, ++ const void *secret, ++ size_t secret_size) { + + #if HAVE_OPENSSL + TPML_DIGEST_VALUES values = {}; + TSS2_RC rc; + + assert(c); +- assert(data || sz == 0); ++ assert(data || data_size == 0); ++ assert(secret || secret_size == 0); ++ ++ if (data_size == SIZE_MAX) ++ data_size = strlen(data); ++ if (secret_size == SIZE_MAX) ++ secret_size = strlen(secret); + + if (pcr_index >= TPM2_PCRS_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Can't measure into unsupported PCR %u, refusing.", pcr_index); +@@ -1946,8 +1954,17 @@ int tpm2_extend_bytes( + + values.digests[values.count].hashAlg = id; + +- if (EVP_Digest(data, sz, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash word."); ++ /* So here's a twist: sometimes we want to measure secrets (e.g. root file system volume ++ * key), but we'd rather not leak a literal hash of the secret to the TPM (given that the ++ * wire is unprotected, and some other subsystem might use the simple, literal hash of the ++ * secret for other purposes, maybe because it needs a shorter secret derived from it for ++ * some unrelated purpose, who knows). Hence we instead measure an HMAC signature of a ++ * private non-secret string instead. */ ++ if (secret_size > 0) { ++ if (!HMAC(implementation, secret, secret_size, data, data_size, (unsigned char*) &values.digests[values.count].digest, NULL)) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure."); ++ } else if (EVP_Digest(data, data_size, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data to measure."); + + values.count++; + } +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 4cab52a949..96e6c31b0a 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -70,7 +70,7 @@ static inline void Esys_Freep(void *p) { + int tpm2_get_good_pcr_banks(ESYS_CONTEXT *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks); + int tpm2_get_good_pcr_banks_strv(ESYS_CONTEXT *c, uint32_t pcr_mask, char ***ret); + +-int tpm2_extend_bytes(ESYS_CONTEXT *c, char **banks, unsigned pcr_index, const void *data, size_t sz); ++int tpm2_extend_bytes(ESYS_CONTEXT *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size); + + #else + struct tpm2_context; diff --git a/SOURCES/0482-cryptsetup-add-tpm2-measure-pcr-and-tpm2-measure-ban.patch b/SOURCES/0482-cryptsetup-add-tpm2-measure-pcr-and-tpm2-measure-ban.patch new file mode 100644 index 0000000..7c9e629 --- /dev/null +++ b/SOURCES/0482-cryptsetup-add-tpm2-measure-pcr-and-tpm2-measure-ban.patch @@ -0,0 +1,407 @@ +From 46659ff4eac28b7a87658668894058bd63c28e81 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Wed, 12 Oct 2022 09:56:32 +0200 +Subject: [PATCH] cryptsetup: add tpm2-measure-pcr= and tpm2-measure-bank= + crypttab options + +These options allow measuring the volume key used for unlocking the +volume to a TPM2 PCR. This is ideally used for the volume key of the +root file system and can then be used to bind other resources to the +root file system volume in a secure way. + +See: #24503 +(cherry picked from commit 94c0c85e302d00923dc5bbf9d1b937875f1d0c66) + +Related: RHEL-16182 +--- + meson.build | 3 +- + src/cryptsetup/cryptsetup.c | 226 +++++++++++++++++++++++++++++++++--- + src/fundamental/tpm-pcr.h | 3 + + 3 files changed, 217 insertions(+), 15 deletions(-) + +diff --git a/meson.build b/meson.build +index e09c426a72..fe7b47eef5 100644 +--- a/meson.build ++++ b/meson.build +@@ -2837,7 +2837,8 @@ if conf.get('HAVE_LIBCRYPTSETUP') == 1 + include_directories : includes, + link_with : [libshared], + dependencies : [libcryptsetup, +- libp11kit], ++ libp11kit, ++ libopenssl], + install_rpath : rootpkglibdir, + install : true, + install_dir : rootlibexecdir) +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index a25fb28948..20862e926d 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -8,6 +8,7 @@ + #include + + #include "sd-device.h" ++#include "sd-messages.h" + + #include "alloc-util.h" + #include "ask-password-api.h" +@@ -38,6 +39,7 @@ + #include "random-util.h" + #include "string-table.h" + #include "strv.h" ++#include "tpm-pcr.h" + #include "tpm2-util.h" + + /* internal helper */ +@@ -89,13 +91,15 @@ static bool arg_fido2_device_auto = false; + static void *arg_fido2_cid = NULL; + static size_t arg_fido2_cid_size = 0; + static char *arg_fido2_rp_id = NULL; +-static char *arg_tpm2_device = NULL; ++static char *arg_tpm2_device = NULL; /* These and the following fields are about locking an encypted volume to the local TPM */ + static bool arg_tpm2_device_auto = false; + static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; + static char *arg_tpm2_signature = NULL; + static bool arg_tpm2_pin = false; + static bool arg_headless = false; + static usec_t arg_token_timeout_usec = 30*USEC_PER_SEC; ++static unsigned arg_tpm2_measure_pcr = UINT_MAX; /* This and the following field is about measuring the unlocked volume key to the local TPM */ ++static char **arg_tpm2_measure_banks = NULL; + + STATIC_DESTRUCTOR_REGISTER(arg_cipher, freep); + STATIC_DESTRUCTOR_REGISTER(arg_hash, freep); +@@ -107,6 +111,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_fido2_cid, freep); + STATIC_DESTRUCTOR_REGISTER(arg_fido2_rp_id, freep); + STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); + STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep); ++STATIC_DESTRUCTOR_REGISTER(arg_tpm2_measure_banks, strv_freep); + + static const char* const passphrase_type_table[_PASSPHRASE_TYPE_MAX] = { + [PASSPHRASE_REGULAR] = "passphrase", +@@ -420,6 +425,48 @@ static int parse_one_option(const char *option) { + + arg_tpm2_pin = r; + ++ } else if ((val = startswith(option, "tpm2-measure-pcr="))) { ++ unsigned pcr; ++ ++ r = safe_atou(val, &pcr); ++ if (r < 0) { ++ r = parse_boolean(val); ++ if (r < 0) { ++ log_error_errno(r, "Failed to parse %s, ignoring: %m", option); ++ return 0; ++ } ++ ++ pcr = r ? TPM_PCR_INDEX_VOLUME_KEY : UINT_MAX; ++ } else if (pcr >= TPM2_PCRS_MAX) { ++ log_error("Selected TPM index for measurement %u outside of allowed range 0…%u, ignoring.", pcr, TPM2_PCRS_MAX-1); ++ return 0; ++ } ++ ++ arg_tpm2_measure_pcr = pcr; ++ ++ } else if ((val = startswith(option, "tpm2-measure-bank="))) { ++ ++#if HAVE_OPENSSL ++ _cleanup_strv_free_ char **l = NULL; ++ ++ l = strv_split(optarg, ":"); ++ if (!l) ++ return log_oom(); ++ ++ STRV_FOREACH(i, l) { ++ const EVP_MD *implementation; ++ ++ implementation = EVP_get_digestbyname(*i); ++ if (!implementation) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown bank '%s', refusing.", val); ++ ++ if (strv_extend(&arg_tpm2_measure_banks, EVP_MD_name(implementation)) < 0) ++ return log_oom(); ++ } ++#else ++ log_error("Build lacks OpenSSL support, cannot measure to PCR banks, ignoring: %s", option); ++#endif ++ + } else if ((val = startswith(option, "try-empty-password="))) { + + r = parse_boolean(val); +@@ -762,6 +809,149 @@ static int get_password( + return 0; + } + ++static int measure_volume_key( ++ struct crypt_device *cd, ++ const char *name, ++ const void *volume_key, ++ size_t volume_key_size) { ++ ++ int r; ++ ++ assert(cd); ++ assert(name); ++ assert(volume_key); ++ assert(volume_key_size > 0); ++ ++ if (arg_tpm2_measure_pcr == UINT_MAX) { ++ log_debug("Not measuring volume key, deactivated."); ++ return 0; ++ } ++ ++#if HAVE_TPM2 ++ r = dlopen_tpm2(); ++ if (r < 0) ++ return log_error_errno(r, "Failed to load TPM2 libraries: %m"); ++ ++ _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; ++ r = tpm2_context_init(arg_tpm2_device, &c); ++ if (r < 0) ++ return r; ++ ++ _cleanup_strv_free_ char **l = NULL; ++ if (strv_isempty(arg_tpm2_measure_banks)) { ++ r = tpm2_get_good_pcr_banks_strv(c.esys_context, UINT32_C(1) << arg_tpm2_measure_pcr, &l); ++ if (r < 0) ++ return r; ++ } ++ ++ _cleanup_free_ char *joined = strv_join(l ?: arg_tpm2_measure_banks, ", "); ++ if (!joined) ++ return log_oom(); ++ ++ /* Note: we don't directly measure the volume key, it might be a security problem to send an ++ * unprotected direct hash of the secret volume key over the wire to the TPM. Hence let's instead ++ * send a HMAC signature instead. */ ++ ++ _cleanup_free_ char *escaped = NULL; ++ escaped = xescape(name, ":"); /* avoid ambiguity around ":" once we join things below */ ++ if (!escaped) ++ return log_oom(); ++ ++ _cleanup_free_ char *s = NULL; ++ s = strjoin("cryptsetup:", escaped, ":", strempty(crypt_get_uuid(cd))); ++ if (!s) ++ return log_oom(); ++ ++ r = tpm2_extend_bytes(c.esys_context, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size); ++ if (r < 0) ++ return r; ++ ++ log_struct(LOG_INFO, ++ "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR, ++ LOG_MESSAGE("Successfully extended PCR index %u with '%s' and volume key (banks %s).", arg_tpm2_measure_pcr, s, joined), ++ "MEASURING=%s", s, ++ "PCR=%u", arg_tpm2_measure_pcr, ++ "BANKS=%s", joined); ++ ++ return 0; ++#else ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support disabled, not measuring."); ++#endif ++} ++ ++static int measured_crypt_activate_by_volume_key( ++ struct crypt_device *cd, ++ const char *name, ++ const void *volume_key, ++ size_t volume_key_size, ++ uint32_t flags) { ++ ++ int r; ++ ++ assert(cd); ++ assert(name); ++ ++ /* A wrapper around crypt_activate_by_volume_key() which also measures to a PCR if that's requested. */ ++ ++ r = crypt_activate_by_volume_key(cd, name, volume_key, volume_key_size, flags); ++ if (r < 0) ++ return r; ++ ++ if (volume_key_size == 0) { ++ log_debug("Not measuring volume key, none specified."); ++ return r; ++ } ++ ++ (void) measure_volume_key(cd, name, volume_key, volume_key_size); /* OK if fails */ ++ return r; ++} ++ ++static int measured_crypt_activate_by_passphrase( ++ struct crypt_device *cd, ++ const char *name, ++ int keyslot, ++ const char *passphrase, ++ size_t passphrase_size, ++ uint32_t flags) { ++ ++ _cleanup_(erase_and_freep) void *vk = NULL; ++ size_t vks; ++ int r; ++ ++ assert(cd); ++ ++ /* A wrapper around crypt_activate_by_passphrase() which also measures to a PCR if that's ++ * requested. Note that we need the volume key for the measurement, and ++ * crypt_activate_by_passphrase() doesn't give us access to this. Hence, we operate indirectly, and ++ * retrieve the volume key first, and then activate through that. */ ++ ++ if (arg_tpm2_measure_pcr == UINT_MAX) { ++ log_debug("Not measuring volume key, deactivated."); ++ goto shortcut; ++ } ++ ++ r = crypt_get_volume_key_size(cd); ++ if (r < 0) ++ return r; ++ if (r == 0) { ++ log_debug("Not measuring volume key, none defined."); ++ goto shortcut; ++ } ++ ++ vk = malloc(vks = r); ++ if (!vk) ++ return -ENOMEM; ++ ++ r = crypt_volume_key_get(cd, keyslot, vk, &vks, passphrase, passphrase_size); ++ if (r < 0) ++ return r; ++ ++ return measured_crypt_activate_by_volume_key(cd, name, vk, vks, flags); ++ ++shortcut: ++ return crypt_activate_by_passphrase(cd, name, keyslot, passphrase, passphrase_size, flags); ++} ++ + static int attach_tcrypt( + struct crypt_device *cd, + const char *name, +@@ -830,7 +1020,7 @@ static int attach_tcrypt( + return log_error_errno(r, "Failed to load tcrypt superblock on device %s: %m", crypt_get_device_name(cd)); + } + +- r = crypt_activate_by_volume_key(cd, name, NULL, 0, flags); ++ r = measured_crypt_activate_by_volume_key(cd, name, NULL, 0, flags); + if (r < 0) + return log_error_errno(r, "Failed to activate tcrypt device %s: %m", crypt_get_device_name(cd)); + +@@ -928,6 +1118,14 @@ static int run_security_device_monitor( + } + + static bool libcryptsetup_plugins_support(void) { ++ ++#if HAVE_TPM2 ++ /* Currently, there's no way for us to query the volume key when plugins are used. Hence don't use ++ * plugins, if measurement has been requested. */ ++ if (arg_tpm2_measure_pcr != UINT_MAX) ++ return false; ++#endif ++ + #if HAVE_LIBCRYPTSETUP_PLUGINS + int r; + +@@ -1173,7 +1371,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( + } + + if (pass_volume_key) +- r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); ++ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + else { + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + ssize_t base64_encoded_size; +@@ -1184,7 +1382,7 @@ static int attach_luks_or_plain_or_bitlk_by_fido2( + if (base64_encoded_size < 0) + return log_oom(); + +- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); ++ r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); + } + if (r == -EPERM) { + log_error_errno(r, "Failed to activate with FIDO2 decrypted key. (Key incorrect?)"); +@@ -1321,7 +1519,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( + assert(decrypted_key); + + if (pass_volume_key) +- r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); ++ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + else { + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + ssize_t base64_encoded_size; +@@ -1338,7 +1536,7 @@ static int attach_luks_or_plain_or_bitlk_by_pkcs11( + if (base64_encoded_size < 0) + return log_oom(); + +- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); ++ r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); + } + if (r == -EPERM) { + log_error_errno(r, "Failed to activate with PKCS#11 decrypted key. (Key incorrect?)"); +@@ -1610,7 +1808,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( + assert(decrypted_key); + + if (pass_volume_key) +- r = crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); ++ r = measured_crypt_activate_by_volume_key(cd, name, decrypted_key, decrypted_key_size, flags); + else { + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + ssize_t base64_encoded_size; +@@ -1621,7 +1819,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( + if (base64_encoded_size < 0) + return log_oom(); + +- r = crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); ++ r = measured_crypt_activate_by_passphrase(cd, name, keyslot, base64_encoded, base64_encoded_size, flags); + } + if (r == -EPERM) { + log_error_errno(r, "Failed to activate with TPM2 decrypted key. (Key incorrect?)"); +@@ -1648,9 +1846,9 @@ static int attach_luks_or_plain_or_bitlk_by_key_data( + assert(key_data); + + if (pass_volume_key) +- r = crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags); ++ r = measured_crypt_activate_by_volume_key(cd, name, key_data, key_data_size, flags); + else +- r = crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags); ++ r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, key_data, key_data_size, flags); + if (r == -EPERM) { + log_error_errno(r, "Failed to activate. (Key incorrect?)"); + return -EAGAIN; /* Log actual error, but return EAGAIN */ +@@ -1701,9 +1899,9 @@ static int attach_luks_or_plain_or_bitlk_by_key_file( + return log_error_errno(r, "Failed to read key file '%s': %m", key_file); + + if (pass_volume_key) +- r = crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags); ++ r = measured_crypt_activate_by_volume_key(cd, name, kfdata, kfsize, flags); + else +- r = crypt_activate_by_passphrase(cd, name, arg_key_slot, kfdata, kfsize, flags); ++ r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, kfdata, kfsize, flags); + if (r == -EPERM) { + log_error_errno(r, "Failed to activate with key file '%s'. (Key data incorrect?)", key_file); + return -EAGAIN; /* Log actual error, but return EAGAIN */ +@@ -1729,9 +1927,9 @@ static int attach_luks_or_plain_or_bitlk_by_passphrase( + r = -EINVAL; + STRV_FOREACH(p, passwords) { + if (pass_volume_key) +- r = crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags); ++ r = measured_crypt_activate_by_volume_key(cd, name, *p, arg_key_size, flags); + else +- r = crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags); ++ r = measured_crypt_activate_by_passphrase(cd, name, arg_key_slot, *p, strlen(*p), flags); + if (r >= 0) + break; + } +diff --git a/src/fundamental/tpm-pcr.h b/src/fundamental/tpm-pcr.h +index 794d593825..ec4c3a2b85 100644 +--- a/src/fundamental/tpm-pcr.h ++++ b/src/fundamental/tpm-pcr.h +@@ -25,6 +25,9 @@ + /* This TPM PCR is where we extend the initrd sysext images into which we pass to the booted kernel */ + #define TPM_PCR_INDEX_INITRD_SYSEXTS 13U + ++/* This TPM PCR is where we measure the root fs volume key (and maybe /var/'s) if it is split off */ ++#define TPM_PCR_INDEX_VOLUME_KEY 15U ++ + /* List of PE sections that have special meaning for us in unified kernels. This is the canonical order in + * which we measure the sections into TPM PCR 11 (see above). PLEASE DO NOT REORDER! */ + typedef enum UnifiedSection { diff --git a/SOURCES/0483-man-document-the-new-crypttab-measurement-options.patch b/SOURCES/0483-man-document-the-new-crypttab-measurement-options.patch new file mode 100644 index 0000000..4b7201e --- /dev/null +++ b/SOURCES/0483-man-document-the-new-crypttab-measurement-options.patch @@ -0,0 +1,62 @@ +From 4b28fbe37b02e0df2bd746303108a5e3ed089209 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 14 Oct 2022 15:27:34 +0200 +Subject: [PATCH] man: document the new crypttab measurement options + +(cherry picked from commit 572f78767f9958559aa4a3060fc5c9a006766240) + +Related: RHEL-16182 +--- + man/crypttab.xml | 22 ++++++++++++++++++++++ + man/systemd-cryptenroll.xml | 5 +++++ + 2 files changed, 27 insertions(+) + +diff --git a/man/crypttab.xml b/man/crypttab.xml +index cbbb8ab2a9..1dd9bb1bb6 100644 +--- a/man/crypttab.xml ++++ b/man/crypttab.xml +@@ -700,6 +700,28 @@ + order). + + ++ ++ ++ ++ Controls whether to measure the volume key of the encrypted volume to a TPM2 PCR. If ++ set to "no" (which is the default) no PCR extension is done. If set to "yes" the volume key is ++ measured into PCR 15. If set to a decimal integer in the range 0…23 the volume key is measured into ++ the specified PCR. The volume key is measured along with the activated volume name and its UUID. This ++ functionality is particularly useful for the encrypted volume backing the root file system, as it ++ then allows later TPM objects to be securely bound to the root file system and hence the specific ++ installation. ++ ++ ++ ++ ++ ++ Selects one or more TPM2 PCR banks to measure the volume key into, as configured with ++ above. Multiple banks may be specified, separated by a colon ++ character. If not specified automatically determines available and used banks. Expects a message ++ digest name (e.g. sha1, sha256, …) as argument, to identify the ++ bank. ++ ++ + + + +diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml +index ad338cdcc5..f08d95c6fb 100644 +--- a/man/systemd-cryptenroll.xml ++++ b/man/systemd-cryptenroll.xml +@@ -324,6 +324,11 @@ + 14 + The shim project measures its "MOK" certificates and hashes into this PCR. + ++ ++ ++ 15 ++ systemd-cryptsetup7 optionally measures the volume key of activated LUKS volumes into this PCR. ++ + + + diff --git a/SOURCES/0484-gpt-auto-generator-automatically-measure-root-var-vo.patch b/SOURCES/0484-gpt-auto-generator-automatically-measure-root-var-vo.patch new file mode 100644 index 0000000..e2e2682 --- /dev/null +++ b/SOURCES/0484-gpt-auto-generator-automatically-measure-root-var-vo.patch @@ -0,0 +1,149 @@ +From 0e471978c582a614467d20d041f65c935c407abf Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 14 Oct 2022 15:54:09 +0200 +Subject: [PATCH] gpt-auto-generator: automatically measure root/var volume + keys into PCR 15 + +let's enable PCR 15 measurements automatically if gpt-auto discovery is +used and systemd-stub is also used. + +(cherry picked from commit ff386f985bb51a48a11f74f6370dedf1bbfb4658) + +Related: RHEL-16182 +--- + man/systemd-gpt-auto-generator.xml | 8 +++++ + src/gpt-auto-generator/gpt-auto-generator.c | 36 ++++++++++++++++++--- + 2 files changed, 39 insertions(+), 5 deletions(-) + +diff --git a/man/systemd-gpt-auto-generator.xml b/man/systemd-gpt-auto-generator.xml +index 8ad249ec5d..f26bda511c 100644 +--- a/man/systemd-gpt-auto-generator.xml ++++ b/man/systemd-gpt-auto-generator.xml +@@ -221,6 +221,13 @@ + systems, make sure to set the correct default subvolumes on them, + using btrfs subvolume set-default. + ++ If the system was booted via ++ systemd-stub7 and the ++ stub reported to userspace that the kernel image was measured to a TPM2 PCR, then any discovered root and ++ /var/ volume identifiers (and volume encryption key in case it is encrypted) will be ++ automatically measured into PCR 15 on activation, via ++ systemd-pcrfs@.service8. ++ + systemd-gpt-auto-generator implements + systemd.generator7. + +@@ -272,6 +279,7 @@ + systemd.swap5, + systemd-fstab-generator8, + systemd-cryptsetup@.service8, ++ systemd-pcrfs@.service8, + machine-id5, + cryptsetup8, + fstab5, +diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c +index 0bab43e69a..2134185f04 100644 +--- a/src/gpt-auto-generator/gpt-auto-generator.c ++++ b/src/gpt-auto-generator/gpt-auto-generator.c +@@ -47,10 +47,11 @@ static int add_cryptsetup( + const char *what, + bool rw, + bool require, ++ bool measure, + char **ret_device) { + + #if HAVE_LIBCRYPTSETUP +- _cleanup_free_ char *e = NULL, *n = NULL, *d = NULL; ++ _cleanup_free_ char *e = NULL, *n = NULL, *d = NULL, *options = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; + +@@ -84,7 +85,28 @@ static int add_cryptsetup( + "After=%s\n", + d, d); + +- r = generator_write_cryptsetup_service_section(f, id, what, NULL, rw ? NULL : "read-only"); ++ if (!rw) { ++ options = strdup("read-only"); ++ if (!options) ++ return log_oom(); ++ } ++ ++ if (measure) { ++ /* We only measure the root volume key into PCR 15 if we are booted with sd-stub (i.e. in a ++ * UKI), and sd-stub measured the UKI. We do this in order not to step into people's own PCR ++ * assignment, under the assumption that people who are fine to use sd-stub with its PCR ++ * assignments are also OK with our PCR 15 use here. */ ++ ++ r = efi_get_variable(EFI_LOADER_VARIABLE(StubPcrKernelImage), NULL, NULL, NULL); /* we don't actually care which PCR the UKI used for itself */ ++ if (r == -ENOENT) ++ log_debug_errno(r, "Will not measure volume key of volume '%s', because not booted via systemd-stub with measurements enabled.", id); ++ else if (r < 0) ++ log_debug_errno(r, "Failed to determine whether booted via systemd-stub with measurements enabled, ignoring: %m"); ++ else if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes")) ++ return log_oom(); ++ } ++ ++ r = generator_write_cryptsetup_service_section(f, id, what, NULL, options); + if (r < 0) + return r; + +@@ -139,6 +161,7 @@ static int add_mount( + const char *fstype, + bool rw, + bool growfs, ++ bool measure, + const char *options, + const char *description, + const char *post) { +@@ -159,7 +182,7 @@ static int add_mount( + log_debug("Adding %s: %s fstype=%s", where, what, fstype ?: "(any)"); + + if (streq_ptr(fstype, "crypto_LUKS")) { +- r = add_cryptsetup(id, what, rw, true, &crypto_what); ++ r = add_cryptsetup(id, what, rw, /* require= */ true, measure, &crypto_what); + if (r < 0) + return r; + +@@ -277,6 +300,7 @@ static int add_partition_mount( + p->fstype, + p->rw, + p->growfs, ++ /* measure= */ STR_IN_SET(id, "root", "var"), /* by default measure rootfs and /var, since they contain the "identity" of the system */ + NULL, + description, + SPECIAL_LOCAL_FS_TARGET); +@@ -301,7 +325,7 @@ static int add_partition_swap(DissectedPartition *p) { + } + + if (streq_ptr(p->fstype, "crypto_LUKS")) { +- r = add_cryptsetup("swap", p->node, true, true, &crypto_what); ++ r = add_cryptsetup("swap", p->node, /* rw= */ true, /* require= */ true, /* measure= */ false, &crypto_what); + if (r < 0) + return r; + what = crypto_what; +@@ -374,6 +398,7 @@ static int add_automount( + fstype, + rw, + growfs, ++ /* measure= */ false, + opt, + description, + NULL); +@@ -582,7 +607,7 @@ static int add_root_cryptsetup(void) { + /* If a device /dev/gpt-auto-root-luks appears, then make it pull in systemd-cryptsetup-root.service, which + * sets it up, and causes /dev/gpt-auto-root to appear which is all we are looking for. */ + +- return add_cryptsetup("root", "/dev/gpt-auto-root-luks", true, false, NULL); ++ return add_cryptsetup("root", "/dev/gpt-auto-root-luks", /* rw= */ true, /* require= */ false, /* measure= */ true, NULL); + #else + return 0; + #endif +@@ -629,6 +654,7 @@ static int add_root_mount(void) { + NULL, + /* rw= */ arg_root_rw > 0, + /* growfs= */ false, ++ /* measure= */ true, + NULL, + "Root Partition", + in_initrd() ? SPECIAL_INITRD_ROOT_FS_TARGET : SPECIAL_LOCAL_FS_TARGET); diff --git a/SOURCES/0485-blkid-util-define-enum-for-blkid_do_safeprobe-return.patch b/SOURCES/0485-blkid-util-define-enum-for-blkid_do_safeprobe-return.patch new file mode 100644 index 0000000..3e2ae13 --- /dev/null +++ b/SOURCES/0485-blkid-util-define-enum-for-blkid_do_safeprobe-return.patch @@ -0,0 +1,169 @@ +From 4c9eb27048c07b0cf93377344ddfe2f8980a88e4 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Thu, 1 Dec 2022 15:37:59 +0100 +Subject: [PATCH] blkid-util: define enum for blkid_do_safeprobe() return + values + +libblkid really should define an enum for this on its own, but it +currently doesn't and returns literal numeric values. Lets make this +more readable by adding our own symbolic names via an enum. + +(cherry picked from commit 2e3944b872cf57dbccdda14ec66772e8fdd2273b) + +Related: RHEL-16182 +--- + src/home/homework-luks.c | 16 ++++++++++------ + src/partition/repart.c | 9 +++++---- + src/shared/blkid-util.h | 10 ++++++++++ + src/shared/dissect-image.c | 16 ++++++++++------ + src/shared/find-esp.c | 10 ++++++---- + 5 files changed, 41 insertions(+), 20 deletions(-) + +diff --git a/src/home/homework-luks.c b/src/home/homework-luks.c +index 53fa61b103..8795707f6e 100644 +--- a/src/home/homework-luks.c ++++ b/src/home/homework-luks.c +@@ -148,10 +148,12 @@ static int probe_file_system_by_fd( + + errno = 0; + r = blkid_do_safeprobe(b); +- if (IN_SET(r, -2, 1)) /* nothing found or ambiguous result */ ++ if (r == _BLKID_SAFEPROBE_ERROR) ++ return errno_or_else(EIO); ++ if (IN_SET(r, _BLKID_SAFEPROBE_AMBIGUOUS, _BLKID_SAFEPROBE_NOT_FOUND)) + return -ENOPKG; +- if (r != 0) +- return errno > 0 ? -errno : -EIO; ++ ++ assert(r == _BLKID_SAFEPROBE_FOUND); + + (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); + if (!fstype) +@@ -665,10 +667,12 @@ static int luks_validate( + + errno = 0; + r = blkid_do_safeprobe(b); +- if (IN_SET(r, -2, 1)) /* nothing found or ambiguous result */ ++ if (r == _BLKID_SAFEPROBE_ERROR) ++ return errno_or_else(EIO); ++ if (IN_SET(r, _BLKID_SAFEPROBE_AMBIGUOUS, _BLKID_SAFEPROBE_NOT_FOUND)) + return -ENOPKG; +- if (r != 0) +- return errno > 0 ? -errno : -EIO; ++ ++ assert(r == _BLKID_SAFEPROBE_FOUND); + + (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); + if (streq_ptr(fstype, "crypto_LUKS")) { +diff --git a/src/partition/repart.c b/src/partition/repart.c +index 8875e09389..cbd900969e 100644 +--- a/src/partition/repart.c ++++ b/src/partition/repart.c +@@ -4604,12 +4604,14 @@ static int resolve_copy_blocks_auto_candidate( + + errno = 0; + r = blkid_do_safeprobe(b); +- if (IN_SET(r, -2, 1)) { /* nothing found or ambiguous result */ ++ if (r == _BLKID_SAFEPROBE_ERROR) ++ return log_error_errno(errno_or_else(EIO), "Unable to probe for partition table of '%s': %m", p); ++ if (IN_SET(r, _BLKID_SAFEPROBE_AMBIGUOUS, _BLKID_SAFEPROBE_NOT_FOUND)) { + log_debug("Didn't find partition table on block device '%s'.", p); + return false; + } +- if (r != 0) +- return log_error_errno(errno_or_else(EIO), "Unable to probe for partition table of '%s': %m", p); ++ ++ assert(r == _BLKID_SAFEPROBE_FOUND); + + (void) blkid_probe_lookup_value(b, "PTTYPE", &pttype, NULL); + if (!streq_ptr(pttype, "gpt")) { +@@ -4621,7 +4623,6 @@ static int resolve_copy_blocks_auto_candidate( + pl = blkid_probe_get_partitions(b); + if (!pl) + return log_error_errno(errno_or_else(EIO), "Unable read partition table of '%s': %m", p); +- errno = 0; + + pp = blkid_partlist_devno_to_partition(pl, partition_devno); + if (!pp) { +diff --git a/src/shared/blkid-util.h b/src/shared/blkid-util.h +index aa444990fd..5df39eccfc 100644 +--- a/src/shared/blkid-util.h ++++ b/src/shared/blkid-util.h +@@ -7,4 +7,14 @@ + # include "macro.h" + + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(blkid_probe, blkid_free_probe, NULL); ++ ++/* Define symbolic names for blkid_do_safeprobe() return values, since blkid only uses literal numbers. We ++ * prefix these symbolic definitions with underscores, to not invade libblkid's namespace needlessly. */ ++enum { ++ _BLKID_SAFEPROBE_FOUND = 0, ++ _BLKID_SAFEPROBE_NOT_FOUND = 1, ++ _BLKID_SAFEPROBE_AMBIGUOUS = -2, ++ _BLKID_SAFEPROBE_ERROR = -1, ++}; ++ + #endif +diff --git a/src/shared/dissect-image.c b/src/shared/dissect-image.c +index d2abd5a087..7721769061 100644 +--- a/src/shared/dissect-image.c ++++ b/src/shared/dissect-image.c +@@ -201,14 +201,16 @@ int probe_filesystem_full(int fd, const char *path, char **ret_fstype) { + + errno = 0; + r = blkid_do_safeprobe(b); +- if (r == 1) ++ if (r == _BLKID_SAFEPROBE_NOT_FOUND) + goto not_found; +- if (r == -2) ++ if (r == _BLKID_SAFEPROBE_AMBIGUOUS) + return log_debug_errno(SYNTHETIC_ERRNO(EUCLEAN), + "Results ambiguous for partition %s", path); +- if (r != 0) ++ if (r == _BLKID_SAFEPROBE_ERROR) + return log_debug_errno(errno_or_else(EIO), "Failed to probe partition %s: %m", path); + ++ assert(r == _BLKID_SAFEPROBE_FOUND); ++ + (void) blkid_probe_lookup_value(b, "TYPE", &fstype, NULL); + + if (fstype) { +@@ -506,10 +508,12 @@ static int dissect_image( + + errno = 0; + r = blkid_do_safeprobe(b); +- if (IN_SET(r, -2, 1)) +- return log_debug_errno(SYNTHETIC_ERRNO(ENOPKG), "Failed to identify any partition table."); +- if (r != 0) ++ if (r == _BLKID_SAFEPROBE_ERROR) + return errno_or_else(EIO); ++ if (IN_SET(r, _BLKID_SAFEPROBE_AMBIGUOUS, _BLKID_SAFEPROBE_NOT_FOUND)) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOPKG), "Failed to identify any partition table."); ++ ++ assert(r == _BLKID_SAFEPROBE_FOUND); + + if ((!(flags & DISSECT_IMAGE_GPT_ONLY) && + (flags & DISSECT_IMAGE_GENERIC_ROOT)) || +diff --git a/src/shared/find-esp.c b/src/shared/find-esp.c +index fa234c8b5f..f005432887 100644 +--- a/src/shared/find-esp.c ++++ b/src/shared/find-esp.c +@@ -571,12 +571,14 @@ static int verify_xbootldr_blkid( + + errno = 0; + r = blkid_do_safeprobe(b); +- if (r == -2) ++ if (r == _BLKID_SAFEPROBE_AMBIGUOUS) + return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "%s: File system is ambiguous.", node); +- else if (r == 1) ++ if (r == _BLKID_SAFEPROBE_NOT_FOUND) + return log_error_errno(SYNTHETIC_ERRNO(ENODEV), "%s: File system does not contain a label.", node); +- else if (r != 0) +- return log_error_errno(errno ?: SYNTHETIC_ERRNO(EIO), "%s: Failed to probe file system: %m", node); ++ if (r == _BLKID_SAFEPROBE_ERROR) ++ return log_error_errno(errno_or_else(EIO), "%s: Failed to probe file system: %m", node); ++ ++ assert(r == _BLKID_SAFEPROBE_FOUND); + + r = blkid_probe_lookup_value(b, "PART_ENTRY_SCHEME", &type, NULL); + if (r != 0) diff --git a/SOURCES/0486-pcrphase-make-tool-more-generic-reuse-for-measuring-.patch b/SOURCES/0486-pcrphase-make-tool-more-generic-reuse-for-measuring-.patch new file mode 100644 index 0000000..daf2696 --- /dev/null +++ b/SOURCES/0486-pcrphase-make-tool-more-generic-reuse-for-measuring-.patch @@ -0,0 +1,372 @@ +From d2b974f55d80f09d544a3af6a2ef987df4284260 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 14 Oct 2022 23:29:48 +0200 +Subject: [PATCH] pcrphase: make tool more generic, reuse for measuring machine + id/fs uuids + +See: #24503 +(cherry picked from commit 17984c55513fc18f9bd4878c37fa87d278ab1e1d) + +Related: RHEL-16182 +--- + meson.build | 4 +- + src/boot/pcrphase.c | 210 +++++++++++++++++++++++++++++++++++++++----- + 2 files changed, 189 insertions(+), 25 deletions(-) + +diff --git a/meson.build b/meson.build +index fe7b47eef5..54155eee1f 100644 +--- a/meson.build ++++ b/meson.build +@@ -2610,7 +2610,9 @@ if conf.get('HAVE_BLKID') == 1 and conf.get('HAVE_GNU_EFI') == 1 + 'src/boot/pcrphase.c', + include_directories : includes, + link_with : [libshared], +- dependencies : [libopenssl, tpm2], ++ dependencies : [libopenssl, ++ tpm2, ++ libblkid], + install_rpath : rootpkglibdir, + install : true, + install_dir : rootlibexecdir) +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index 1f3dc4ab3a..12629b2be3 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -2,12 +2,20 @@ + + #include + ++#include + #include + ++#include "blkid-util.h" ++#include "blockdev-util.h" ++#include "chase-symlinks.h" + #include "efivars.h" + #include "env-util.h" ++#include "escape.h" ++#include "fd-util.h" + #include "main-func.h" ++#include "mountpoint-util.h" + #include "openssl-util.h" ++#include "parse-argument.h" + #include "parse-util.h" + #include "pretty-print.h" + #include "tpm-pcr.h" +@@ -16,9 +24,12 @@ + static bool arg_graceful = false; + static char *arg_tpm2_device = NULL; + static char **arg_banks = NULL; ++static char *arg_file_system = NULL; ++static bool arg_machine_id = false; + + STATIC_DESTRUCTOR_REGISTER(arg_banks, strv_freep); + STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); ++STATIC_DESTRUCTOR_REGISTER(arg_file_system, freep); + + static int help(int argc, char *argv[], void *userdata) { + _cleanup_free_ char *link = NULL; +@@ -28,7 +39,9 @@ static int help(int argc, char *argv[], void *userdata) { + if (r < 0) + return log_oom(); + +- printf("%1$s [OPTIONS...] WORD ...\n" ++ printf("%1$s [OPTIONS...] WORD\n" ++ "%1$s [OPTIONS...] --file-system=PATH\n" ++ "%1$s [OPTIONS...] --machine-id\n" + "\n%5$sMeasure boot phase into TPM2 PCR 11.%6$s\n" + "\n%3$sOptions:%4$s\n" + " -h --help Show this help\n" +@@ -36,6 +49,8 @@ static int help(int argc, char *argv[], void *userdata) { + " --bank=DIGEST Select TPM bank (SHA1, SHA256)\n" + " --tpm2-device=PATH Use specified TPM2 device\n" + " --graceful Exit gracefully if no TPM2 device is found\n" ++ " --file-system=PATH Measure UUID/labels of file system into PCR 15\n" ++ " --machine-id Measure machine ID into PCR 15\n" + "\nSee the %2$s for details.\n", + program_invocation_short_name, + link, +@@ -53,6 +68,8 @@ static int parse_argv(int argc, char *argv[]) { + ARG_BANK, + ARG_TPM2_DEVICE, + ARG_GRACEFUL, ++ ARG_FILE_SYSTEM, ++ ARG_MACHINE_ID, + }; + + static const struct option options[] = { +@@ -61,10 +78,12 @@ static int parse_argv(int argc, char *argv[]) { + { "bank", required_argument, NULL, ARG_BANK }, + { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, + { "graceful", no_argument, NULL, ARG_GRACEFUL }, ++ { "file-system", required_argument, NULL, ARG_FILE_SYSTEM }, ++ { "machine-id", no_argument, NULL, ARG_MACHINE_ID }, + {} + }; + +- int c; ++ int c, r; + + assert(argc >= 0); + assert(argv); +@@ -112,6 +131,17 @@ static int parse_argv(int argc, char *argv[]) { + arg_graceful = true; + break; + ++ case ARG_FILE_SYSTEM: ++ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_file_system); ++ if (r < 0) ++ return r; ++ ++ break; ++ ++ case ARG_MACHINE_ID: ++ arg_machine_id = true; ++ break; ++ + case '?': + return -EINVAL; + +@@ -119,10 +149,13 @@ static int parse_argv(int argc, char *argv[]) { + assert_not_reached(); + } + ++ if (arg_file_system && arg_machine_id) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "--file-system= and --machine-id may not be combined."); ++ + return 1; + } + +-static int determine_banks(struct tpm2_context *c) { ++static int determine_banks(struct tpm2_context *c, unsigned target_pcr_nr) { + _cleanup_strv_free_ char **l = NULL; + int r; + +@@ -131,7 +164,7 @@ static int determine_banks(struct tpm2_context *c) { + if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */ + return 0; + +- r = tpm2_get_good_pcr_banks_strv(c->esys_context, UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &l); ++ r = tpm2_get_good_pcr_banks_strv(c->esys_context, UINT32_C(1) << target_pcr_nr, &l); + if (r < 0) + return r; + +@@ -139,11 +172,77 @@ static int determine_banks(struct tpm2_context *c) { + return 0; + } + ++static int get_file_system_word( ++ sd_device *d, ++ const char *prefix, ++ char **ret) { ++ ++ int r; ++ ++ assert(d); ++ assert(prefix); ++ assert(ret); ++ ++ _cleanup_close_ int block_fd = sd_device_open(d, O_RDONLY|O_CLOEXEC|O_NONBLOCK); ++ if (block_fd < 0) ++ return block_fd; ++ ++ _cleanup_(blkid_free_probep) blkid_probe b = blkid_new_probe(); ++ if (!b) ++ return -ENOMEM; ++ ++ errno = 0; ++ r = blkid_probe_set_device(b, block_fd, 0, 0); ++ if (r != 0) ++ return errno_or_else(ENOMEM); ++ ++ (void) blkid_probe_enable_superblocks(b, 1); ++ (void) blkid_probe_set_superblocks_flags(b, BLKID_SUBLKS_TYPE|BLKID_SUBLKS_UUID|BLKID_SUBLKS_LABEL); ++ (void) blkid_probe_enable_partitions(b, 1); ++ (void) blkid_probe_set_partitions_flags(b, BLKID_PARTS_ENTRY_DETAILS); ++ ++ errno = 0; ++ r = blkid_do_safeprobe(b); ++ if (r == _BLKID_SAFEPROBE_ERROR) ++ return errno_or_else(EIO); ++ if (IN_SET(r, _BLKID_SAFEPROBE_AMBIGUOUS, _BLKID_SAFEPROBE_NOT_FOUND)) ++ return -ENOPKG; ++ ++ assert(r == _BLKID_SAFEPROBE_FOUND); ++ ++ _cleanup_strv_free_ char **l = strv_new(prefix); ++ if (!l) ++ return log_oom(); ++ ++ FOREACH_STRING(field, "TYPE", "UUID", "LABEL", "PART_ENTRY_UUID", "PART_ENTRY_TYPE", "PART_ENTRY_NAME") { ++ const char *v = NULL; ++ ++ (void) blkid_probe_lookup_value(b, field, &v, NULL); ++ ++ _cleanup_free_ char *escaped = xescape(strempty(v), ":"); /* Avoid ambiguity around ":" */ ++ if (!escaped) ++ return log_oom(); ++ ++ r = strv_consume(&l, TAKE_PTR(escaped)); ++ if (r < 0) ++ return log_oom(); ++ ++ } ++ ++ assert(strv_length(l) == 7); /* We always want 7 components, to avoid ambiguous strings */ ++ ++ _cleanup_free_ char *word = strv_join(l, ":"); ++ if (!word) ++ return log_oom(); ++ ++ *ret = TAKE_PTR(word); ++ return 0; ++} ++ + static int run(int argc, char *argv[]) { ++ _cleanup_free_ char *joined = NULL, *pcr_string = NULL, *word = NULL; + _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; +- _cleanup_free_ char *joined = NULL, *pcr_string = NULL; +- const char *word; +- unsigned pcr_nr; ++ unsigned target_pcr_nr, efi_pcr_nr; + size_t length; + int r; + +@@ -153,16 +252,79 @@ static int run(int argc, char *argv[]) { + if (r <= 0) + return r; + +- if (optind+1 != argc) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument."); ++ if (arg_file_system) { ++ _cleanup_free_ char *normalized = NULL, *normalized_escaped = NULL; ++ _cleanup_(sd_device_unrefp) sd_device *d = NULL; ++ _cleanup_close_ int dfd = -EBADF; ++ ++ if (optind != argc) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); + +- word = argv[optind]; ++ dfd = chase_symlinks_and_open(arg_file_system, NULL, 0, O_DIRECTORY|O_CLOEXEC, &normalized); ++ if (dfd < 0) ++ return log_error_errno(dfd, "Failed to open path '%s': %m", arg_file_system); + +- /* Refuse to measure an empty word. We want to be able to write the series of measured words +- * separated by colons, where multiple separating colons are collapsed. Thus it makes sense to +- * disallow an empty word to avoid ambiguities. */ +- if (isempty(word)) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing."); ++ r = fd_is_mount_point(dfd, NULL, 0); ++ if (r < 0) ++ return log_error_errno(r, "Failed to determine if path '%s' is mount point: %m", normalized); ++ if (r == 0) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTDIR), "Specified path '%s' is not a mount point, refusing: %m", normalized); ++ ++ normalized_escaped = xescape(normalized, ":"); /* Avoid ambiguity around ":" */ ++ if (!normalized_escaped) ++ return log_oom(); ++ ++ _cleanup_free_ char* prefix = strjoin("file-system:", normalized_escaped); ++ if (!prefix) ++ return log_oom(); ++ ++ r = block_device_new_from_fd(dfd, BLOCK_DEVICE_LOOKUP_BACKING, &d); ++ if (r < 0) { ++ log_notice_errno(r, "Unable to determine backing block device of '%s', measuring generic fallback file system identity string: %m", arg_file_system); ++ ++ word = strjoin(prefix, "::::::"); ++ if (!word) ++ return log_oom(); ++ } else { ++ r = get_file_system_word(d, prefix, &word); ++ if (r < 0) ++ return log_error_errno(r, "Failed to get file system identifier string for '%s': %m", arg_file_system); ++ } ++ ++ target_pcr_nr = TPM_PCR_INDEX_VOLUME_KEY; /* → PCR 15 */ ++ ++ } else if (arg_machine_id) { ++ sd_id128_t mid; ++ ++ if (optind != argc) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected no argument."); ++ ++ r = sd_id128_get_machine(&mid); ++ if (r < 0) ++ return log_error_errno(r, "Failed to acquire machine ID: %m"); ++ ++ word = strjoin("machine-id:", SD_ID128_TO_STRING(mid)); ++ if (!word) ++ return log_oom(); ++ ++ target_pcr_nr = TPM_PCR_INDEX_VOLUME_KEY; /* → PCR 15 */ ++ ++ } else { ++ if (optind+1 != argc) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Expected a single argument."); ++ ++ word = strdup(argv[optind]); ++ if (!word) ++ return log_oom(); ++ ++ /* Refuse to measure an empty word. We want to be able to write the series of measured words ++ * separated by colons, where multiple separating colons are collapsed. Thus it makes sense to ++ * disallow an empty word to avoid ambiguities. */ ++ if (isempty(word)) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "String to measure cannot be empty, refusing."); ++ ++ target_pcr_nr = TPM_PCR_INDEX_KERNEL_IMAGE; /* → PCR 11 */ ++ } + + if (arg_graceful && tpm2_support() != TPM2_SUPPORT_FULL) { + log_notice("No complete TPM2 support detected, exiting gracefully."); +@@ -187,14 +349,14 @@ static int run(int argc, char *argv[]) { + return log_error_errno(r, "Failed to read StubPcrKernelImage EFI variable: %m"); + else { + /* Let's validate that the stub announced PCR 11 as we expected. */ +- r = safe_atou(pcr_string, &pcr_nr); ++ r = safe_atou(pcr_string, &efi_pcr_nr); + if (r < 0) + return log_error_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string); +- if (pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE) { ++ if (efi_pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE) { + if (b != 0) +- return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); ++ return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", efi_pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); + else +- log_notice("Kernel stub measured kernel image into PCR %u, which is different than expected %u, but told to measure anyway, hence proceeding.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); ++ log_notice("Kernel stub measured kernel image into PCR %u, which is different than expected %u, but told to measure anyway, hence proceeding.", efi_pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); + } else + log_debug("Kernel stub reported same PCR %u as we want to use, proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE); + } +@@ -207,7 +369,7 @@ static int run(int argc, char *argv[]) { + if (r < 0) + return r; + +- r = determine_banks(&c); ++ r = determine_banks(&c, target_pcr_nr); + if (r < 0) + return r; + if (strv_isempty(arg_banks)) /* Still none? */ +@@ -217,17 +379,17 @@ static int run(int argc, char *argv[]) { + if (!joined) + return log_oom(); + +- log_debug("Measuring '%s' into PCR index %u, banks %s.", word, TPM_PCR_INDEX_KERNEL_IMAGE, joined); ++ log_debug("Measuring '%s' into PCR index %u, banks %s.", word, target_pcr_nr, joined); + +- r = tpm2_extend_bytes(c.esys_context, arg_banks, TPM_PCR_INDEX_KERNEL_IMAGE, word, length, NULL, 0); /* → PCR 11 */ ++ r = tpm2_extend_bytes(c.esys_context, arg_banks, target_pcr_nr, word, length, NULL, 0); + if (r < 0) + return r; + + log_struct(LOG_INFO, + "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR, +- LOG_MESSAGE("Successfully extended PCR index %u with '%s' (banks %s).", TPM_PCR_INDEX_KERNEL_IMAGE, word, joined), ++ LOG_MESSAGE("Extended PCR index %u with '%s' (banks %s).", target_pcr_nr, word, joined), + "MEASURING=%s", word, +- "PCR=%u", TPM_PCR_INDEX_KERNEL_IMAGE, ++ "PCR=%u", target_pcr_nr, + "BANKS=%s", joined); + + return EXIT_SUCCESS; diff --git a/SOURCES/0487-units-measure-etc-machine-id-into-PCR-15-during-earl.patch b/SOURCES/0487-units-measure-etc-machine-id-into-PCR-15-during-earl.patch new file mode 100644 index 0000000..e84fbd3 --- /dev/null +++ b/SOURCES/0487-units-measure-etc-machine-id-into-PCR-15-during-earl.patch @@ -0,0 +1,61 @@ +From 46f2825866379e5019516269c9de88b8e2ba7c78 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Sun, 16 Oct 2022 18:21:12 +0200 +Subject: [PATCH] units: measure /etc/machine-id into PCR 15 during early boot + +We want PCR 15 to be useful for binding per-system policy to. Let's +measure the machine ID into it, to ensure that every OS we can +distinguish will get a different PCR (even if the root disk encryption +key is already measured into it). + +(cherry picked from commit 072c8f650519f47a575b1e39509599ace21e2c8f) + +Related: RHEL-16182 +--- + units/meson.build | 2 ++ + units/systemd-pcrmachine.service.in | 23 +++++++++++++++++++++++ + 2 files changed, 25 insertions(+) + create mode 100644 units/systemd-pcrmachine.service.in + +diff --git a/units/meson.build b/units/meson.build +index a99f27adc5..9046e5d066 100644 +--- a/units/meson.build ++++ b/units/meson.build +@@ -266,6 +266,8 @@ in_units = [ + 'sysinit.target.wants/'], + ['systemd-growfs-root.service', ''], + ['systemd-growfs@.service', ''], ++ ['systemd-pcrmachine.service', 'HAVE_GNU_EFI HAVE_OPENSSL HAVE_TPM2', ++ 'sysinit.target.wants/'], + ] + + add_wants = [] +diff --git a/units/systemd-pcrmachine.service.in b/units/systemd-pcrmachine.service.in +new file mode 100644 +index 0000000000..e154a7eec1 +--- /dev/null ++++ b/units/systemd-pcrmachine.service.in +@@ -0,0 +1,23 @@ ++# SPDX-License-Identifier: LGPL-2.1-or-later ++# ++# This file is part of systemd. ++# ++# systemd is free software; you can redistribute it and/or modify it ++# under the terms of the GNU Lesser General Public License as published by ++# the Free Software Foundation; either version 2.1 of the License, or ++# (at your option) any later version. ++ ++[Unit] ++Description=TPM2 PCR Machine ID Measurement ++Documentation=man:systemd-pcrmachine.service(8) ++DefaultDependencies=no ++Conflicts=shutdown.target ++Before=sysinit.target shutdown.target ++AssertPathExists=!/etc/initrd-release ++ConditionSecurity=tpm2 ++ConditionPathExists=/sys/firmware/efi/efivars/StubPcrKernelImage-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f ++ ++[Service] ++Type=oneshot ++RemainAfterExit=yes ++ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase --machine-id diff --git a/SOURCES/0488-generators-optionally-measure-file-systems-at-boot.patch b/SOURCES/0488-generators-optionally-measure-file-systems-at-boot.patch new file mode 100644 index 0000000..b7357e8 --- /dev/null +++ b/SOURCES/0488-generators-optionally-measure-file-systems-at-boot.patch @@ -0,0 +1,268 @@ +From 1d797fa7c074a9b6aa770466a3718a41d17d4aaf Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Sun, 16 Oct 2022 23:25:04 +0200 +Subject: [PATCH] generators: optionally, measure file systems at boot + +If we use gpt-auto-generator, automatically measure root fs and /var. + +Otherwise, add x-systemd.measure option to request this. + +(cherry picked from commit 04959faa632272a8fc9cdac3121b2e4af721c1b6) + +Related: RHEL-16182 +--- + src/basic/special.h | 2 ++ + src/fstab-generator/fstab-generator.c | 21 +++++++++--- + src/gpt-auto-generator/gpt-auto-generator.c | 6 ++++ + src/shared/generator.c | 37 +++++++++++++++++++++ + src/shared/generator.h | 4 +++ + units/meson.build | 2 ++ + units/systemd-pcrfs-root.service.in | 24 +++++++++++++ + units/systemd-pcrfs@.service.in | 25 ++++++++++++++ + 8 files changed, 116 insertions(+), 5 deletions(-) + create mode 100644 units/systemd-pcrfs-root.service.in + create mode 100644 units/systemd-pcrfs@.service.in + +diff --git a/src/basic/special.h b/src/basic/special.h +index 9bb36c5732..0e4342eb40 100644 +--- a/src/basic/special.h ++++ b/src/basic/special.h +@@ -89,6 +89,8 @@ + #define SPECIAL_UDEVD_SERVICE "systemd-udevd.service" + #define SPECIAL_GROWFS_SERVICE "systemd-growfs@.service" + #define SPECIAL_GROWFS_ROOT_SERVICE "systemd-growfs-root.service" ++#define SPECIAL_PCRFS_SERVICE "systemd-pcrfs@.service" ++#define SPECIAL_PCRFS_ROOT_SERVICE "systemd-pcrfs-root.service" + + /* Services systemd relies on */ + #define SPECIAL_DBUS_SERVICE "dbus.service" +diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c +index c3fe285344..c4915a37d3 100644 +--- a/src/fstab-generator/fstab-generator.c ++++ b/src/fstab-generator/fstab-generator.c +@@ -42,6 +42,7 @@ typedef enum MountPointFlags { + MOUNT_MAKEFS = 1 << 3, + MOUNT_GROWFS = 1 << 4, + MOUNT_RW_ONLY = 1 << 5, ++ MOUNT_PCRFS = 1 << 6, + } MountPointFlags; + + typedef struct Mount { +@@ -238,9 +239,9 @@ static int add_swap( + return true; + } + +- log_debug("Found swap entry what=%s makefs=%s growfs=%s noauto=%s nofail=%s", ++ log_debug("Found swap entry what=%s makefs=%s growfs=%s pcrfs=%s noauto=%s nofail=%s", + what, +- yes_no(flags & MOUNT_MAKEFS), yes_no(flags & MOUNT_GROWFS), ++ yes_no(flags & MOUNT_MAKEFS), yes_no(flags & MOUNT_GROWFS), yes_no(flags & MOUNT_PCRFS), + yes_no(flags & MOUNT_NOAUTO), yes_no(flags & MOUNT_NOFAIL)); + + r = unit_name_from_path(what, ".swap", &name); +@@ -291,6 +292,8 @@ static int add_swap( + if (flags & MOUNT_GROWFS) + /* TODO: swap devices must be wiped and recreated */ + log_warning("%s: growing swap devices is currently unsupported.", what); ++ if (flags & MOUNT_PCRFS) ++ log_warning("%s: measuring swap devices is currently unsupported.", what); + + if (!(flags & MOUNT_NOAUTO)) { + r = generator_add_symlink(arg_dest, SPECIAL_SWAP_TARGET, +@@ -642,6 +645,12 @@ static int add_mount( + return r; + } + ++ if (flags & MOUNT_PCRFS) { ++ r = generator_hook_up_pcrfs(dest, where, target_unit); ++ if (r < 0) ++ return r; ++ } ++ + if (!FLAGS_SET(flags, MOUNT_AUTOMOUNT)) { + if (!FLAGS_SET(flags, MOUNT_NOAUTO) && strv_isempty(wanted_by) && strv_isempty(required_by)) { + r = generator_add_symlink(dest, target_unit, +@@ -784,6 +793,8 @@ static MountPointFlags fstab_options_to_flags(const char *options, bool is_swap) + flags |= MOUNT_MAKEFS; + if (fstab_test_option(options, "x-systemd.growfs\0")) + flags |= MOUNT_GROWFS; ++ if (fstab_test_option(options, "x-systemd.pcrfs\0")) ++ flags |= MOUNT_PCRFS; + if (fstab_test_yes_no_option(options, "noauto\0" "auto\0")) + flags |= MOUNT_NOAUTO; + if (fstab_test_yes_no_option(options, "nofail\0" "fail\0")) +@@ -912,9 +923,9 @@ static int parse_fstab_one( + } + + +- log_debug("Found entry what=%s where=%s type=%s makefs=%s growfs=%s noauto=%s nofail=%s", ++ log_debug("Found entry what=%s where=%s type=%s makefs=%s growfs=%s pcrfs=%s noauto=%s nofail=%s", + what, where, strna(fstype), +- yes_no(flags & MOUNT_MAKEFS), yes_no(flags & MOUNT_GROWFS), ++ yes_no(flags & MOUNT_MAKEFS), yes_no(flags & MOUNT_GROWFS), yes_no(flags & MOUNT_PCRFS), + yes_no(flags & MOUNT_NOAUTO), yes_no(flags & MOUNT_NOFAIL)); + + bool is_sysroot = in_initrd() && path_equal(where, "/sysroot"); +@@ -1127,7 +1138,7 @@ static int add_sysroot_mount(void) { + fstype, + opts, + is_device_path(what) ? 1 : 0, /* passno */ +- 0, /* makefs off, growfs off, noauto off, nofail off, automount off */ ++ 0, /* makefs off, pcrfs off, noauto off, nofail off, automount off */ + SPECIAL_INITRD_ROOT_FS_TARGET); + } + +diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c +index 2134185f04..2620a12f03 100644 +--- a/src/gpt-auto-generator/gpt-auto-generator.c ++++ b/src/gpt-auto-generator/gpt-auto-generator.c +@@ -245,6 +245,12 @@ static int add_mount( + return r; + } + ++ if (measure) { ++ r = generator_hook_up_pcrfs(arg_dest, where, post); ++ if (r < 0) ++ return r; ++ } ++ + if (post) { + r = generator_add_symlink(arg_dest, post, "requires", unit); + if (r < 0) +diff --git a/src/shared/generator.c b/src/shared/generator.c +index f1c5e506ab..284e5fc580 100644 +--- a/src/shared/generator.c ++++ b/src/shared/generator.c +@@ -645,6 +645,43 @@ int generator_hook_up_growfs( + return generator_add_symlink_full(dir, where_unit, "wants", growfs_unit_path, instance); + } + ++int generator_hook_up_pcrfs( ++ const char *dir, ++ const char *where, ++ const char *target) { ++ ++ const char *pcrfs_unit, *pcrfs_unit_path; ++ _cleanup_free_ char *where_unit = NULL, *instance = NULL; ++ int r; ++ ++ assert(dir); ++ assert(where); ++ ++ r = unit_name_from_path(where, ".mount", &where_unit); ++ if (r < 0) ++ return log_error_errno(r, "Failed to make unit name from path '%s': %m", where); ++ ++ if (empty_or_root(where)) { ++ pcrfs_unit = SPECIAL_PCRFS_ROOT_SERVICE; ++ pcrfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_PCRFS_ROOT_SERVICE; ++ } else { ++ pcrfs_unit = SPECIAL_PCRFS_SERVICE; ++ pcrfs_unit_path = SYSTEM_DATA_UNIT_DIR "/" SPECIAL_PCRFS_SERVICE; ++ ++ r = unit_name_path_escape(where, &instance); ++ if (r < 0) ++ return log_error_errno(r, "Failed to escape path '%s': %m", where); ++ } ++ ++ if (target) { ++ r = generator_add_ordering(dir, target, "After", pcrfs_unit, instance); ++ if (r < 0) ++ return r; ++ } ++ ++ return generator_add_symlink_full(dir, where_unit, "wants", pcrfs_unit_path, instance); ++} ++ + int generator_enable_remount_fs_service(const char *dir) { + /* Pull in systemd-remount-fs.service */ + return generator_add_symlink(dir, SPECIAL_LOCAL_FS_TARGET, "wants", +diff --git a/src/shared/generator.h b/src/shared/generator.h +index a4049dbd8f..111900fd45 100644 +--- a/src/shared/generator.h ++++ b/src/shared/generator.h +@@ -81,6 +81,10 @@ int generator_hook_up_growfs( + const char *dir, + const char *where, + const char *target); ++int generator_hook_up_pcrfs( ++ const char *dir, ++ const char *where, ++ const char *target); + + int generator_enable_remount_fs_service(const char *dir); + +diff --git a/units/meson.build b/units/meson.build +index 9046e5d066..3a1f5229a0 100644 +--- a/units/meson.build ++++ b/units/meson.build +@@ -264,6 +264,8 @@ in_units = [ + 'sysinit.target.wants/'], + ['systemd-pcrphase.service', 'HAVE_GNU_EFI HAVE_OPENSSL HAVE_TPM2', + 'sysinit.target.wants/'], ++ ['systemd-pcrfs-root.service', ''], ++ ['systemd-pcrfs@.service', ''], + ['systemd-growfs-root.service', ''], + ['systemd-growfs@.service', ''], + ['systemd-pcrmachine.service', 'HAVE_GNU_EFI HAVE_OPENSSL HAVE_TPM2', +diff --git a/units/systemd-pcrfs-root.service.in b/units/systemd-pcrfs-root.service.in +new file mode 100644 +index 0000000000..b0da413bb4 +--- /dev/null ++++ b/units/systemd-pcrfs-root.service.in +@@ -0,0 +1,24 @@ ++# SPDX-License-Identifier: LGPL-2.1-or-later ++# ++# This file is part of systemd. ++# ++# systemd is free software; you can redistribute it and/or modify it ++# under the terms of the GNU Lesser General Public License as published by ++# the Free Software Foundation; either version 2.1 of the License, or ++# (at your option) any later version. ++ ++[Unit] ++Description=TPM2 PCR Root File System Measurement ++Documentation=man:systemd-pcrfs-root.service(8) ++DefaultDependencies=no ++Conflicts=shutdown.target ++After=systemd-pcrmachine.service ++Before=shutdown.target ++AssertPathExists=!/etc/initrd-release ++ConditionSecurity=tpm2 ++ConditionPathExists=/sys/firmware/efi/efivars/StubPcrKernelImage-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f ++ ++[Service] ++Type=oneshot ++RemainAfterExit=yes ++ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase --file-system=/ +diff --git a/units/systemd-pcrfs@.service.in b/units/systemd-pcrfs@.service.in +new file mode 100644 +index 0000000000..ec1ff118c3 +--- /dev/null ++++ b/units/systemd-pcrfs@.service.in +@@ -0,0 +1,25 @@ ++# SPDX-License-Identifier: LGPL-2.1-or-later ++# ++# This file is part of systemd. ++# ++# systemd is free software; you can redistribute it and/or modify it ++# under the terms of the GNU Lesser General Public License as published by ++# the Free Software Foundation; either version 2.1 of the License, or ++# (at your option) any later version. ++ ++[Unit] ++Description=TPM2 PCR File System Measurement of %f ++Documentation=man:systemd-pcrfs@.service(8) ++DefaultDependencies=no ++BindsTo=%i.mount ++Conflicts=shutdown.target ++After=%i.mount systemd-pcrfs-root.service ++Before=shutdown.target ++AssertPathExists=!/etc/initrd-release ++ConditionSecurity=tpm2 ++ConditionPathExists=/sys/firmware/efi/efivars/StubPcrKernelImage-4a67b082-0a4c-41cf-b6c7-440b29bb8c4f ++ ++[Service] ++Type=oneshot ++RemainAfterExit=yes ++ExecStart={{ROOTLIBEXECDIR}}/systemd-pcrphase --file-system=%f diff --git a/SOURCES/0489-tpm2-add-common-helper-for-checking-if-we-are-runnin.patch b/SOURCES/0489-tpm2-add-common-helper-for-checking-if-we-are-runnin.patch new file mode 100644 index 0000000..03fc99e --- /dev/null +++ b/SOURCES/0489-tpm2-add-common-helper-for-checking-if-we-are-runnin.patch @@ -0,0 +1,282 @@ +From f4a9a464838c75f76731c5e6800a35cc4ec62cad Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 17 Oct 2022 14:50:56 +0200 +Subject: [PATCH] tpm2: add common helper for checking if we are running on UKI + with TPM measurements + +Let's introduce a common implementation of a function that checks +whether we are booted on a kernel with systemd-stub that has TPM PCR +measurements enabled. Do our own userspace measurements only if we +detect that. + +PCRs are scarce and most likely there are projects which already make +use of them in other ways. Hence, instead of blindly stepping into their +territory let's conditionalize things so that people have to explicitly +buy into our PCR assignments before we start measuring things into them. +Specifically bind everything to an UKI that reported measurements. + +This was previously already implemented in systemd-pcrphase, but with +this change we expand this to all tools that process PCR measurement +settings. + +The env var to override the check is renamed to SYSTEMD_FORCE_MEASURE, +to make it more generic (since we'll use it at multiple places now). +This is not a compat break, since the original env var for that was not +included in any stable release yet. + +(cherry picked from commit 6c51b49ce0892ff923233a6031add4877100f5b0) + +Related: RHEL-16182 +--- + docs/ENVIRONMENT.md | 9 +++-- + src/boot/pcrphase.c | 38 +++++--------------- + src/cryptsetup/cryptsetup.c | 9 +++++ + src/fstab-generator/fstab-generator.c | 12 +++++-- + src/gpt-auto-generator/gpt-auto-generator.c | 10 +++--- + src/shared/efi-loader.c | 39 +++++++++++++++++++++ + src/shared/efi-loader.h | 2 ++ + 7 files changed, 80 insertions(+), 39 deletions(-) + +diff --git a/docs/ENVIRONMENT.md b/docs/ENVIRONMENT.md +index 7b2dd13673..51b1e851ff 100644 +--- a/docs/ENVIRONMENT.md ++++ b/docs/ENVIRONMENT.md +@@ -484,7 +484,10 @@ SYSTEMD_HOME_DEBUG_SUFFIX=foo \ + specified algorithm takes an effect immediately, you need to explicitly run + `journalctl --rotate`. + +-`systemd-pcrphase`: ++`systemd-pcrphase`, `systemd-cryptsetup`: + +-* `$SYSTEMD_PCRPHASE_STUB_VERIFY` – Takes a boolean. If false the requested +- measurement is done even if no EFI stub usage was reported via EFI variables. ++* `$SYSTEMD_FORCE_MEASURE=1` — If set, force measuring of resources (which are ++ marked for measurement) even if not booted on a kernel equipped with ++ systemd-stub. Normally, requested measurement of resources is conditionalized ++ on kernels that have booted with `systemd-stub`. With this environment ++ variable the test for that my be bypassed, for testing purposes. +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index 12629b2be3..fda9a8420d 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -8,15 +8,14 @@ + #include "blkid-util.h" + #include "blockdev-util.h" + #include "chase-symlinks.h" ++#include "efi-loader.h" + #include "efivars.h" +-#include "env-util.h" + #include "escape.h" + #include "fd-util.h" + #include "main-func.h" + #include "mountpoint-util.h" + #include "openssl-util.h" + #include "parse-argument.h" +-#include "parse-util.h" + #include "pretty-print.h" + #include "tpm-pcr.h" + #include "tpm2-util.h" +@@ -240,9 +239,9 @@ static int get_file_system_word( + } + + static int run(int argc, char *argv[]) { +- _cleanup_free_ char *joined = NULL, *pcr_string = NULL, *word = NULL; + _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; +- unsigned target_pcr_nr, efi_pcr_nr; ++ _cleanup_free_ char *joined = NULL, *word = NULL; ++ unsigned target_pcr_nr; + size_t length; + int r; + +@@ -333,32 +332,13 @@ static int run(int argc, char *argv[]) { + + length = strlen(word); + +- int b = getenv_bool("SYSTEMD_PCRPHASE_STUB_VERIFY"); +- if (b < 0 && b != -ENXIO) +- log_warning_errno(b, "Unable to parse $SYSTEMD_PCRPHASE_STUB_VERIFY value, ignoring."); +- + /* Skip logic if sd-stub is not used, after all PCR 11 might have a very different purpose then. */ +- r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubPcrKernelImage), &pcr_string); +- if (r == -ENOENT) { +- if (b != 0) { +- log_info("Kernel stub did not measure kernel image into PCR %u, skipping measurement.", TPM_PCR_INDEX_KERNEL_IMAGE); +- return EXIT_SUCCESS; +- } else +- log_notice("Kernel stub did not measure kernel image into PCR %u, but told to measure anyway, hence proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE); +- } else if (r < 0) +- return log_error_errno(r, "Failed to read StubPcrKernelImage EFI variable: %m"); +- else { +- /* Let's validate that the stub announced PCR 11 as we expected. */ +- r = safe_atou(pcr_string, &efi_pcr_nr); +- if (r < 0) +- return log_error_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string); +- if (efi_pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE) { +- if (b != 0) +- return log_error_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", efi_pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); +- else +- log_notice("Kernel stub measured kernel image into PCR %u, which is different than expected %u, but told to measure anyway, hence proceeding.", efi_pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); +- } else +- log_debug("Kernel stub reported same PCR %u as we want to use, proceeding.", TPM_PCR_INDEX_KERNEL_IMAGE); ++ r = efi_stub_measured(); ++ if (r < 0) ++ return log_error_errno(r, "Failed to detect if we are running on a kernel image with TPM measurement enabled: %m"); ++ if (r == 0) { ++ log_info("Kernel stub did not measure kernel image into PCR %u, skipping userspace measurement, too.", TPM_PCR_INDEX_KERNEL_IMAGE); ++ return EXIT_SUCCESS; + } + + r = dlopen_tpm2(); +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index 20862e926d..4d587fed1e 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -19,6 +19,7 @@ + #include "cryptsetup-util.h" + #include "device-util.h" + #include "efi-api.h" ++#include "efi-loader.h" + #include "env-util.h" + #include "escape.h" + #include "fileio.h" +@@ -827,6 +828,14 @@ static int measure_volume_key( + return 0; + } + ++ r = efi_stub_measured(); ++ if (r < 0) ++ return log_warning_errno(r, "Failed to detect if we are running on a kernel image with TPM measurement enabled: %m"); ++ if (r == 0) { ++ log_debug("Kernel stub did not measure kernel image into the expected PCR, skipping userspace measurement, too."); ++ return 0; ++ } ++ + #if HAVE_TPM2 + r = dlopen_tpm2(); + if (r < 0) +diff --git a/src/fstab-generator/fstab-generator.c b/src/fstab-generator/fstab-generator.c +index c4915a37d3..b9606a5341 100644 +--- a/src/fstab-generator/fstab-generator.c ++++ b/src/fstab-generator/fstab-generator.c +@@ -8,6 +8,7 @@ + #include "bus-error.h" + #include "bus-locator.h" + #include "chase-symlinks.h" ++#include "efi-loader.h" + #include "env-util.h" + #include "fd-util.h" + #include "fileio.h" +@@ -646,9 +647,16 @@ static int add_mount( + } + + if (flags & MOUNT_PCRFS) { +- r = generator_hook_up_pcrfs(dest, where, target_unit); ++ r = efi_stub_measured(); + if (r < 0) +- return r; ++ log_warning_errno(r, "Failed to detect if we are running on a kernel image with TPM measurement enabled, assuming not: %m"); ++ else if (r == 0) ++ log_debug("Kernel stub did not measure kernel image into PCR, skipping userspace measurement, too."); ++ else { ++ r = generator_hook_up_pcrfs(dest, where, target_unit); ++ if (r < 0) ++ return r; ++ } + } + + if (!FLAGS_SET(flags, MOUNT_AUTOMOUNT)) { +diff --git a/src/gpt-auto-generator/gpt-auto-generator.c b/src/gpt-auto-generator/gpt-auto-generator.c +index 2620a12f03..27139a624e 100644 +--- a/src/gpt-auto-generator/gpt-auto-generator.c ++++ b/src/gpt-auto-generator/gpt-auto-generator.c +@@ -97,11 +97,11 @@ static int add_cryptsetup( + * assignment, under the assumption that people who are fine to use sd-stub with its PCR + * assignments are also OK with our PCR 15 use here. */ + +- r = efi_get_variable(EFI_LOADER_VARIABLE(StubPcrKernelImage), NULL, NULL, NULL); /* we don't actually care which PCR the UKI used for itself */ +- if (r == -ENOENT) +- log_debug_errno(r, "Will not measure volume key of volume '%s', because not booted via systemd-stub with measurements enabled.", id); +- else if (r < 0) +- log_debug_errno(r, "Failed to determine whether booted via systemd-stub with measurements enabled, ignoring: %m"); ++ r = efi_stub_measured(); ++ if (r < 0) ++ log_warning_errno(r, "Failed to determine whether booted via systemd-stub with measurements enabled, ignoring: %m"); ++ else if (r == 0) ++ log_debug("Will not measure volume key of volume '%s', because not booted via systemd-stub with measurements enabled.", id); + else if (!strextend_with_separator(&options, ",", "tpm2-measure-pcr=yes")) + return log_oom(); + } +diff --git a/src/shared/efi-loader.c b/src/shared/efi-loader.c +index 1340412cda..621fa082ba 100644 +--- a/src/shared/efi-loader.c ++++ b/src/shared/efi-loader.c +@@ -2,10 +2,12 @@ + + #include "alloc-util.h" + #include "efi-loader.h" ++#include "env-util.h" + #include "parse-util.h" + #include "path-util.h" + #include "stat-util.h" + #include "strv.h" ++#include "tpm-pcr.h" + #include "utf8.h" + + #if ENABLE_EFI +@@ -236,6 +238,43 @@ int efi_stub_get_features(uint64_t *ret) { + return 0; + } + ++int efi_stub_measured(void) { ++ _cleanup_free_ char *pcr_string = NULL; ++ unsigned pcr_nr; ++ int r; ++ ++ /* Checks if we are booted on a kernel with sd-stub which measured the kernel into PCR 11. Or in ++ * other words, if we are running on a TPM enabled UKI. ++ * ++ * Returns == 0 and > 0 depending on the result of the test. Returns -EREMOTE if we detected a stub ++ * being used, but it measured things into a different PCR than we are configured for in ++ * userspace. (i.e. we expect PCR 11 being used for this by both sd-stub and us) */ ++ ++ r = getenv_bool_secure("SYSTEMD_FORCE_MEASURE"); /* Give user a chance to override the variable test, ++ * for debugging purposes */ ++ if (r >= 0) ++ return r; ++ if (r != -ENXIO) ++ log_debug_errno(r, "Failed to parse $SYSTEMD_FORCE_MEASURE, ignoring: %m"); ++ ++ if (!is_efi_boot()) ++ return 0; ++ ++ r = efi_get_variable_string(EFI_LOADER_VARIABLE(StubPcrKernelImage), &pcr_string); ++ if (r == -ENOENT) ++ return 0; ++ if (r < 0) ++ return r; ++ ++ r = safe_atou(pcr_string, &pcr_nr); ++ if (r < 0) ++ return log_debug_errno(r, "Failed to parse StubPcrKernelImage EFI variable: %s", pcr_string); ++ if (pcr_nr != TPM_PCR_INDEX_KERNEL_IMAGE) ++ return log_debug_errno(SYNTHETIC_ERRNO(EREMOTE), "Kernel stub measured kernel image into PCR %u, which is different than expected %u.", pcr_nr, TPM_PCR_INDEX_KERNEL_IMAGE); ++ ++ return 1; ++} ++ + int efi_loader_get_config_timeout_one_shot(usec_t *ret) { + _cleanup_free_ char *v = NULL; + static struct stat cache_stat = {}; +diff --git a/src/shared/efi-loader.h b/src/shared/efi-loader.h +index 84968869ab..56ccdee9c1 100644 +--- a/src/shared/efi-loader.h ++++ b/src/shared/efi-loader.h +@@ -18,6 +18,8 @@ int efi_loader_get_entries(char ***ret); + int efi_loader_get_features(uint64_t *ret); + int efi_stub_get_features(uint64_t *ret); + ++int efi_stub_measured(void); ++ + int efi_loader_get_config_timeout_one_shot(usec_t *ret); + int efi_loader_update_entry_one_shot_cache(char **cache, struct stat *cache_stat); + diff --git a/SOURCES/0490-man-document-new-machine-id-fs-measurement-options.patch b/SOURCES/0490-man-document-new-machine-id-fs-measurement-options.patch new file mode 100644 index 0000000..724313a --- /dev/null +++ b/SOURCES/0490-man-document-new-machine-id-fs-measurement-options.patch @@ -0,0 +1,160 @@ +From b7c36073f9a645967feba035e21468976b567adb Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 17 Oct 2022 15:20:53 +0200 +Subject: [PATCH] man: document new machine-id/fs measurement options + +(cherry picked from commit 2bd33c909c0cf02a2a794ac83d66e8b32879c25d) + +Related: RHEL-16182 +--- + man/rules/meson.build | 5 ++- + man/systemd-pcrphase.service.xml | 57 +++++++++++++++++++++++++++----- + man/systemd.mount.xml | 14 ++++++++ + 3 files changed, 67 insertions(+), 9 deletions(-) + +diff --git a/man/rules/meson.build b/man/rules/meson.build +index c7045840f2..65a16b1e2a 100644 +--- a/man/rules/meson.build ++++ b/man/rules/meson.build +@@ -971,7 +971,10 @@ manpages = [ + ['systemd-path', '1', [], ''], + ['systemd-pcrphase.service', + '8', +- ['systemd-pcrphase', ++ ['systemd-pcrfs-root.service', ++ 'systemd-pcrfs@.service', ++ 'systemd-pcrmachine.service', ++ 'systemd-pcrphase', + 'systemd-pcrphase-initrd.service', + 'systemd-pcrphase-sysinit.service'], + 'HAVE_GNU_EFI'], +diff --git a/man/systemd-pcrphase.service.xml b/man/systemd-pcrphase.service.xml +index 9b7cc80b3a..95b0e05269 100644 +--- a/man/systemd-pcrphase.service.xml ++++ b/man/systemd-pcrphase.service.xml +@@ -20,15 +20,21 @@ + systemd-pcrphase.service + systemd-pcrphase-sysinit.service + systemd-pcrphase-initrd.service ++ systemd-pcrmachine.service ++ systemd-pcrfs-root.service ++ systemd-pcrfs@.service + systemd-pcrphase +- Measure boot phase into TPM2 PCR 11 ++ Measure boot phase into TPM2 PCR 11, machine ID and file system identity into PCR 15 + + + + systemd-pcrphase.service + systemd-pcrphase-sysinit.service + systemd-pcrphase-initrd.service +- /usr/lib/systemd/system-pcrphase STRING ++ systemd-pcrmachine.service ++ systemd-pcrfs-root.service ++ systemd-pcrfs@.service ++ /usr/lib/systemd/system-pcrphase STRING + + + +@@ -39,13 +45,23 @@ + systemd-pcrphase-initrd.service are system services that measure specific strings + into TPM2 PCR 11 during boot at various milestones of the boot process. + ++ systemd-pcrmachine.service is a system service that measures the machine ID ++ (see machine-id5) into ++ PCR 15. ++ ++ systemd-pcrfs-root.service and systemd-pcrfs@.service are ++ services that measure file system identity information (i.e. mount point, file system type, label and ++ UUID, partition label and UUID) into PCR 15. systemd-pcrfs-root.service does so for ++ the root file system, systemd-pcrfs@.service is a template unit that measures the ++ file system indicated by its instance identifier instead. ++ + These services require + systemd-stub7 to be +- used in a unified kernel image (UKI) setup. They execute no operation when invoked when the stub has not +- been used to invoke the kernel. The stub will measure the invoked kernel and associated vendor resources +- into PCR 11 before handing control to it; once userspace is invoked these services then will extend +- certain literal strings indicating various phases of the boot process into TPM2 PCR 11. During a regular +- boot process the following strings are extended into PCR 11. ++ used in a unified kernel image (UKI). They execute no operation when the stub has not been used to invoke ++ the kernel. The stub will measure the invoked kernel and associated vendor resources into PCR 11 before ++ handing control to it; once userspace is invoked these services then will extend TPM2 PCR 11 with certain ++ literal strings indicating phases of the boot process. During a regular boot process PCR 11 is extended ++ with the following strings: + + + enter-initrd is extended into PCR 11 early when the initrd +@@ -104,6 +120,14 @@ + Use + systemd-measure1 to + pre-calculate expected PCR 11 values for specific boot phases (via the switch). ++ ++ systemd-pcrfs-root.service and systemd-pcrfs@.service are ++ automatically pulled into the initial transaction by ++ systemd-gpt-generator8 ++ for the root and /var/ file ++ systems. systemd-fstab-generator8 ++ will do this for all mounts with the mount option in ++ /etc/fstab. + + + +@@ -139,6 +163,21 @@ + TPM2 device will cause the invocation to fail. + + ++ ++ ++ ++ Instead of measuring a word specified on the command line into PCR 11, measure the ++ host's machine ID into PCR 15. ++ ++ ++ ++ ++ ++ Instead of measuring a word specified on the command line into PCR 11, measure ++ identity information of the specified file system into PCR 15. The parameter must be the path to the ++ established mount point of the file system to measure. ++ ++ + + + +@@ -150,7 +189,9 @@ + + systemd1, + systemd-stub7, +- systemd-measure1 ++ systemd-measure1, ++ systemd-gpt-generator8, ++ systemd-fstab-generator8 + + + +diff --git a/man/systemd.mount.xml b/man/systemd.mount.xml +index 773ca04cd6..3dbc623f44 100644 +--- a/man/systemd.mount.xml ++++ b/man/systemd.mount.xml +@@ -366,6 +366,20 @@ + Options= setting in a unit file. + + ++ ++ ++ ++ Measures file system identity information (mount point, type, label, UUID, partition ++ label, partition UUID) into PCR 15 after the file system has been mounted. This ensures the ++ systemd-pcrfs@.service8 ++ or systemd-pcrfs-root.service services are pulled in by the mount unit. ++ ++ Note that this option can only be used in /etc/fstab, and will be ignored ++ when part of the Options= setting in a unit file. It is also implied for the root ++ and /usr/ partitions dicovered by ++ systemd-gpt-auto-generator8. ++ ++ + + + diff --git a/SOURCES/0491-test-add-simple-integration-test-for-checking-PCR-ex.patch b/SOURCES/0491-test-add-simple-integration-test-for-checking-PCR-ex.patch new file mode 100644 index 0000000..166f112 --- /dev/null +++ b/SOURCES/0491-test-add-simple-integration-test-for-checking-PCR-ex.patch @@ -0,0 +1,67 @@ +From 994451b5e3010e2b12c01522bed9cc246304696e Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 16 Dec 2022 16:25:34 +0100 +Subject: [PATCH] test: add simple integration test for checking PCR extension + works as it should + +(cherry picked from commit f44ed151c6c203f01a9fe8623b282ecd4ef2e0a9) + +Related: RHEL-16182 +--- + test/TEST-70-TPM2/test.sh | 1 + + test/units/testsuite-70.sh | 30 ++++++++++++++++++++++++++++++ + 2 files changed, 31 insertions(+) + +diff --git a/test/TEST-70-TPM2/test.sh b/test/TEST-70-TPM2/test.sh +index 7c19821ad2..f448a4a5f1 100755 +--- a/test/TEST-70-TPM2/test.sh ++++ b/test/TEST-70-TPM2/test.sh +@@ -20,6 +20,7 @@ test_append_files() { + install_dmevent + generate_module_dependencies + inst_binary tpm2_pcrextend ++ inst_binary tpm2_pcrread + inst_binary openssl + } + +diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh +index 89cd2a3f82..3b4d66b686 100755 +--- a/test/units/testsuite-70.sh ++++ b/test/units/testsuite-70.sh +@@ -155,6 +155,36 @@ else + echo "/usr/lib/systemd/systemd-measure or PCR sysfs files not found, skipping signed PCR policy test case" + fi + ++if [ -e /usr/lib/systemd/systemd-pcrphase ] && \ ++ [ -f /sys/class/tpm/tpm0/pcr-sha256/11 ]; then ++ ++ # Let's measure the machine ID ++ tpm2_pcrread sha256:15 -Q -o /tmp/oldpcr15 ++ mv /etc/machine-id /etc/machine-id.save ++ echo 994013bf23864ee7992eab39a96dd3bb >/etc/machine-id ++ SYSTEMD_FORCE_MEASURE=1 /usr/lib/systemd/systemd-pcrphase --machine-id ++ mv /etc/machine-id.save /etc/machine-id ++ tpm2_pcrread sha256:15 -Q -o /tmp/newpcr15 ++ ++ # And check it matches expectations ++ ( cat /tmp/oldpcr15 ; ++ echo -n "machine-id:994013bf23864ee7992eab39a96dd3bb" | openssl dgst -binary -sha256 ) | openssl dgst -binary -sha256 | cmp - /tmp/newpcr15 ++ ++ rm /tmp/oldpcr15 /tmp/newpcr15 ++ ++ # And similar for the boot phase measurement into PCR 11 ++ tpm2_pcrread sha256:11 -Q -o /tmp/oldpcr11 ++ SYSTEMD_FORCE_MEASURE=1 /usr/lib/systemd/systemd-pcrphase foobar ++ tpm2_pcrread sha256:11 -Q -o /tmp/newpcr11 ++ ++ ( cat /tmp/oldpcr11 ; ++ echo -n "foobar" | openssl dgst -binary -sha256 ) | openssl dgst -binary -sha256 | cmp - /tmp/newpcr11 ++ ++ rm /tmp/oldpcr11 /tmp/newpcr11 ++else ++ echo "/usr/lib/systemd/systemd-pcrphase or PCR sysfs files not found, skipping PCR extension test case" ++fi ++ + echo OK >/testok + + exit 0 diff --git a/SOURCES/0492-update-TODO.patch b/SOURCES/0492-update-TODO.patch new file mode 100644 index 0000000..8f3f7c0 --- /dev/null +++ b/SOURCES/0492-update-TODO.patch @@ -0,0 +1,28 @@ +From 48d3b9b2ee68a41e41ccb493e24c0283d752e4f8 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 14 Oct 2022 21:21:46 +0200 +Subject: [PATCH] update TODO + +(cherry picked from commit a67a50e8f4a3d19713fe9b84653616fcba5ae14c) + +Related: RHEL-16182 +--- + TODO | 5 ++--- + 1 file changed, 2 insertions(+), 3 deletions(-) + +diff --git a/TODO b/TODO +index 8c67f93f35..aa3f1c596c 100644 +--- a/TODO ++++ b/TODO +@@ -354,9 +354,8 @@ Features: + and via the time window TPM logic invalidated if node doesn't keep itself + updated, or becomes corrupted in some way. + +-* Always measure the LUKS rootfs volume key into PCR 15, and derive the machine +- ID from it securely. This would then allow us to bind secrets a specific +- system securely. ++* in the initrd, once the rootfs encryption key has been measured to PCR 15, ++ derive default machine ID to use from it, and pass it to host PID 1. + + * nspawn: maybe allow TPM passthrough, backed by swtpm, and measure --image= + hash into its PCR 11, so that nspawn instances can be TPM enabled, and diff --git a/SOURCES/0493-cryptsetup-retry-TPM2-unseal-operation-if-it-fails-w.patch b/SOURCES/0493-cryptsetup-retry-TPM2-unseal-operation-if-it-fails-w.patch new file mode 100644 index 0000000..e725535 --- /dev/null +++ b/SOURCES/0493-cryptsetup-retry-TPM2-unseal-operation-if-it-fails-w.patch @@ -0,0 +1,139 @@ +From 7f8a43eff0d800f21e9f873010637d08da13da67 Mon Sep 17 00:00:00 2001 +From: Antonio Alvarez Feijoo +Date: Wed, 7 Dec 2022 16:52:27 +0100 +Subject: [PATCH] cryptsetup: retry TPM2 unseal operation if it fails with + TPM2_RC_PCR_CHANGED +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Quoting "Trusted Platform Module Library - Part 3: Commands (Rev. 01.59)": + +"pcrUpdateCounter – this parameter is updated by TPM2_PolicyPCR(). This value +may only be set once during a policy. Each time TPM2_PolicyPCR() executes, it +checks to see if policySession->pcrUpdateCounter has its default state, +indicating that this is the first TPM2_PolicyPCR(). If it has its default value, +then policySession->pcrUpdateCounter is set to the current value of +pcrUpdateCounter. If policySession->pcrUpdateCounter does not have its default +value and its value is not the same as pcrUpdateCounter, the TPM shall return +TPM_RC_PCR_CHANGED. + +If this parameter and pcrUpdateCounter are not the same, it indicates that PCR +have changed since checked by the previous TPM2_PolicyPCR(). Since they have +changed, the previous PCR validation is no longer valid." + +The TPM will return TPM_RC_PCR_CHANGED if any PCR value changes (no matter +which) between validating the PCRs binded to the enrollment and unsealing the +HMAC key, so this patch adds a retry mechanism in this case. + +Fixes #24906 + +(cherry picked from commit 0254e4d66af7aa893b31b2326335ded5dde48b51) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 81 ++++++++++++++++++++++++------------------ + 1 file changed, 46 insertions(+), 35 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index aca7f22e54..d1a4e9cd11 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1608,6 +1608,8 @@ finish: + return r; + } + ++#define RETRY_UNSEAL_MAX 30u ++ + int tpm2_unseal(const char *device, + uint32_t hash_pcr_mask, + uint16_t pcr_bank, +@@ -1719,44 +1721,53 @@ int tpm2_unseal(const char *device, + if (r < 0) + goto finish; + +- r = tpm2_make_policy_session( +- c.esys_context, +- primary, +- hmac_session, +- TPM2_SE_POLICY, +- hash_pcr_mask, +- pcr_bank, +- pubkey, pubkey_size, +- pubkey_pcr_mask, +- signature, +- !!pin, +- &session, +- &policy_digest, +- /* ret_pcr_bank= */ NULL); +- if (r < 0) +- goto finish; ++ for (unsigned i = RETRY_UNSEAL_MAX;; i--) { ++ r = tpm2_make_policy_session( ++ c.esys_context, ++ primary, ++ hmac_session, ++ TPM2_SE_POLICY, ++ hash_pcr_mask, ++ pcr_bank, ++ pubkey, pubkey_size, ++ pubkey_pcr_mask, ++ signature, ++ !!pin, ++ &session, ++ &policy_digest, ++ /* ret_pcr_bank= */ NULL); ++ if (r < 0) ++ goto finish; + +- /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not +- * wait until the TPM2 tells us to go away. */ +- if (known_policy_hash_size > 0 && +- memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0) +- return log_error_errno(SYNTHETIC_ERRNO(EPERM), +- "Current policy digest does not match stored policy digest, cancelling " +- "TPM2 authentication attempt."); ++ /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not ++ * wait until the TPM2 tells us to go away. */ ++ if (known_policy_hash_size > 0 && ++ memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0) ++ return log_error_errno(SYNTHETIC_ERRNO(EPERM), ++ "Current policy digest does not match stored policy digest, cancelling " ++ "TPM2 authentication attempt."); + +- log_debug("Unsealing HMAC key."); ++ log_debug("Unsealing HMAC key."); + +- rc = sym_Esys_Unseal( +- c.esys_context, +- hmac_key, +- session, +- hmac_session, /* use HMAC session to enable parameter encryption */ +- ESYS_TR_NONE, +- &unsealed); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; ++ rc = sym_Esys_Unseal( ++ c.esys_context, ++ hmac_key, ++ session, ++ hmac_session, /* use HMAC session to enable parameter encryption */ ++ ESYS_TR_NONE, ++ &unsealed); ++ if (rc == TPM2_RC_PCR_CHANGED && i > 0) { ++ log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i); ++ session = tpm2_flush_context_verbose(c.esys_context, session); ++ continue; ++ } ++ if (rc != TSS2_RC_SUCCESS) { ++ r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); ++ goto finish; ++ } ++ ++ break; + } + + secret = memdup(unsealed->buffer, unsealed->size); diff --git a/SOURCES/0494-boot-Simplify-object-erasure.patch b/SOURCES/0494-boot-Simplify-object-erasure.patch new file mode 100644 index 0000000..e0a9cc4 --- /dev/null +++ b/SOURCES/0494-boot-Simplify-object-erasure.patch @@ -0,0 +1,151 @@ +From ffff09335760efc655faf093b6fbb364cfae4ad7 Mon Sep 17 00:00:00 2001 +From: Jan Janssen +Date: Sat, 7 Jan 2023 22:16:52 +0100 +Subject: [PATCH] boot: Simplify object erasure + +This erase_obj() machinery looks like voodoo and creates an awful lot of +noise as soon as we get back to building with -O0. We can do this in a +more simple way by introducing a struct that holds the information we +need on cleanup. When building with optimization enabled, all this gets +inlined and the eraser vanishes. + +(cherry picked from commit 3f92dc2fd4070b213e6bc85263a9bef06ec9a486) + +Related: RHEL-16182 +--- + src/basic/memory-util.c | 18 ---------- + src/basic/memory-util.h | 12 +------ + src/boot/efi/random-seed.c | 1 + + src/fundamental/memory-util-fundamental.h | 42 +++++++++++++++++++++++ + src/fundamental/meson.build | 1 + + 5 files changed, 45 insertions(+), 29 deletions(-) + create mode 100644 src/fundamental/memory-util-fundamental.h + +diff --git a/src/basic/memory-util.c b/src/basic/memory-util.c +index 2983762117..c4f54c7b4e 100644 +--- a/src/basic/memory-util.c ++++ b/src/basic/memory-util.c +@@ -38,21 +38,3 @@ bool memeqbyte(uint8_t byte, const void *data, size_t length) { + /* Now we know first 16 bytes match, memcmp() with self. */ + return memcmp(data, p + 16, length) == 0; + } +- +-#if !HAVE_EXPLICIT_BZERO +-/* +- * The pointer to memset() is volatile so that compiler must de-reference the pointer and can't assume that +- * it points to any function in particular (such as memset(), which it then might further "optimize"). This +- * approach is inspired by openssl's crypto/mem_clr.c. +- */ +-typedef void *(*memset_t)(void *,int,size_t); +- +-static volatile memset_t memset_func = memset; +- +-void* explicit_bzero_safe(void *p, size_t l) { +- if (l > 0) +- memset_func(p, '\0', l); +- +- return p; +-} +-#endif +diff --git a/src/basic/memory-util.h b/src/basic/memory-util.h +index eea9c0e92f..d26a0918e1 100644 +--- a/src/basic/memory-util.h ++++ b/src/basic/memory-util.h +@@ -9,6 +9,7 @@ + + #include "alloc-util.h" + #include "macro.h" ++#include "memory-util-fundamental.h" + + size_t page_size(void) _pure_; + #define PAGE_ALIGN(l) ALIGN_TO((l), page_size()) +@@ -91,17 +92,6 @@ static inline void *mempmem_safe(const void *haystack, size_t haystacklen, const + return (uint8_t*) p + needlelen; + } + +-#if HAVE_EXPLICIT_BZERO +-static inline void* explicit_bzero_safe(void *p, size_t l) { +- if (l > 0) +- explicit_bzero(p, l); +- +- return p; +-} +-#else +-void *explicit_bzero_safe(void *p, size_t l); +-#endif +- + static inline void* erase_and_free(void *p) { + size_t l; + +diff --git a/src/boot/efi/random-seed.c b/src/boot/efi/random-seed.c +index aea4f7e532..3c9df5bb54 100644 +--- a/src/boot/efi/random-seed.c ++++ b/src/boot/efi/random-seed.c +@@ -3,6 +3,7 @@ + #include + #include + ++#include "memory-util-fundamental.h" + #include "missing_efi.h" + #include "random-seed.h" + #include "secure-boot.h" +diff --git a/src/fundamental/memory-util-fundamental.h b/src/fundamental/memory-util-fundamental.h +new file mode 100644 +index 0000000000..9015300ae8 +--- /dev/null ++++ b/src/fundamental/memory-util-fundamental.h +@@ -0,0 +1,42 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include ++ ++#ifdef SD_BOOT ++# include "efi-string.h" ++#else ++# include ++#endif ++ ++#include "macro-fundamental.h" ++ ++#if defined(HAVE_EXPLICIT_BZERO) ++static inline void *explicit_bzero_safe(void *p, size_t l) { ++ if (p && l > 0) ++ explicit_bzero(p, l); ++ ++ return p; ++} ++#else ++static inline void *explicit_bzero_safe(void *p, size_t l) { ++ if (p && l > 0) { ++ memset(p, 0, l); ++ __asm__ __volatile__("" : : "r"(p) : "memory"); ++ } ++ return p; ++} ++#endif ++ ++struct VarEraser { ++ void *p; ++ size_t size; ++}; ++ ++static inline void erase_var(struct VarEraser *e) { ++ explicit_bzero_safe(e->p, e->size); ++} ++ ++/* Mark var to be erased when leaving scope. */ ++#define CLEANUP_ERASE(var) \ ++ _cleanup_(erase_var) _unused_ struct VarEraser CONCATENATE(_eraser_, UNIQ) = { .p = &var, .size = sizeof(var) } +diff --git a/src/fundamental/meson.build b/src/fundamental/meson.build +index 3810d6b456..4b8e32337d 100644 +--- a/src/fundamental/meson.build ++++ b/src/fundamental/meson.build +@@ -6,6 +6,7 @@ fundamental_headers = files( + 'bootspec-fundamental.h', + 'efivars-fundamental.h', + 'macro-fundamental.h', ++ 'memory-util-fundamental.h', + 'sha256.h', + 'string-util-fundamental.h', + 'tpm-pcr.h', diff --git a/SOURCES/0495-tree-wide-use-CLEANUP_ERASE-at-various-places.patch b/SOURCES/0495-tree-wide-use-CLEANUP_ERASE-at-various-places.patch new file mode 100644 index 0000000..e4b5130 --- /dev/null +++ b/SOURCES/0495-tree-wide-use-CLEANUP_ERASE-at-various-places.patch @@ -0,0 +1,494 @@ +From a86b58ed1419b9af8a486dcaf95608eb07241965 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 10 Jan 2023 12:39:58 +0100 +Subject: [PATCH] tree-wide: use CLEANUP_ERASE() at various places + +Let's use this new macro wherever it makes sense, as it allows us to +shorten or clean-up paths, and makes it less likely to miss a return +path. + +(cherry picked from commit 692597c84395ad2b3f8e221bb1eca55a9dfc544f) + +Related: RHEL-16182 +--- + src/home/homework-fscrypt.c | 58 ++++++++++-------------------- + src/shared/ask-password-api.c | 66 +++++++++++++---------------------- + src/shared/creds-util.c | 24 ++++++------- + src/shared/ethtool-util.c | 11 +++--- + src/shared/tpm2-util.c | 15 ++++---- + 5 files changed, 67 insertions(+), 107 deletions(-) + +diff --git a/src/home/homework-fscrypt.c b/src/home/homework-fscrypt.c +index bd32393d93..455a4c88fc 100644 +--- a/src/home/homework-fscrypt.c ++++ b/src/home/homework-fscrypt.c +@@ -58,10 +58,10 @@ static int fscrypt_upload_volume_key( + }; + memcpy(key.raw, volume_key, volume_key_size); + ++ CLEANUP_ERASE(key); ++ + /* Upload to the kernel */ + serial = add_key("logon", description, &key, sizeof(key), where); +- explicit_bzero_safe(&key, sizeof(key)); +- + if (serial < 0) + return log_error_errno(errno, "Failed to install master key in keyring: %m"); + +@@ -124,20 +124,18 @@ static int fscrypt_slot_try_one( + * resulting hash. + */ + ++ CLEANUP_ERASE(derived); ++ + if (PKCS5_PBKDF2_HMAC( + password, strlen(password), + salt, salt_size, + 0xFFFF, EVP_sha512(), +- sizeof(derived), derived) != 1) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed"); +- goto finish; +- } ++ sizeof(derived), derived) != 1) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed"); + + context = EVP_CIPHER_CTX_new(); +- if (!context) { +- r = log_oom(); +- goto finish; +- } ++ if (!context) ++ return log_oom(); + + /* We use AES256 in counter mode */ + assert_se(cc = EVP_aes_256_ctr()); +@@ -145,13 +143,8 @@ static int fscrypt_slot_try_one( + /* We only use the first half of the derived key */ + assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc)); + +- if (EVP_DecryptInit_ex(context, cc, NULL, derived, NULL) != 1) { +- r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context."); +- goto finish; +- } +- +- /* Flush out the derived key now, we don't need it anymore */ +- explicit_bzero_safe(derived, sizeof(derived)); ++ if (EVP_DecryptInit_ex(context, cc, NULL, derived, NULL) != 1) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize decryption context."); + + decrypted_size = encrypted_size + EVP_CIPHER_key_length(cc) * 2; + decrypted = malloc(decrypted_size); +@@ -184,10 +177,6 @@ static int fscrypt_slot_try_one( + *ret_decrypted_size = decrypted_size; + + return 0; +- +-finish: +- explicit_bzero_safe(derived, sizeof(derived)); +- return r; + } + + static int fscrypt_slot_try_many( +@@ -414,20 +403,18 @@ static int fscrypt_slot_set( + if (r < 0) + return log_error_errno(r, "Failed to generate salt: %m"); + ++ CLEANUP_ERASE(derived); ++ + if (PKCS5_PBKDF2_HMAC( + password, strlen(password), + salt, sizeof(salt), + 0xFFFF, EVP_sha512(), +- sizeof(derived), derived) != 1) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed"); +- goto finish; +- } ++ sizeof(derived), derived) != 1) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PBKDF2 failed"); + + context = EVP_CIPHER_CTX_new(); +- if (!context) { +- r = log_oom(); +- goto finish; +- } ++ if (!context) ++ return log_oom(); + + /* We use AES256 in counter mode */ + cc = EVP_aes_256_ctr(); +@@ -435,13 +422,8 @@ static int fscrypt_slot_set( + /* We only use the first half of the derived key */ + assert(sizeof(derived) >= (size_t) EVP_CIPHER_key_length(cc)); + +- if (EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1) { +- r = log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context."); +- goto finish; +- } +- +- /* Flush out the derived key now, we don't need it anymore */ +- explicit_bzero_safe(derived, sizeof(derived)); ++ if (EVP_EncryptInit_ex(context, cc, NULL, derived, NULL) != 1) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize encryption context."); + + encrypted_size = volume_key_size + EVP_CIPHER_key_length(cc) * 2; + encrypted = malloc(encrypted_size); +@@ -478,10 +460,6 @@ static int fscrypt_slot_set( + log_info("Written key slot %s.", label); + + return 0; +- +-finish: +- explicit_bzero_safe(derived, sizeof(derived)); +- return r; + } + + int home_create_fscrypt( +diff --git a/src/shared/ask-password-api.c b/src/shared/ask-password-api.c +index 871af2ec99..5f05271caa 100644 +--- a/src/shared/ask-password-api.c ++++ b/src/shared/ask-password-api.c +@@ -253,6 +253,8 @@ int ask_password_plymouth( + if (r < 0) + return r; + ++ CLEANUP_ERASE(buffer); ++ + pollfd[POLL_SOCKET].fd = fd; + pollfd[POLL_SOCKET].events = POLLIN; + pollfd[POLL_INOTIFY].fd = notify; +@@ -266,20 +268,16 @@ int ask_password_plymouth( + else + timeout = USEC_INFINITY; + +- if (flag_file && access(flag_file, F_OK) < 0) { +- r = -errno; +- goto finish; +- } ++ if (flag_file && access(flag_file, F_OK) < 0) ++ return -errno; + + r = ppoll_usec(pollfd, notify >= 0 ? 2 : 1, timeout); + if (r == -EINTR) + continue; + if (r < 0) +- goto finish; +- if (r == 0) { +- r = -ETIME; +- goto finish; +- } ++ return r; ++ if (r == 0) ++ return -ETIME; + + if (notify >= 0 && pollfd[POLL_INOTIFY].revents != 0) + (void) flush_fd(notify); +@@ -292,13 +290,10 @@ int ask_password_plymouth( + if (ERRNO_IS_TRANSIENT(errno)) + continue; + +- r = -errno; +- goto finish; +- } +- if (k == 0) { +- r = -EIO; +- goto finish; ++ return -errno; + } ++ if (k == 0) ++ return -EIO; + + p += k; + +@@ -310,14 +305,12 @@ int ask_password_plymouth( + * with a normal password request */ + packet = mfree(packet); + +- if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) { +- r = -ENOMEM; +- goto finish; +- } ++ if (asprintf(&packet, "*\002%c%s%n", (int) (strlen(message) + 1), message, &n) < 0) ++ return -ENOMEM; + + r = loop_write(fd, packet, n+1, true); + if (r < 0) +- goto finish; ++ return r; + + flags &= ~ASK_PASSWORD_ACCEPT_CACHED; + p = 0; +@@ -325,8 +318,7 @@ int ask_password_plymouth( + } + + /* No password, because UI not shown */ +- r = -ENOENT; +- goto finish; ++ return -ENOENT; + + } else if (IN_SET(buffer[0], 2, 9)) { + uint32_t size; +@@ -338,35 +330,25 @@ int ask_password_plymouth( + + memcpy(&size, buffer+1, sizeof(size)); + size = le32toh(size); +- if (size + 5 > sizeof(buffer)) { +- r = -EIO; +- goto finish; +- } ++ if (size + 5 > sizeof(buffer)) ++ return -EIO; + + if (p-5 < size) + continue; + + l = strv_parse_nulstr(buffer + 5, size); +- if (!l) { +- r = -ENOMEM; +- goto finish; +- } ++ if (!l) ++ return -ENOMEM; + + *ret = l; + break; + +- } else { ++ } else + /* Unknown packet */ +- r = -EIO; +- goto finish; +- } ++ return -EIO; + } + +- r = 0; +- +-finish: +- explicit_bzero_safe(buffer, sizeof(buffer)); +- return r; ++ return 0; + } + + #define NO_ECHO "(no echo) " +@@ -428,6 +410,8 @@ int ask_password_tty( + return -errno; + } + ++ CLEANUP_ERASE(passphrase); ++ + /* If the caller didn't specify a TTY, then use the controlling tty, if we can. */ + if (ttyfd < 0) + ttyfd = cttyfd = open("/dev/tty", O_RDWR|O_NOCTTY|O_CLOEXEC); +@@ -631,7 +615,6 @@ int ask_password_tty( + } + + x = strndup(passphrase, p); +- explicit_bzero_safe(passphrase, sizeof(passphrase)); + if (!x) { + r = -ENOMEM; + goto finish; +@@ -891,6 +874,8 @@ int ask_password_agent( + goto finish; + } + ++ CLEANUP_ERASE(passphrase); ++ + cmsg_close_all(&msghdr); + + if (n == 0) { +@@ -915,7 +900,6 @@ int ask_password_agent( + l = strv_new(""); + else + l = strv_parse_nulstr(passphrase+1, n-1); +- explicit_bzero_safe(passphrase, n); + if (!l) { + r = -ENOMEM; + goto finish; +diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c +index ecf90e2084..9ac0320c58 100644 +--- a/src/shared/creds-util.c ++++ b/src/shared/creds-util.c +@@ -165,7 +165,6 @@ static int make_credential_host_secret( + void **ret_data, + size_t *ret_size) { + +- struct credential_host_secret_format buf; + _cleanup_free_ char *t = NULL; + _cleanup_close_ int fd = -1; + int r; +@@ -189,21 +188,23 @@ static int make_credential_host_secret( + if (r < 0) + log_debug_errno(r, "Failed to set file attributes for secrets file, ignoring: %m"); + +- buf = (struct credential_host_secret_format) { ++ struct credential_host_secret_format buf = { + .machine_id = machine_id, + }; + ++ CLEANUP_ERASE(buf); ++ + r = crypto_random_bytes(buf.data, sizeof(buf.data)); + if (r < 0) +- goto finish; ++ goto fail; + + r = loop_write(fd, &buf, sizeof(buf), false); + if (r < 0) +- goto finish; ++ goto fail; + + if (fsync(fd) < 0) { + r = -errno; +- goto finish; ++ goto fail; + } + + warn_not_encrypted(fd, flags, dirname, fn); +@@ -211,17 +212,17 @@ static int make_credential_host_secret( + if (t) { + r = rename_noreplace(dfd, t, dfd, fn); + if (r < 0) +- goto finish; ++ goto fail; + + t = mfree(t); + } else if (linkat(fd, "", dfd, fn, AT_EMPTY_PATH) < 0) { + r = -errno; +- goto finish; ++ goto fail; + } + + if (fsync(dfd) < 0) { + r = -errno; +- goto finish; ++ goto fail; + } + + if (ret_data) { +@@ -230,7 +231,7 @@ static int make_credential_host_secret( + copy = memdup(buf.data, sizeof(buf.data)); + if (!copy) { + r = -ENOMEM; +- goto finish; ++ goto fail; + } + + *ret_data = copy; +@@ -239,13 +240,12 @@ static int make_credential_host_secret( + if (ret_size) + *ret_size = sizeof(buf.data); + +- r = 0; ++ return 0; + +-finish: ++fail: + if (t && unlinkat(dfd, t, 0) < 0) + log_debug_errno(errno, "Failed to remove temporary credential key: %m"); + +- explicit_bzero_safe(&buf, sizeof(buf)); + return r; + } + +diff --git a/src/shared/ethtool-util.c b/src/shared/ethtool-util.c +index e39b2f754b..1900537917 100644 +--- a/src/shared/ethtool-util.c ++++ b/src/shared/ethtool-util.c +@@ -434,6 +434,8 @@ int ethtool_set_wol( + + strscpy(ifr.ifr_name, sizeof(ifr.ifr_name), ifname); + ++ CLEANUP_ERASE(ecmd); ++ + if (ioctl(*ethtool_fd, SIOCETHTOOL, &ifr) < 0) + return -errno; + +@@ -466,16 +468,11 @@ int ethtool_set_wol( + need_update = true; + } + +- if (!need_update) { +- explicit_bzero_safe(&ecmd, sizeof(ecmd)); ++ if (!need_update) + return 0; +- } + + ecmd.cmd = ETHTOOL_SWOL; +- r = RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr)); +- +- explicit_bzero_safe(&ecmd, sizeof(ecmd)); +- return r; ++ return RET_NERRNO(ioctl(*ethtool_fd, SIOCETHTOOL, &ifr)); + } + + int ethtool_set_nic_buffer_size(int *ethtool_fd, const char *ifname, const netdev_ring_param *ring) { +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index d1a4e9cd11..7e98ec851b 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -778,13 +778,14 @@ static void hash_pin(const char *pin, size_t len, TPM2B_AUTH *auth) { + + assert(auth); + assert(pin); ++ + auth->size = SHA256_DIGEST_SIZE; + ++ CLEANUP_ERASE(hash); ++ + sha256_init_ctx(&hash); + sha256_process_bytes(pin, len, &hash); + sha256_finish_ctx(&hash, auth->buffer); +- +- explicit_bzero_safe(&hash, sizeof(hash)); + } + + static int tpm2_make_encryption_session( +@@ -816,11 +817,11 @@ static int tpm2_make_encryption_session( + if (pin) { + TPM2B_AUTH auth = {}; + ++ CLEANUP_ERASE(auth); ++ + hash_pin(pin, strlen(pin), &auth); + + rc = sym_Esys_TR_SetAuth(c, bind_key, &auth); +- /* ESAPI knows about it, so clear it from our memory */ +- explicit_bzero_safe(&auth, sizeof(auth)); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno( + SYNTHETIC_ERRNO(ENOTRECOVERABLE), +@@ -1412,8 +1413,8 @@ int tpm2_seal(const char *device, + static const TPML_PCR_SELECTION creation_pcr = {}; + _cleanup_(erase_and_freep) void *secret = NULL; + _cleanup_free_ void *blob = NULL, *hash = NULL; +- TPM2B_SENSITIVE_CREATE hmac_sensitive; + ESYS_TR primary = ESYS_TR_NONE, session = ESYS_TR_NONE; ++ TPM2B_SENSITIVE_CREATE hmac_sensitive; + TPMI_ALG_PUBLIC primary_alg; + TPM2B_PUBLIC hmac_template; + TPMI_ALG_HASH pcr_bank; +@@ -1453,6 +1454,8 @@ int tpm2_seal(const char *device, + + start = now(CLOCK_MONOTONIC); + ++ CLEANUP_ERASE(hmac_sensitive); ++ + r = tpm2_context_init(device, &c); + if (r < 0) + return r; +@@ -1541,7 +1544,6 @@ int tpm2_seal(const char *device, + } + + secret = memdup(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size); +- explicit_bzero_safe(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size); + if (!secret) { + r = log_oom(); + goto finish; +@@ -1602,7 +1604,6 @@ int tpm2_seal(const char *device, + r = 0; + + finish: +- explicit_bzero_safe(&hmac_sensitive, sizeof(hmac_sensitive)); + primary = tpm2_flush_context_verbose(c.esys_context, primary); + session = tpm2_flush_context_verbose(c.esys_context, session); + return r; diff --git a/SOURCES/0496-dlfcn-add-new-safe_dclose-helper.patch b/SOURCES/0496-dlfcn-add-new-safe_dclose-helper.patch new file mode 100644 index 0000000..de9941d --- /dev/null +++ b/SOURCES/0496-dlfcn-add-new-safe_dclose-helper.patch @@ -0,0 +1,49 @@ +From fa2bce7c0447bd836fc8c2020ac714e4a47b9900 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Wed, 25 Jan 2023 11:54:44 +0100 +Subject: [PATCH] dlfcn: add new safe_dclose() helper + +Let's allow destructing loaded module handles in our usual way that is +fine with NULL handles, and also returns the NULL handle again. + +(cherry picked from commit f2592ef0e113aef0e8e7141cab2b17521760b064) + +Related: RHEL-16182 +--- + src/shared/dlfcn-util.h | 8 ++++++++ + src/shared/tpm2-util.c | 6 +----- + 2 files changed, 9 insertions(+), 5 deletions(-) + +diff --git a/src/shared/dlfcn-util.h b/src/shared/dlfcn-util.h +index d786d035d7..7bd5ff4595 100644 +--- a/src/shared/dlfcn-util.h ++++ b/src/shared/dlfcn-util.h +@@ -20,3 +20,11 @@ int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_l + * "foobar" is loaded into a variable "sym_foobar". */ + #define DLSYM_ARG(arg) \ + &sym_##arg, STRINGIFY(arg) ++ ++static inline void *safe_dlclose(void *p) { ++ if (!p) ++ return NULL; ++ ++ assert_se(dlclose(p) == 0); ++ return NULL; ++} +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 7e98ec851b..278cdf3692 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -110,11 +110,7 @@ void tpm2_context_destroy(struct tpm2_context *c) { + sym_Esys_Finalize(&c->esys_context); + + c->tcti_context = mfree(c->tcti_context); +- +- if (c->tcti_dl) { +- dlclose(c->tcti_dl); +- c->tcti_dl = NULL; +- } ++ c->tcti_dl = safe_dlclose(c->tcti_dl); + } + + static inline void Esys_Finalize_wrapper(ESYS_CONTEXT **c) { diff --git a/SOURCES/0497-tpm2-rename-tpm2-alg-id-string-functions.patch b/SOURCES/0497-tpm2-rename-tpm2-alg-id-string-functions.patch new file mode 100644 index 0000000..2943191 --- /dev/null +++ b/SOURCES/0497-tpm2-rename-tpm2-alg-id-string-functions.patch @@ -0,0 +1,237 @@ +From 73fd8e125a5c22cd341c056b774ab6cb2122b951 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 9 Dec 2022 17:20:24 -0500 +Subject: [PATCH] tpm2: rename tpm2 alg id<->string functions + +The 'pcr_bank' functions operate on hash algs, and are not specific to the PCR +banks, while the 'primary_alg' functions operate on asymmetric algs, and are +not specific to primary keys. + +(cherry picked from commit 7bfe0a48d9df6e9488aaec2eeb5bfec051681e40) + +Related: RHEL-16182 +--- + src/boot/measure.c | 2 +- + .../cryptsetup-token-systemd-tpm2.c | 8 ++-- + src/shared/creds-util.c | 4 +- + src/shared/tpm2-util.c | 44 +++++++++---------- + src/shared/tpm2-util.h | 8 ++-- + 5 files changed, 33 insertions(+), 33 deletions(-) + +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 84a7c357a4..8af3a337d6 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -837,7 +837,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + assert(sizeof(intermediate_digest.buffer) >= SHA256_DIGEST_SIZE); + sha256_direct(p->value, p->value_size, intermediate_digest.buffer); + +- int tpmalg = tpm2_pcr_bank_from_string(EVP_MD_name(p->md)); ++ int tpmalg = tpm2_hash_alg_from_string(EVP_MD_name(p->md)); + if (tpmalg < 0) { + log_error_errno(tpmalg, "Unsupported PCR bank"); + goto finish; +diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +index 1eb924529c..98bcaac4d8 100644 +--- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +@@ -220,10 +220,10 @@ _public_ void cryptsetup_token_dump( + return (void) crypt_log_debug_errno(cd, r, "Can not dump " TOKEN_NAME " content: %m"); + + crypt_log(cd, "\ttpm2-hash-pcrs: %s\n", strna(hash_pcrs_str)); +- crypt_log(cd, "\ttpm2-pcr-bank: %s\n", strna(tpm2_pcr_bank_to_string(pcr_bank))); ++ crypt_log(cd, "\ttpm2-pcr-bank: %s\n", strna(tpm2_hash_alg_to_string(pcr_bank))); + crypt_log(cd, "\ttpm2-pubkey:" CRYPT_DUMP_LINE_SEP "%s\n", pubkey_str); + crypt_log(cd, "\ttpm2-pubkey-pcrs: %s\n", strna(pubkey_pcrs_str)); +- crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_primary_alg_to_string(primary_alg))); ++ crypt_log(cd, "\ttpm2-primary-alg: %s\n", strna(tpm2_asym_alg_to_string(primary_alg))); + crypt_log(cd, "\ttpm2-blob: %s\n", blob_str); + crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str); + crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN)); +@@ -281,7 +281,7 @@ _public_ int cryptsetup_token_validate( + return 1; + } + +- if (tpm2_pcr_bank_from_string(json_variant_string(w)) < 0) { ++ if (tpm2_hash_alg_from_string(json_variant_string(w)) < 0) { + crypt_log_debug(cd, "TPM2 PCR bank invalid or not supported: %s.", json_variant_string(w)); + return 1; + } +@@ -298,7 +298,7 @@ _public_ int cryptsetup_token_validate( + return 1; + } + +- if (tpm2_primary_alg_from_string(json_variant_string(w)) < 0) { ++ if (tpm2_asym_alg_from_string(json_variant_string(w)) < 0) { + crypt_log_debug(cd, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w)); + return 1; + } +diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c +index 9ac0320c58..e9cafb8097 100644 +--- a/src/shared/creds-util.c ++++ b/src/shared/creds-util.c +@@ -944,9 +944,9 @@ int decrypt_credential_and_warn( + + if (!TPM2_PCR_MASK_VALID(t->pcr_mask)) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR mask out of range."); +- if (!tpm2_pcr_bank_to_string(le16toh(t->pcr_bank))) ++ if (!tpm2_hash_alg_to_string(le16toh(t->pcr_bank))) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 PCR bank invalid or not supported"); +- if (!tpm2_primary_alg_to_string(le16toh(t->primary_alg))) ++ if (!tpm2_asym_alg_to_string(le16toh(t->primary_alg))) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "TPM2 primary key algorithm invalid or not supported."); + if (le32toh(t->blob_size) > CREDENTIAL_FIELD_SIZE_MAX) + return log_error_errno(SYNTHETIC_ERRNO(EBADMSG), "Unexpected TPM2 blob size."); +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 278cdf3692..fe4d63b775 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -528,7 +528,7 @@ static int tpm2_bank_has24(const TPMS_PCR_SELECTION *selection) { + * TPM2 on a Client PC must have at least 24 PCRs. If this TPM has less, just skip over it. */ + if (selection->sizeofSelect < TPM2_PCRS_MAX/8) { + log_debug("Skipping TPM2 PCR bank %s with fewer than 24 PCRs.", +- strna(tpm2_pcr_bank_to_string(selection->hash))); ++ strna(tpm2_hash_alg_to_string(selection->hash))); + return false; + } + +@@ -545,7 +545,7 @@ static int tpm2_bank_has24(const TPMS_PCR_SELECTION *selection) { + + if (!valid) + log_debug("TPM2 PCR bank %s has fewer than 24 PCR bits enabled, ignoring.", +- strna(tpm2_pcr_bank_to_string(selection->hash))); ++ strna(tpm2_hash_alg_to_string(selection->hash))); + + return valid; + } +@@ -747,7 +747,7 @@ int tpm2_get_good_pcr_banks_strv( + const EVP_MD *implementation; + const char *salg; + +- salg = tpm2_pcr_bank_to_string(algs[i]); ++ salg = tpm2_hash_alg_to_string(algs[i]); + if (!salg) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure."); + +@@ -971,7 +971,7 @@ static int find_signature( + if (!json_variant_is_object(v)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Signature is not a JSON object."); + +- k = tpm2_pcr_bank_to_string(pcr_bank); ++ k = tpm2_hash_alg_to_string(pcr_bank); + if (!k) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Don't know PCR bank %" PRIu16, pcr_bank); + +@@ -1956,7 +1956,7 @@ int tpm2_extend_bytes( + if ((size_t) EVP_MD_size(implementation) > sizeof(values.digests[values.count].digest)) + return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2."); + +- id = tpm2_pcr_bank_from_string(EVP_MD_name(implementation)); ++ id = tpm2_hash_alg_from_string(EVP_MD_name(implementation)); + if (id < 0) + return log_error_errno(id, "Can't map hash name to TPM2."); + +@@ -2140,8 +2140,8 @@ int tpm2_make_luks2_json( + JSON_BUILD_PAIR("keyslots", JSON_BUILD_ARRAY(JSON_BUILD_STRING(keyslot_as_string))), + JSON_BUILD_PAIR("tpm2-blob", JSON_BUILD_BASE64(blob, blob_size)), + JSON_BUILD_PAIR("tpm2-pcrs", JSON_BUILD_VARIANT(hmj)), +- JSON_BUILD_PAIR_CONDITION(!!tpm2_pcr_bank_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_pcr_bank_to_string(pcr_bank))), +- JSON_BUILD_PAIR_CONDITION(!!tpm2_primary_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_primary_alg_to_string(primary_alg))), ++ JSON_BUILD_PAIR_CONDITION(!!tpm2_hash_alg_to_string(pcr_bank), "tpm2-pcr-bank", JSON_BUILD_STRING(tpm2_hash_alg_to_string(pcr_bank))), ++ JSON_BUILD_PAIR_CONDITION(!!tpm2_asym_alg_to_string(primary_alg), "tpm2-primary-alg", JSON_BUILD_STRING(tpm2_asym_alg_to_string(primary_alg))), + JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)), + JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)), + JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)), +@@ -2209,7 +2209,7 @@ int tpm2_parse_luks2_json( + if (!json_variant_is_string(w)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 PCR bank is not a string."); + +- r = tpm2_pcr_bank_from_string(json_variant_string(w)); ++ r = tpm2_hash_alg_from_string(json_variant_string(w)); + if (r < 0) + return log_debug_errno(r, "TPM2 PCR bank invalid or not supported: %s", json_variant_string(w)); + +@@ -2225,9 +2225,9 @@ int tpm2_parse_luks2_json( + if (!json_variant_is_string(w)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 primary key algorithm is not a string."); + +- r = tpm2_primary_alg_from_string(json_variant_string(w)); ++ r = tpm2_asym_alg_from_string(json_variant_string(w)); + if (r < 0) +- return log_debug_errno(r, "TPM2 primary key algorithm invalid or not supported: %s", json_variant_string(w)); ++ return log_debug_errno(r, "TPM2 asymmetric algorithm invalid or not supported: %s", json_variant_string(w)); + + primary_alg = r; + } +@@ -2299,31 +2299,31 @@ int tpm2_parse_luks2_json( + return 0; + } + +-const char *tpm2_pcr_bank_to_string(uint16_t bank) { +- if (bank == TPM2_ALG_SHA1) ++const char *tpm2_hash_alg_to_string(uint16_t alg) { ++ if (alg == TPM2_ALG_SHA1) + return "sha1"; +- if (bank == TPM2_ALG_SHA256) ++ if (alg == TPM2_ALG_SHA256) + return "sha256"; +- if (bank == TPM2_ALG_SHA384) ++ if (alg == TPM2_ALG_SHA384) + return "sha384"; +- if (bank == TPM2_ALG_SHA512) ++ if (alg == TPM2_ALG_SHA512) + return "sha512"; + return NULL; + } + +-int tpm2_pcr_bank_from_string(const char *bank) { +- if (strcaseeq_ptr(bank, "sha1")) ++int tpm2_hash_alg_from_string(const char *alg) { ++ if (strcaseeq_ptr(alg, "sha1")) + return TPM2_ALG_SHA1; +- if (strcaseeq_ptr(bank, "sha256")) ++ if (strcaseeq_ptr(alg, "sha256")) + return TPM2_ALG_SHA256; +- if (strcaseeq_ptr(bank, "sha384")) ++ if (strcaseeq_ptr(alg, "sha384")) + return TPM2_ALG_SHA384; +- if (strcaseeq_ptr(bank, "sha512")) ++ if (strcaseeq_ptr(alg, "sha512")) + return TPM2_ALG_SHA512; + return -EINVAL; + } + +-const char *tpm2_primary_alg_to_string(uint16_t alg) { ++const char *tpm2_asym_alg_to_string(uint16_t alg) { + if (alg == TPM2_ALG_ECC) + return "ecc"; + if (alg == TPM2_ALG_RSA) +@@ -2331,7 +2331,7 @@ const char *tpm2_primary_alg_to_string(uint16_t alg) { + return NULL; + } + +-int tpm2_primary_alg_from_string(const char *alg) { ++int tpm2_asym_alg_from_string(const char *alg) { + if (strcaseeq_ptr(alg, "ecc")) + return TPM2_ALG_ECC; + if (strcaseeq_ptr(alg, "rsa")) +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 96e6c31b0a..9e302021ab 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -125,11 +125,11 @@ static inline bool TPM2_PCR_MASK_VALID(uint64_t pcr_mask) { + #define TPM2_ALG_RSA 0x1 + #endif + +-const char *tpm2_pcr_bank_to_string(uint16_t bank); +-int tpm2_pcr_bank_from_string(const char *bank); ++const char *tpm2_hash_alg_to_string(uint16_t alg); ++int tpm2_hash_alg_from_string(const char *alg); + +-const char *tpm2_primary_alg_to_string(uint16_t alg); +-int tpm2_primary_alg_from_string(const char *alg); ++const char *tpm2_asym_alg_to_string(uint16_t alg); ++int tpm2_asym_alg_from_string(const char *alg); + + typedef struct { + uint32_t search_pcr_mask; diff --git a/SOURCES/0498-tpm2-rename-struct-tpm2_context-to-Tpm2Context.patch b/SOURCES/0498-tpm2-rename-struct-tpm2_context-to-Tpm2Context.patch new file mode 100644 index 0000000..f72fc17 --- /dev/null +++ b/SOURCES/0498-tpm2-rename-struct-tpm2_context-to-Tpm2Context.patch @@ -0,0 +1,151 @@ +From 55edf6d2080573d3395aec6f9f99e62cf8bd8d01 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 23 Jan 2023 19:52:56 -0500 +Subject: [PATCH] tpm2: rename struct tpm2_context to Tpm2Context + +This aligns with systemd coding guidelines for struct naming + +(cherry picked from commit bd860983a6f884e37e88915f545d5520f92890ec) + +Related: RHEL-16182 +--- + src/boot/measure.c | 2 +- + src/boot/pcrphase.c | 4 ++-- + src/cryptsetup/cryptsetup.c | 2 +- + src/shared/tpm2-util.c | 10 +++++----- + src/shared/tpm2-util.h | 14 +++++++------- + 5 files changed, 16 insertions(+), 16 deletions(-) + +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 8af3a337d6..d71a7a1d13 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -717,7 +717,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL; +- _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; ++ _cleanup_(tpm2_context_destroy) Tpm2Context c = {}; + _cleanup_fclose_ FILE *privkeyf = NULL; + ESYS_TR session_handle = ESYS_TR_NONE; + TSS2_RC rc; +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index fda9a8420d..694e131ac1 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -154,7 +154,7 @@ static int parse_argv(int argc, char *argv[]) { + return 1; + } + +-static int determine_banks(struct tpm2_context *c, unsigned target_pcr_nr) { ++static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) { + _cleanup_strv_free_ char **l = NULL; + int r; + +@@ -239,7 +239,7 @@ static int get_file_system_word( + } + + static int run(int argc, char *argv[]) { +- _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; ++ _cleanup_(tpm2_context_destroy) Tpm2Context c = {}; + _cleanup_free_ char *joined = NULL, *word = NULL; + unsigned target_pcr_nr; + size_t length; +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index 4d587fed1e..712d208741 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -841,7 +841,7 @@ static int measure_volume_key( + if (r < 0) + return log_error_errno(r, "Failed to load TPM2 libraries: %m"); + +- _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; ++ _cleanup_(tpm2_context_destroy) Tpm2Context c = {}; + r = tpm2_context_init(arg_tpm2_device, &c); + if (r < 0) + return r; +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index fe4d63b775..5c4d5476a3 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -103,7 +103,7 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal)); + } + +-void tpm2_context_destroy(struct tpm2_context *c) { ++void tpm2_context_destroy(Tpm2Context *c) { + assert(c); + + if (c->esys_context) +@@ -137,7 +137,7 @@ ESYS_TR tpm2_flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle) { + return ESYS_TR_NONE; + } + +-int tpm2_context_init(const char *device, struct tpm2_context *ret) { ++int tpm2_context_init(const char *device, Tpm2Context *ret) { + _cleanup_(Esys_Finalize_wrapper) ESYS_CONTEXT *c = NULL; + _cleanup_free_ TSS2_TCTI_CONTEXT *tcti = NULL; + _cleanup_(dlclosep) void *dl = NULL; +@@ -237,7 +237,7 @@ int tpm2_context_init(const char *device, struct tpm2_context *ret) { + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to start up TPM: %s", sym_Tss2_RC_Decode(rc)); + +- *ret = (struct tpm2_context) { ++ *ret = (Tpm2Context) { + .esys_context = TAKE_PTR(c), + .tcti_context = TAKE_PTR(tcti), + .tcti_dl = TAKE_PTR(dl), +@@ -1402,7 +1402,7 @@ int tpm2_seal(const char *device, + uint16_t *ret_pcr_bank, + uint16_t *ret_primary_alg) { + +- _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; ++ _cleanup_(tpm2_context_destroy) Tpm2Context c = {}; + _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; + _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; + _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; +@@ -1623,7 +1623,7 @@ int tpm2_unseal(const char *device, + void **ret_secret, + size_t *ret_secret_size) { + +- _cleanup_(tpm2_context_destroy) struct tpm2_context c = {}; ++ _cleanup_(tpm2_context_destroy) Tpm2Context c = {}; + ESYS_TR primary = ESYS_TR_NONE, session = ESYS_TR_NONE, hmac_session = ESYS_TR_NONE, + hmac_key = ESYS_TR_NONE; + _cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL; +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 9e302021ab..bc960c6f50 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -52,11 +52,11 @@ int dlopen_tpm2(void); + int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg); + int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, void **ret_secret, size_t *ret_secret_size); + +-struct tpm2_context { ++typedef struct { + void *tcti_dl; + TSS2_TCTI_CONTEXT *tcti_context; + ESYS_CONTEXT *esys_context; +-}; ++} Tpm2Context; + + ESYS_TR tpm2_flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle); + +@@ -72,12 +72,12 @@ int tpm2_get_good_pcr_banks_strv(ESYS_CONTEXT *c, uint32_t pcr_mask, char ***ret + + int tpm2_extend_bytes(ESYS_CONTEXT *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size); + +-#else +-struct tpm2_context; +-#endif ++#else /* HAVE_TPM2 */ ++typedef struct {} Tpm2Context; ++#endif /* HAVE_TPM2 */ + +-int tpm2_context_init(const char *device, struct tpm2_context *ret); +-void tpm2_context_destroy(struct tpm2_context *c); ++int tpm2_context_init(const char *device, Tpm2Context *ret); ++void tpm2_context_destroy(Tpm2Context *c); + + int tpm2_list_devices(void); + int tpm2_find_device_auto(int log_level, char **ret); diff --git a/SOURCES/0499-tpm2-use-ref-counter-for-Tpm2Context.patch b/SOURCES/0499-tpm2-use-ref-counter-for-Tpm2Context.patch new file mode 100644 index 0000000..0fa05cf --- /dev/null +++ b/SOURCES/0499-tpm2-use-ref-counter-for-Tpm2Context.patch @@ -0,0 +1,454 @@ +From 32a83032a4a5b239b72bde647128a004521db799 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 23 Jan 2023 19:52:56 -0500 +Subject: [PATCH] tpm2: use ref counter for Tpm2Context + +This will be used by Tpm2Handle instances, which is added in later patches. + +The refcounting allows the context to be retained until all Tpm2Handles have +been cleaned up, and the initial ref is released, before cleaning the context. + +(cherry picked from commit 68d084cee56e2686fb840106de20e267482183be) + +Related: RHEL-16182 +--- + src/boot/measure.c | 14 +++--- + src/boot/pcrphase.c | 8 ++-- + src/cryptsetup/cryptsetup.c | 8 ++-- + src/shared/tpm2-util.c | 91 ++++++++++++++++++------------------- + src/shared/tpm2-util.h | 11 +++-- + 5 files changed, 68 insertions(+), 64 deletions(-) + +diff --git a/src/boot/measure.c b/src/boot/measure.c +index d71a7a1d13..701d5471a1 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -717,7 +717,6 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL; +- _cleanup_(tpm2_context_destroy) Tpm2Context c = {}; + _cleanup_fclose_ FILE *privkeyf = NULL; + ESYS_TR session_handle = ESYS_TR_NONE; + TSS2_RC rc; +@@ -793,7 +792,8 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + if (r < 0) + return r; + +- r = tpm2_context_init(arg_tpm2_device, &c); ++ _cleanup_tpm2_context_ Tpm2Context *c = NULL; ++ r = tpm2_context_new(arg_tpm2_device, &c); + if (r < 0) + return r; + +@@ -812,7 +812,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + PcrState *p = pcr_states + i; + + rc = sym_Esys_StartAuthSession( +- c.esys_context, ++ c->esys_context, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -847,7 +847,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + tpm2_pcr_mask_to_selection(1 << TPM_PCR_INDEX_KERNEL_IMAGE, tpmalg, &pcr_selection); + + rc = sym_Esys_PolicyPCR( +- c.esys_context, ++ c->esys_context, + session_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -862,7 +862,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + + _cleanup_(Esys_Freep) TPM2B_DIGEST *pcr_policy_digest = NULL; + rc = sym_Esys_PolicyGetDigest( +- c.esys_context, ++ c->esys_context, + session_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -874,7 +874,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + goto finish; + } + +- session_handle = tpm2_flush_context_verbose(c.esys_context, session_handle); ++ session_handle = tpm2_flush_context_verbose(c->esys_context, session_handle); + + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = NULL; + mdctx = EVP_MD_CTX_new(); +@@ -965,7 +965,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + r = 0; + + finish: +- session_handle = tpm2_flush_context_verbose(c.esys_context, session_handle); ++ session_handle = tpm2_flush_context_verbose(c->esys_context, session_handle); + return r; + } + +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index 694e131ac1..bbe58fa209 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -239,7 +239,6 @@ static int get_file_system_word( + } + + static int run(int argc, char *argv[]) { +- _cleanup_(tpm2_context_destroy) Tpm2Context c = {}; + _cleanup_free_ char *joined = NULL, *word = NULL; + unsigned target_pcr_nr; + size_t length; +@@ -345,11 +344,12 @@ static int run(int argc, char *argv[]) { + if (r < 0) + return log_error_errno(r, "Failed to load TPM2 libraries: %m"); + +- r = tpm2_context_init(arg_tpm2_device, &c); ++ _cleanup_tpm2_context_ Tpm2Context *c = NULL; ++ r = tpm2_context_new(arg_tpm2_device, &c); + if (r < 0) + return r; + +- r = determine_banks(&c, target_pcr_nr); ++ r = determine_banks(c, target_pcr_nr); + if (r < 0) + return r; + if (strv_isempty(arg_banks)) /* Still none? */ +@@ -361,7 +361,7 @@ static int run(int argc, char *argv[]) { + + log_debug("Measuring '%s' into PCR index %u, banks %s.", word, target_pcr_nr, joined); + +- r = tpm2_extend_bytes(c.esys_context, arg_banks, target_pcr_nr, word, length, NULL, 0); ++ r = tpm2_extend_bytes(c->esys_context, arg_banks, target_pcr_nr, word, length, NULL, 0); + if (r < 0) + return r; + +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index 712d208741..08744bda0c 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -841,14 +841,14 @@ static int measure_volume_key( + if (r < 0) + return log_error_errno(r, "Failed to load TPM2 libraries: %m"); + +- _cleanup_(tpm2_context_destroy) Tpm2Context c = {}; +- r = tpm2_context_init(arg_tpm2_device, &c); ++ _cleanup_tpm2_context_ Tpm2Context *c = NULL; ++ r = tpm2_context_new(arg_tpm2_device, &c); + if (r < 0) + return r; + + _cleanup_strv_free_ char **l = NULL; + if (strv_isempty(arg_tpm2_measure_banks)) { +- r = tpm2_get_good_pcr_banks_strv(c.esys_context, UINT32_C(1) << arg_tpm2_measure_pcr, &l); ++ r = tpm2_get_good_pcr_banks_strv(c->esys_context, UINT32_C(1) << arg_tpm2_measure_pcr, &l); + if (r < 0) + return r; + } +@@ -871,7 +871,7 @@ static int measure_volume_key( + if (!s) + return log_oom(); + +- r = tpm2_extend_bytes(c.esys_context, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size); ++ r = tpm2_extend_bytes(c->esys_context, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size); + if (r < 0) + return r; + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 5c4d5476a3..51bb1c082d 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -103,23 +103,21 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal)); + } + +-void tpm2_context_destroy(Tpm2Context *c) { +- assert(c); ++static Tpm2Context *tpm2_context_free(Tpm2Context *c) { ++ if (!c) ++ return NULL; + + if (c->esys_context) + sym_Esys_Finalize(&c->esys_context); + + c->tcti_context = mfree(c->tcti_context); + c->tcti_dl = safe_dlclose(c->tcti_dl); +-} + +-static inline void Esys_Finalize_wrapper(ESYS_CONTEXT **c) { +- /* A wrapper around Esys_Finalize() for use with _cleanup_(). Only reasons we need this wrapper is +- * because the function itself warn logs if we'd pass a pointer to NULL, and we don't want that. */ +- if (*c) +- sym_Esys_Finalize(c); ++ return mfree(c); + } + ++DEFINE_TRIVIAL_REF_UNREF_FUNC(Tpm2Context, tpm2_context, tpm2_context_free); ++ + ESYS_TR tpm2_flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle) { + TSS2_RC rc; + +@@ -137,13 +135,19 @@ ESYS_TR tpm2_flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle) { + return ESYS_TR_NONE; + } + +-int tpm2_context_init(const char *device, Tpm2Context *ret) { +- _cleanup_(Esys_Finalize_wrapper) ESYS_CONTEXT *c = NULL; +- _cleanup_free_ TSS2_TCTI_CONTEXT *tcti = NULL; +- _cleanup_(dlclosep) void *dl = NULL; ++int tpm2_context_new(const char *device, Tpm2Context **ret_context) { ++ _cleanup_tpm2_context_ Tpm2Context *context = NULL; + TSS2_RC rc; + int r; + ++ assert(ret_context); ++ ++ context = new0(Tpm2Context, 1); ++ if (!context) ++ return log_oom(); ++ ++ context->n_ref = 1; ++ + r = dlopen_tpm2(); + if (r < 0) + return log_error_errno(r, "TPM2 support not installed: %m"); +@@ -191,11 +195,11 @@ int tpm2_context_init(const char *device, Tpm2Context *ret) { + if (!filename_is_valid(fn)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 driver name '%s' not valid, refusing.", driver); + +- dl = dlopen(fn, RTLD_NOW); +- if (!dl) ++ context->tcti_dl = dlopen(fn, RTLD_NOW); ++ if (!context->tcti_dl) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to load %s: %s", fn, dlerror()); + +- func = dlsym(dl, TSS2_TCTI_INFO_SYMBOL); ++ func = dlsym(context->tcti_dl, TSS2_TCTI_INFO_SYMBOL); + if (!func) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to find TCTI info symbol " TSS2_TCTI_INFO_SYMBOL ": %s", +@@ -205,7 +209,6 @@ int tpm2_context_init(const char *device, Tpm2Context *ret) { + if (!info) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to get TCTI info data."); + +- + log_debug("Loaded TCTI module '%s' (%s) [Version %" PRIu32 "]", info->name, info->description, info->version); + + rc = info->init(NULL, &sz, NULL); +@@ -213,22 +216,22 @@ int tpm2_context_init(const char *device, Tpm2Context *ret) { + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc)); + +- tcti = malloc0(sz); +- if (!tcti) ++ context->tcti_context = malloc0(sz); ++ if (!context->tcti_context) + return log_oom(); + +- rc = info->init(tcti, &sz, param); ++ rc = info->init(context->tcti_context, &sz, param); + if (rc != TPM2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc)); + } + +- rc = sym_Esys_Initialize(&c, tcti, NULL); ++ rc = sym_Esys_Initialize(&context->esys_context, context->tcti_context, NULL); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to initialize TPM context: %s", sym_Tss2_RC_Decode(rc)); + +- rc = sym_Esys_Startup(c, TPM2_SU_CLEAR); ++ rc = sym_Esys_Startup(context->esys_context, TPM2_SU_CLEAR); + if (rc == TPM2_RC_INITIALIZE) + log_debug("TPM already started up."); + else if (rc == TSS2_RC_SUCCESS) +@@ -237,11 +240,7 @@ int tpm2_context_init(const char *device, Tpm2Context *ret) { + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to start up TPM: %s", sym_Tss2_RC_Decode(rc)); + +- *ret = (Tpm2Context) { +- .esys_context = TAKE_PTR(c), +- .tcti_context = TAKE_PTR(tcti), +- .tcti_dl = TAKE_PTR(dl), +- }; ++ *ret_context = TAKE_PTR(context); + + return 0; + } +@@ -1402,7 +1401,6 @@ int tpm2_seal(const char *device, + uint16_t *ret_pcr_bank, + uint16_t *ret_primary_alg) { + +- _cleanup_(tpm2_context_destroy) Tpm2Context c = {}; + _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; + _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; + _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; +@@ -1452,21 +1450,22 @@ int tpm2_seal(const char *device, + + CLEANUP_ERASE(hmac_sensitive); + +- r = tpm2_context_init(device, &c); ++ _cleanup_tpm2_context_ Tpm2Context *c = NULL; ++ r = tpm2_context_new(device, &c); + if (r < 0) + return r; + +- r = tpm2_make_primary(c.esys_context, &primary, 0, &primary_alg); ++ r = tpm2_make_primary(c->esys_context, &primary, 0, &primary_alg); + if (r < 0) + return r; + + /* we cannot use the bind key before its created */ +- r = tpm2_make_encryption_session(c.esys_context, primary, ESYS_TR_NONE, NULL, &session); ++ r = tpm2_make_encryption_session(c->esys_context, primary, ESYS_TR_NONE, NULL, &session); + if (r < 0) + goto finish; + + r = tpm2_make_policy_session( +- c.esys_context, ++ c->esys_context, + primary, + session, + TPM2_SE_TRIAL, +@@ -1506,7 +1505,7 @@ int tpm2_seal(const char *device, + + assert(sizeof(hmac_sensitive.sensitive.data.buffer) >= hmac_sensitive.sensitive.data.size); + +- (void) tpm2_credit_random(c.esys_context); ++ (void) tpm2_credit_random(c->esys_context); + + log_debug("Generating secret key data."); + +@@ -1519,7 +1518,7 @@ int tpm2_seal(const char *device, + log_debug("Creating HMAC key."); + + rc = sym_Esys_Create( +- c.esys_context, ++ c->esys_context, + primary, + session, /* use HMAC session to enable parameter encryption */ + ESYS_TR_NONE, +@@ -1600,8 +1599,8 @@ int tpm2_seal(const char *device, + r = 0; + + finish: +- primary = tpm2_flush_context_verbose(c.esys_context, primary); +- session = tpm2_flush_context_verbose(c.esys_context, session); ++ primary = tpm2_flush_context_verbose(c->esys_context, primary); ++ session = tpm2_flush_context_verbose(c->esys_context, session); + return r; + } + +@@ -1623,7 +1622,6 @@ int tpm2_unseal(const char *device, + void **ret_secret, + size_t *ret_secret_size) { + +- _cleanup_(tpm2_context_destroy) Tpm2Context c = {}; + ESYS_TR primary = ESYS_TR_NONE, session = ESYS_TR_NONE, hmac_session = ESYS_TR_NONE, + hmac_key = ESYS_TR_NONE; + _cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL; +@@ -1674,11 +1672,12 @@ int tpm2_unseal(const char *device, + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to unmarshal public key: %s", sym_Tss2_RC_Decode(rc)); + +- r = tpm2_context_init(device, &c); ++ _cleanup_tpm2_context_ Tpm2Context *c = NULL; ++ r = tpm2_context_new(device, &c); + if (r < 0) + return r; + +- r = tpm2_make_primary(c.esys_context, &primary, primary_alg, NULL); ++ r = tpm2_make_primary(c->esys_context, &primary, primary_alg, NULL); + if (r < 0) + return r; + +@@ -1691,7 +1690,7 @@ int tpm2_unseal(const char *device, + * primary key is not verified and they could attack there as well. + */ + rc = sym_Esys_Load( +- c.esys_context, ++ c->esys_context, + primary, + ESYS_TR_PASSWORD, + ESYS_TR_NONE, +@@ -1714,13 +1713,13 @@ int tpm2_unseal(const char *device, + goto finish; + } + +- r = tpm2_make_encryption_session(c.esys_context, primary, hmac_key, pin, &hmac_session); ++ r = tpm2_make_encryption_session(c->esys_context, primary, hmac_key, pin, &hmac_session); + if (r < 0) + goto finish; + + for (unsigned i = RETRY_UNSEAL_MAX;; i--) { + r = tpm2_make_policy_session( +- c.esys_context, ++ c->esys_context, + primary, + hmac_session, + TPM2_SE_POLICY, +@@ -1747,7 +1746,7 @@ int tpm2_unseal(const char *device, + log_debug("Unsealing HMAC key."); + + rc = sym_Esys_Unseal( +- c.esys_context, ++ c->esys_context, + hmac_key, + session, + hmac_session, /* use HMAC session to enable parameter encryption */ +@@ -1755,7 +1754,7 @@ int tpm2_unseal(const char *device, + &unsealed); + if (rc == TPM2_RC_PCR_CHANGED && i > 0) { + log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i); +- session = tpm2_flush_context_verbose(c.esys_context, session); ++ session = tpm2_flush_context_verbose(c->esys_context, session); + continue; + } + if (rc != TSS2_RC_SUCCESS) { +@@ -1783,9 +1782,9 @@ int tpm2_unseal(const char *device, + r = 0; + + finish: +- primary = tpm2_flush_context_verbose(c.esys_context, primary); +- session = tpm2_flush_context_verbose(c.esys_context, session); +- hmac_key = tpm2_flush_context_verbose(c.esys_context, hmac_key); ++ primary = tpm2_flush_context_verbose(c->esys_context, primary); ++ session = tpm2_flush_context_verbose(c->esys_context, session); ++ hmac_key = tpm2_flush_context_verbose(c->esys_context, hmac_key); + return r; + } + +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index bc960c6f50..65c875899e 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -53,11 +53,19 @@ int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, si + int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, void **ret_secret, size_t *ret_secret_size); + + typedef struct { ++ unsigned n_ref; ++ + void *tcti_dl; + TSS2_TCTI_CONTEXT *tcti_context; + ESYS_CONTEXT *esys_context; + } Tpm2Context; + ++int tpm2_context_new(const char *device, Tpm2Context **ret_context); ++Tpm2Context *tpm2_context_ref(Tpm2Context *context); ++Tpm2Context *tpm2_context_unref(Tpm2Context *context); ++DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref); ++#define _cleanup_tpm2_context_ _cleanup_(tpm2_context_unrefp) ++ + ESYS_TR tpm2_flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle); + + void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret); +@@ -76,9 +84,6 @@ int tpm2_extend_bytes(ESYS_CONTEXT *c, char **banks, unsigned pcr_index, const v + typedef struct {} Tpm2Context; + #endif /* HAVE_TPM2 */ + +-int tpm2_context_init(const char *device, Tpm2Context *ret); +-void tpm2_context_destroy(Tpm2Context *c); +- + int tpm2_list_devices(void); + int tpm2_find_device_auto(int log_level, char **ret); + diff --git a/SOURCES/0500-tpm2-use-Tpm2Context-instead-of-ESYS_CONTEXT.patch b/SOURCES/0500-tpm2-use-Tpm2Context-instead-of-ESYS_CONTEXT.patch new file mode 100644 index 0000000..c4f7583 --- /dev/null +++ b/SOURCES/0500-tpm2-use-Tpm2Context-instead-of-ESYS_CONTEXT.patch @@ -0,0 +1,482 @@ +From 2932f601fd1957a1778f58301e8c529ed308fa42 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 6 Dec 2022 13:07:34 -0500 +Subject: [PATCH] tpm2: use Tpm2Context* instead of ESYS_CONTEXT* + +This is needed for later patches that use Tpm2Handle, which requires access +to the Tpm2Context. + +(cherry picked from commit 23e9ccc24ae0e7d3f3f609c69dbde171f1c55302) + +Related: RHEL-16182 +--- + src/boot/measure.c | 4 +- + src/boot/pcrphase.c | 4 +- + src/cryptsetup/cryptsetup.c | 4 +- + src/shared/tpm2-util.c | 90 ++++++++++++++++++------------------- + src/shared/tpm2-util.h | 8 ++-- + 5 files changed, 55 insertions(+), 55 deletions(-) + +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 701d5471a1..1bb35e5f76 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -874,7 +874,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + goto finish; + } + +- session_handle = tpm2_flush_context_verbose(c->esys_context, session_handle); ++ session_handle = tpm2_flush_context_verbose(c, session_handle); + + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = NULL; + mdctx = EVP_MD_CTX_new(); +@@ -965,7 +965,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + r = 0; + + finish: +- session_handle = tpm2_flush_context_verbose(c->esys_context, session_handle); ++ session_handle = tpm2_flush_context_verbose(c, session_handle); + return r; + } + +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index bbe58fa209..6c37d34fd6 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -163,7 +163,7 @@ static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) { + if (!strv_isempty(arg_banks)) /* Explicitly configured? Then use that */ + return 0; + +- r = tpm2_get_good_pcr_banks_strv(c->esys_context, UINT32_C(1) << target_pcr_nr, &l); ++ r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << target_pcr_nr, &l); + if (r < 0) + return r; + +@@ -361,7 +361,7 @@ static int run(int argc, char *argv[]) { + + log_debug("Measuring '%s' into PCR index %u, banks %s.", word, target_pcr_nr, joined); + +- r = tpm2_extend_bytes(c->esys_context, arg_banks, target_pcr_nr, word, length, NULL, 0); ++ r = tpm2_extend_bytes(c, arg_banks, target_pcr_nr, word, length, NULL, 0); + if (r < 0) + return r; + +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index 08744bda0c..a78272bc11 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -848,7 +848,7 @@ static int measure_volume_key( + + _cleanup_strv_free_ char **l = NULL; + if (strv_isempty(arg_tpm2_measure_banks)) { +- r = tpm2_get_good_pcr_banks_strv(c->esys_context, UINT32_C(1) << arg_tpm2_measure_pcr, &l); ++ r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << arg_tpm2_measure_pcr, &l); + if (r < 0) + return r; + } +@@ -871,7 +871,7 @@ static int measure_volume_key( + if (!s) + return log_oom(); + +- r = tpm2_extend_bytes(c->esys_context, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size); ++ r = tpm2_extend_bytes(c, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size); + if (r < 0) + return r; + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 51bb1c082d..2111d0c638 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -118,13 +118,13 @@ static Tpm2Context *tpm2_context_free(Tpm2Context *c) { + + DEFINE_TRIVIAL_REF_UNREF_FUNC(Tpm2Context, tpm2_context, tpm2_context_free); + +-ESYS_TR tpm2_flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle) { ++ESYS_TR tpm2_flush_context_verbose(Tpm2Context *c, ESYS_TR handle) { + TSS2_RC rc; + +- if (!c || handle == ESYS_TR_NONE) ++ if (!c || !c->esys_context || handle == ESYS_TR_NONE) + return ESYS_TR_NONE; + +- rc = sym_Esys_FlushContext(c, handle); ++ rc = sym_Esys_FlushContext(c->esys_context, handle); + if (rc != TSS2_RC_SUCCESS) /* We ignore failures here (besides debug logging), since this is called + * in error paths, where we cannot do anything about failures anymore. And + * when it is called in successful codepaths by this time we already did +@@ -247,7 +247,7 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + + #define TPM2_CREDIT_RANDOM_FLAG_PATH "/run/systemd/tpm-rng-credited" + +-static int tpm2_credit_random(ESYS_CONTEXT *c) { ++static int tpm2_credit_random(Tpm2Context *c) { + size_t rps, done = 0; + TSS2_RC rc; + usec_t t; +@@ -274,7 +274,7 @@ static int tpm2_credit_random(ESYS_CONTEXT *c) { + _cleanup_(Esys_Freep) TPM2B_DIGEST *buffer = NULL; + + rc = sym_Esys_GetRandom( +- c, ++ c->esys_context, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -306,7 +306,7 @@ static int tpm2_credit_random(ESYS_CONTEXT *c) { + } + + static int tpm2_make_primary( +- ESYS_CONTEXT *c, ++ Tpm2Context *c, + ESYS_TR *ret_primary, + TPMI_ALG_PUBLIC alg, + TPMI_ALG_PUBLIC *ret_alg) { +@@ -363,7 +363,7 @@ static int tpm2_make_primary( + + if (IN_SET(alg, 0, TPM2_ALG_ECC)) { + rc = sym_Esys_CreatePrimary( +- c, ++ c->esys_context, + ESYS_TR_RH_OWNER, + ESYS_TR_PASSWORD, + ESYS_TR_NONE, +@@ -392,7 +392,7 @@ static int tpm2_make_primary( + + if (IN_SET(alg, 0, TPM2_ALG_RSA)) { + rc = sym_Esys_CreatePrimary( +- c, ++ c->esys_context, + ESYS_TR_RH_OWNER, + ESYS_TR_PASSWORD, + ESYS_TR_NONE, +@@ -468,7 +468,7 @@ static unsigned find_nth_bit(uint32_t mask, unsigned n) { + } + + static int tpm2_pcr_mask_good( +- ESYS_CONTEXT *c, ++ Tpm2Context *c, + TPMI_ALG_HASH bank, + uint32_t mask) { + +@@ -486,7 +486,7 @@ static int tpm2_pcr_mask_good( + tpm2_pcr_mask_to_selection(mask, bank, &selection); + + rc = sym_Esys_PCR_Read( +- c, ++ c->esys_context, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -550,7 +550,7 @@ static int tpm2_bank_has24(const TPMS_PCR_SELECTION *selection) { + } + + static int tpm2_get_best_pcr_bank( +- ESYS_CONTEXT *c, ++ Tpm2Context *c, + uint32_t pcr_mask, + TPMI_ALG_HASH *ret) { + +@@ -563,7 +563,7 @@ static int tpm2_get_best_pcr_bank( + assert(c); + + rc = sym_Esys_GetCapability( +- c, ++ c->esys_context, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -647,7 +647,7 @@ static int tpm2_get_best_pcr_bank( + } + + int tpm2_get_good_pcr_banks( +- ESYS_CONTEXT *c, ++ Tpm2Context *c, + uint32_t pcr_mask, + TPMI_ALG_HASH **ret) { + +@@ -662,7 +662,7 @@ int tpm2_get_good_pcr_banks( + assert(ret); + + rc = sym_Esys_GetCapability( +- c, ++ c->esys_context, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -726,7 +726,7 @@ int tpm2_get_good_pcr_banks( + } + + int tpm2_get_good_pcr_banks_strv( +- ESYS_CONTEXT *c, ++ Tpm2Context *c, + uint32_t pcr_mask, + char ***ret) { + +@@ -784,7 +784,7 @@ static void hash_pin(const char *pin, size_t len, TPM2B_AUTH *auth) { + } + + static int tpm2_make_encryption_session( +- ESYS_CONTEXT *c, ++ Tpm2Context *c, + ESYS_TR primary, + ESYS_TR bind_key, + const char *pin, +@@ -816,7 +816,7 @@ static int tpm2_make_encryption_session( + + hash_pin(pin, strlen(pin), &auth); + +- rc = sym_Esys_TR_SetAuth(c, bind_key, &auth); ++ rc = sym_Esys_TR_SetAuth(c->esys_context, bind_key, &auth); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno( + SYNTHETIC_ERRNO(ENOTRECOVERABLE), +@@ -830,7 +830,7 @@ static int tpm2_make_encryption_session( + * means that the random salt will be encrypted with the well-known key. That way, only the TPM can + * recover the salt, which is then used for key derivation. */ + rc = sym_Esys_StartAuthSession( +- c, ++ c->esys_context, + primary, + bind_key, + ESYS_TR_NONE, +@@ -848,7 +848,7 @@ static int tpm2_make_encryption_session( + /* Enable parameter encryption/decryption with AES in CFB mode. Together with HMAC digests (which are + * always used for sessions), this provides confidentiality, integrity and replay protection for + * operations that use this session. */ +- rc = sym_Esys_TRSess_SetAttributes(c, session, sessionAttributes, 0xff); ++ rc = sym_Esys_TRSess_SetAttributes(c->esys_context, session, sessionAttributes, 0xff); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno( + SYNTHETIC_ERRNO(ENOTRECOVERABLE), +@@ -1047,7 +1047,7 @@ static int find_signature( + #endif + + static int tpm2_make_policy_session( +- ESYS_CONTEXT *c, ++ Tpm2Context *c, + ESYS_TR primary, + ESYS_TR parent_session, + TPM2_SE session_type, +@@ -1124,7 +1124,7 @@ static int tpm2_make_policy_session( + #endif + + rc = sym_Esys_StartAuthSession( +- c, ++ c->esys_context, + primary, + ESYS_TR_NONE, + parent_session, +@@ -1150,7 +1150,7 @@ static int tpm2_make_policy_session( + goto finish; + + rc = sym_Esys_LoadExternal( +- c, ++ c->esys_context, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -1173,7 +1173,7 @@ static int tpm2_make_policy_session( + /* Acquire the "name" of what we just loaded */ + _cleanup_(Esys_Freep) TPM2B_NAME *pubkey_name = NULL; + rc = sym_Esys_TR_GetName( +- c, ++ c->esys_context, + pubkey_handle, + &pubkey_name); + if (rc != TSS2_RC_SUCCESS) { +@@ -1186,7 +1186,7 @@ static int tpm2_make_policy_session( + TPML_PCR_SELECTION pcr_selection; + tpm2_pcr_mask_to_selection(pubkey_pcr_mask, pcr_bank, &pcr_selection); + rc = sym_Esys_PolicyPCR( +- c, ++ c->esys_context, + session, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -1202,7 +1202,7 @@ static int tpm2_make_policy_session( + /* Get the policy hash of the PCR policy */ + _cleanup_(Esys_Freep) TPM2B_DIGEST *approved_policy = NULL; + rc = sym_Esys_PolicyGetDigest( +- c, ++ c->esys_context, + session, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -1255,7 +1255,7 @@ static int tpm2_make_policy_session( + memcpy(policy_signature.signature.rsassa.sig.buffer, signature_raw, signature_size); + + rc = sym_Esys_VerifySignature( +- c, ++ c->esys_context, + pubkey_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -1281,7 +1281,7 @@ static int tpm2_make_policy_session( + } + + rc = sym_Esys_PolicyAuthorize( +- c, ++ c->esys_context, + session, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -1306,7 +1306,7 @@ static int tpm2_make_policy_session( + TPML_PCR_SELECTION pcr_selection; + tpm2_pcr_mask_to_selection(hash_pcr_mask, pcr_bank, &pcr_selection); + rc = sym_Esys_PolicyPCR( +- c, ++ c->esys_context, + session, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -1324,7 +1324,7 @@ static int tpm2_make_policy_session( + log_debug("Configuring PIN policy."); + + rc = sym_Esys_PolicyAuthValue( +- c, ++ c->esys_context, + session, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -1341,7 +1341,7 @@ static int tpm2_make_policy_session( + log_debug("Acquiring policy digest."); + + rc = sym_Esys_PolicyGetDigest( +- c, ++ c->esys_context, + session, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -1455,17 +1455,17 @@ int tpm2_seal(const char *device, + if (r < 0) + return r; + +- r = tpm2_make_primary(c->esys_context, &primary, 0, &primary_alg); ++ r = tpm2_make_primary(c, &primary, 0, &primary_alg); + if (r < 0) + return r; + + /* we cannot use the bind key before its created */ +- r = tpm2_make_encryption_session(c->esys_context, primary, ESYS_TR_NONE, NULL, &session); ++ r = tpm2_make_encryption_session(c, primary, ESYS_TR_NONE, NULL, &session); + if (r < 0) + goto finish; + + r = tpm2_make_policy_session( +- c->esys_context, ++ c, + primary, + session, + TPM2_SE_TRIAL, +@@ -1505,7 +1505,7 @@ int tpm2_seal(const char *device, + + assert(sizeof(hmac_sensitive.sensitive.data.buffer) >= hmac_sensitive.sensitive.data.size); + +- (void) tpm2_credit_random(c->esys_context); ++ (void) tpm2_credit_random(c); + + log_debug("Generating secret key data."); + +@@ -1599,8 +1599,8 @@ int tpm2_seal(const char *device, + r = 0; + + finish: +- primary = tpm2_flush_context_verbose(c->esys_context, primary); +- session = tpm2_flush_context_verbose(c->esys_context, session); ++ primary = tpm2_flush_context_verbose(c, primary); ++ session = tpm2_flush_context_verbose(c, session); + return r; + } + +@@ -1677,7 +1677,7 @@ int tpm2_unseal(const char *device, + if (r < 0) + return r; + +- r = tpm2_make_primary(c->esys_context, &primary, primary_alg, NULL); ++ r = tpm2_make_primary(c, &primary, primary_alg, NULL); + if (r < 0) + return r; + +@@ -1713,13 +1713,13 @@ int tpm2_unseal(const char *device, + goto finish; + } + +- r = tpm2_make_encryption_session(c->esys_context, primary, hmac_key, pin, &hmac_session); ++ r = tpm2_make_encryption_session(c, primary, hmac_key, pin, &hmac_session); + if (r < 0) + goto finish; + + for (unsigned i = RETRY_UNSEAL_MAX;; i--) { + r = tpm2_make_policy_session( +- c->esys_context, ++ c, + primary, + hmac_session, + TPM2_SE_POLICY, +@@ -1754,7 +1754,7 @@ int tpm2_unseal(const char *device, + &unsealed); + if (rc == TPM2_RC_PCR_CHANGED && i > 0) { + log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i); +- session = tpm2_flush_context_verbose(c->esys_context, session); ++ session = tpm2_flush_context_verbose(c, session); + continue; + } + if (rc != TSS2_RC_SUCCESS) { +@@ -1782,9 +1782,9 @@ int tpm2_unseal(const char *device, + r = 0; + + finish: +- primary = tpm2_flush_context_verbose(c->esys_context, primary); +- session = tpm2_flush_context_verbose(c->esys_context, session); +- hmac_key = tpm2_flush_context_verbose(c->esys_context, hmac_key); ++ primary = tpm2_flush_context_verbose(c, primary); ++ session = tpm2_flush_context_verbose(c, session); ++ hmac_key = tpm2_flush_context_verbose(c, hmac_key); + return r; + } + +@@ -1916,7 +1916,7 @@ int tpm2_find_device_auto( + + #if HAVE_TPM2 + int tpm2_extend_bytes( +- ESYS_CONTEXT *c, ++ Tpm2Context *c, + char **banks, + unsigned pcr_index, + const void *data, +@@ -1977,7 +1977,7 @@ int tpm2_extend_bytes( + } + + rc = sym_Esys_PCR_Extend( +- c, ++ c->esys_context, + ESYS_TR_PCR0 + pcr_index, + ESYS_TR_PASSWORD, + ESYS_TR_NONE, +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 65c875899e..0266f8128f 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -66,7 +66,7 @@ Tpm2Context *tpm2_context_unref(Tpm2Context *context); + DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref); + #define _cleanup_tpm2_context_ _cleanup_(tpm2_context_unrefp) + +-ESYS_TR tpm2_flush_context_verbose(ESYS_CONTEXT *c, ESYS_TR handle); ++ESYS_TR tpm2_flush_context_verbose(Tpm2Context *c, ESYS_TR handle); + + void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret); + +@@ -75,10 +75,10 @@ static inline void Esys_Freep(void *p) { + sym_Esys_Free(*(void**) p); + } + +-int tpm2_get_good_pcr_banks(ESYS_CONTEXT *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks); +-int tpm2_get_good_pcr_banks_strv(ESYS_CONTEXT *c, uint32_t pcr_mask, char ***ret); ++int tpm2_get_good_pcr_banks(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks); ++int tpm2_get_good_pcr_banks_strv(Tpm2Context *c, uint32_t pcr_mask, char ***ret); + +-int tpm2_extend_bytes(ESYS_CONTEXT *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size); ++int tpm2_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size); + + #else /* HAVE_TPM2 */ + typedef struct {} Tpm2Context; diff --git a/SOURCES/0501-tpm2-add-Tpm2Handle-with-automatic-cleanup.patch b/SOURCES/0501-tpm2-add-Tpm2Handle-with-automatic-cleanup.patch new file mode 100644 index 0000000..d465b16 --- /dev/null +++ b/SOURCES/0501-tpm2-add-Tpm2Handle-with-automatic-cleanup.patch @@ -0,0 +1,1057 @@ +From 3c05ed19e51be8220479e65c04308e7bd8d2a850 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 24 Jan 2023 10:19:03 -0500 +Subject: [PATCH] tpm2: add Tpm2Handle with automatic cleanup + +This allows using _cleanup_ with the handles, which then allows removing the +use of goto in all functions that use the handles. + +(cherry picked from commit 16e16b8c7b1621f3db96bfc357e5bba727c9dded) + +Related: RHEL-16182 +--- + src/boot/measure.c | 119 +++++-------- + src/shared/tpm2-util.c | 371 ++++++++++++++++++++--------------------- + src/shared/tpm2-util.h | 14 +- + 3 files changed, 237 insertions(+), 267 deletions(-) + +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 1bb35e5f76..65a48a01cd 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -718,7 +718,6 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL; + _cleanup_fclose_ FILE *privkeyf = NULL; +- ESYS_TR session_handle = ESYS_TR_NONE; + TSS2_RC rc; + size_t n; + int r; +@@ -811,6 +810,11 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + }; + PcrState *p = pcr_states + i; + ++ _cleanup_tpm2_handle_ Tpm2Handle *session = NULL; ++ r = tpm2_handle_new(c, &session); ++ if (r < 0) ++ return r; ++ + rc = sym_Esys_StartAuthSession( + c->esys_context, + ESYS_TR_NONE, +@@ -822,12 +826,10 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + TPM2_SE_TRIAL, + &symmetric, + TPM2_ALG_SHA256, +- &session_handle); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ &session->esys_handle); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc)); + + /* Generate a single hash value from the PCRs included in our policy. Given that that's + * exactly one, the calculation is trivial. */ +@@ -838,94 +840,72 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + sha256_direct(p->value, p->value_size, intermediate_digest.buffer); + + int tpmalg = tpm2_hash_alg_from_string(EVP_MD_name(p->md)); +- if (tpmalg < 0) { +- log_error_errno(tpmalg, "Unsupported PCR bank"); +- goto finish; +- } ++ if (tpmalg < 0) ++ return log_error_errno(tpmalg, "Unsupported PCR bank"); + + TPML_PCR_SELECTION pcr_selection; + tpm2_pcr_mask_to_selection(1 << TPM_PCR_INDEX_KERNEL_IMAGE, tpmalg, &pcr_selection); + + rc = sym_Esys_PolicyPCR( + c->esys_context, +- session_handle, ++ session->esys_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &intermediate_digest, + &pcr_selection); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to push PCR policy into TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to push PCR policy into TPM: %s", sym_Tss2_RC_Decode(rc)); + + _cleanup_(Esys_Freep) TPM2B_DIGEST *pcr_policy_digest = NULL; + rc = sym_Esys_PolicyGetDigest( + c->esys_context, +- session_handle, ++ session->esys_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &pcr_policy_digest); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } +- +- session_handle = tpm2_flush_context_verbose(c, session_handle); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc)); + + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = NULL; + mdctx = EVP_MD_CTX_new(); +- if (!mdctx) { +- r = log_oom(); +- goto finish; +- } ++ if (!mdctx) ++ return log_oom(); + +- if (EVP_DigestSignInit(mdctx, NULL, p->md, NULL, privkey) != 1) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to initialize signature context."); +- goto finish; +- } ++ if (EVP_DigestSignInit(mdctx, NULL, p->md, NULL, privkey) != 1) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to initialize signature context."); + +- if (EVP_DigestSignUpdate(mdctx, pcr_policy_digest->buffer, pcr_policy_digest->size) != 1) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to sign data."); +- goto finish; +- } ++ if (EVP_DigestSignUpdate(mdctx, pcr_policy_digest->buffer, pcr_policy_digest->size) != 1) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to sign data."); + + size_t ss; +- if (EVP_DigestSignFinal(mdctx, NULL, &ss) != 1) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to finalize signature"); +- goto finish; +- } ++ if (EVP_DigestSignFinal(mdctx, NULL, &ss) != 1) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to finalize signature"); + + _cleanup_free_ void *sig = malloc(ss); +- if (!sig) { +- r = log_oom(); +- goto finish; +- } ++ if (!sig) ++ return log_oom(); + +- if (EVP_DigestSignFinal(mdctx, sig, &ss) != 1) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to acquire signature data"); +- goto finish; +- } ++ if (EVP_DigestSignFinal(mdctx, sig, &ss) != 1) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to acquire signature data"); + + _cleanup_free_ void *pubkey_fp = NULL; + size_t pubkey_fp_size = 0; + r = pubkey_fingerprint(pubkey, EVP_sha256(), &pubkey_fp, &pubkey_fp_size); + if (r < 0) +- goto finish; ++ return r; + + _cleanup_(json_variant_unrefp) JsonVariant *a = NULL; + r = tpm2_make_pcr_json_array(UINT64_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE, &a); +- if (r < 0) { +- log_error_errno(r, "Failed to build JSON PCR mask array: %m"); +- goto finish; +- } ++ if (r < 0) ++ return log_error_errno(r, "Failed to build JSON PCR mask array: %m"); + + _cleanup_(json_variant_unrefp) JsonVariant *bv = NULL; + r = json_build(&bv, JSON_BUILD_OBJECT( +@@ -933,25 +913,19 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + JSON_BUILD_PAIR("pkfp", JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */ + JSON_BUILD_PAIR("pol", JSON_BUILD_HEX(pcr_policy_digest->buffer, pcr_policy_digest->size)), /* TPM2 policy hash that is signed */ + JSON_BUILD_PAIR("sig", JSON_BUILD_BASE64(sig, ss)))); /* signature data */ +- if (r < 0) { +- log_error_errno(r, "Failed to build JSON object: %m"); +- goto finish; +- } ++ if (r < 0) ++ return log_error_errno(r, "Failed to build JSON object: %m"); + + _cleanup_(json_variant_unrefp) JsonVariant *av = NULL; + av = json_variant_ref(json_variant_by_key(v, p->bank)); + + r = json_variant_append_array(&av, bv); +- if (r < 0) { +- log_error_errno(r, "Failed to append JSON object: %m"); +- goto finish; +- } ++ if (r < 0) ++ return log_error_errno(r, "Failed to append JSON object: %m"); + + r = json_variant_set_field(&v, p->bank, av); +- if (r < 0) { +- log_error_errno(r, "Failed to add JSON field: %m"); +- goto finish; +- } ++ if (r < 0) ++ return log_error_errno(r, "Failed to add JSON field: %m"); + } + + /* Return to the original kernel measurement for the next phase calculation */ +@@ -962,11 +936,8 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + pager_open(arg_pager_flags); + + json_variant_dump(v, arg_json_format_flags, stdout, NULL); +- r = 0; + +-finish: +- session_handle = tpm2_flush_context_verbose(c, session_handle); +- return r; ++ return 0; + } + + static int compare_reported_pcr_nr(uint32_t pcr, const char *varname, const char *description) { +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 2111d0c638..a01c6537b5 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -118,23 +118,6 @@ static Tpm2Context *tpm2_context_free(Tpm2Context *c) { + + DEFINE_TRIVIAL_REF_UNREF_FUNC(Tpm2Context, tpm2_context, tpm2_context_free); + +-ESYS_TR tpm2_flush_context_verbose(Tpm2Context *c, ESYS_TR handle) { +- TSS2_RC rc; +- +- if (!c || !c->esys_context || handle == ESYS_TR_NONE) +- return ESYS_TR_NONE; +- +- rc = sym_Esys_FlushContext(c->esys_context, handle); +- if (rc != TSS2_RC_SUCCESS) /* We ignore failures here (besides debug logging), since this is called +- * in error paths, where we cannot do anything about failures anymore. And +- * when it is called in successful codepaths by this time we already did +- * what we wanted to do, and got the results we wanted so there's no +- * reason to make this fail more loudly than necessary. */ +- log_debug("Failed to get flush context of TPM, ignoring: %s", sym_Tss2_RC_Decode(rc)); +- +- return ESYS_TR_NONE; +-} +- + int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + _cleanup_tpm2_context_ Tpm2Context *context = NULL; + TSS2_RC rc; +@@ -245,6 +228,47 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + return 0; + } + ++static void tpm2_handle_flush(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle) { ++ if (!esys_context || esys_handle == ESYS_TR_NONE) ++ return; ++ ++ TSS2_RC rc = sym_Esys_FlushContext(esys_context, esys_handle); ++ if (rc != TSS2_RC_SUCCESS) /* We ignore failures here (besides debug logging), since this is called ++ * in error paths, where we cannot do anything about failures anymore. And ++ * when it is called in successful codepaths by this time we already did ++ * what we wanted to do, and got the results we wanted so there's no ++ * reason to make this fail more loudly than necessary. */ ++ log_debug("Failed to flush TPM handle, ignoring: %s", sym_Tss2_RC_Decode(rc)); ++} ++ ++Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) { ++ if (!handle) ++ return NULL; ++ ++ _cleanup_tpm2_context_ Tpm2Context *context = (Tpm2Context*)handle->tpm2_context; ++ if (context) ++ tpm2_handle_flush(context->esys_context, handle->esys_handle); ++ ++ return mfree(handle); ++} ++ ++int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle) { ++ _cleanup_tpm2_handle_ Tpm2Handle *handle = NULL; ++ ++ assert(ret_handle); ++ ++ handle = new0(Tpm2Handle, 1); ++ if (!handle) ++ return log_oom(); ++ ++ handle->tpm2_context = tpm2_context_ref(context); ++ handle->esys_handle = ESYS_TR_NONE; ++ ++ *ret_handle = TAKE_PTR(handle); ++ ++ return 0; ++} ++ + #define TPM2_CREDIT_RANDOM_FLAG_PATH "/run/systemd/tpm-rng-credited" + + static int tpm2_credit_random(Tpm2Context *c) { +@@ -307,7 +331,7 @@ static int tpm2_credit_random(Tpm2Context *c) { + + static int tpm2_make_primary( + Tpm2Context *c, +- ESYS_TR *ret_primary, ++ Tpm2Handle **ret_primary, + TPMI_ALG_PUBLIC alg, + TPMI_ALG_PUBLIC *ret_alg) { + +@@ -349,9 +373,9 @@ static int tpm2_make_primary( + }; + + static const TPML_PCR_SELECTION creation_pcr = {}; +- ESYS_TR primary = ESYS_TR_NONE; + TSS2_RC rc; + usec_t ts; ++ int r; + + log_debug("Creating primary key on TPM."); + +@@ -361,6 +385,11 @@ static int tpm2_make_primary( + + ts = now(CLOCK_MONOTONIC); + ++ _cleanup_tpm2_handle_ Tpm2Handle *primary = NULL; ++ r = tpm2_handle_new(c, &primary); ++ if (r < 0) ++ return r; ++ + if (IN_SET(alg, 0, TPM2_ALG_ECC)) { + rc = sym_Esys_CreatePrimary( + c->esys_context, +@@ -372,7 +401,7 @@ static int tpm2_make_primary( + &primary_template_ecc, + NULL, + &creation_pcr, +- &primary, ++ &primary->esys_handle, + NULL, + NULL, + NULL, +@@ -401,7 +430,7 @@ static int tpm2_make_primary( + &primary_template_rsa, + NULL, + &creation_pcr, +- &primary, ++ &primary->esys_handle, + NULL, + NULL, + NULL, +@@ -420,7 +449,8 @@ static int tpm2_make_primary( + + log_debug("Generating primary key on TPM2 took %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC)); + +- *ret_primary = primary; ++ if (ret_primary) ++ *ret_primary = TAKE_PTR(primary); + if (ret_alg) + *ret_alg = alg; + +@@ -785,10 +815,10 @@ static void hash_pin(const char *pin, size_t len, TPM2B_AUTH *auth) { + + static int tpm2_make_encryption_session( + Tpm2Context *c, +- ESYS_TR primary, +- ESYS_TR bind_key, ++ const Tpm2Handle *primary, ++ const Tpm2Handle *bind_key, + const char *pin, +- ESYS_TR *ret_session) { ++ Tpm2Handle **ret_session) { + + static const TPMT_SYM_DEF symmetric = { + .algorithm = TPM2_ALG_AES, +@@ -797,10 +827,11 @@ static int tpm2_make_encryption_session( + }; + const TPMA_SESSION sessionAttributes = TPMA_SESSION_DECRYPT | TPMA_SESSION_ENCRYPT | + TPMA_SESSION_CONTINUESESSION; +- ESYS_TR session = ESYS_TR_NONE; + TSS2_RC rc; ++ int r; + + assert(c); ++ assert(ret_session); + + /* + * if a pin is set for the seal object, use it to bind the session +@@ -816,7 +847,7 @@ static int tpm2_make_encryption_session( + + hash_pin(pin, strlen(pin), &auth); + +- rc = sym_Esys_TR_SetAuth(c->esys_context, bind_key, &auth); ++ rc = sym_Esys_TR_SetAuth(c->esys_context, bind_key->esys_handle, &auth); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno( + SYNTHETIC_ERRNO(ENOTRECOVERABLE), +@@ -829,10 +860,15 @@ static int tpm2_make_encryption_session( + /* Start a salted, unbound HMAC session with a well-known key (e.g. primary key) as tpmKey, which + * means that the random salt will be encrypted with the well-known key. That way, only the TPM can + * recover the salt, which is then used for key derivation. */ ++ _cleanup_tpm2_handle_ Tpm2Handle *session = NULL; ++ r = tpm2_handle_new(c, &session); ++ if (r < 0) ++ return r; ++ + rc = sym_Esys_StartAuthSession( + c->esys_context, +- primary, +- bind_key, ++ primary->esys_handle, ++ bind_key->esys_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -840,7 +876,7 @@ static int tpm2_make_encryption_session( + TPM2_SE_HMAC, + &symmetric, + TPM2_ALG_SHA256, +- &session); ++ &session->esys_handle); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc)); +@@ -848,19 +884,15 @@ static int tpm2_make_encryption_session( + /* Enable parameter encryption/decryption with AES in CFB mode. Together with HMAC digests (which are + * always used for sessions), this provides confidentiality, integrity and replay protection for + * operations that use this session. */ +- rc = sym_Esys_TRSess_SetAttributes(c->esys_context, session, sessionAttributes, 0xff); ++ rc = sym_Esys_TRSess_SetAttributes(c->esys_context, session->esys_handle, sessionAttributes, 0xff); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno( + SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to configure TPM session: %s", + sym_Tss2_RC_Decode(rc)); + +- if (ret_session) { +- *ret_session = session; +- session = ESYS_TR_NONE; +- } ++ *ret_session = TAKE_PTR(session); + +- session = tpm2_flush_context_verbose(c, session); + return 0; + } + +@@ -1048,8 +1080,8 @@ static int find_signature( + + static int tpm2_make_policy_session( + Tpm2Context *c, +- ESYS_TR primary, +- ESYS_TR parent_session, ++ const Tpm2Handle *primary, ++ const Tpm2Handle *parent_session, + TPM2_SE session_type, + uint32_t hash_pcr_mask, + uint16_t pcr_bank, /* If UINT16_MAX, pick best bank automatically, otherwise specify bank explicitly. */ +@@ -1058,7 +1090,7 @@ static int tpm2_make_policy_session( + uint32_t pubkey_pcr_mask, + JsonVariant *signature_json, + bool use_pin, +- ESYS_TR *ret_session, ++ Tpm2Handle **ret_session, + TPM2B_DIGEST **ret_policy_digest, + TPMI_ALG_HASH *ret_pcr_bank) { + +@@ -1068,7 +1100,6 @@ static int tpm2_make_policy_session( + .mode.aes = TPM2_ALG_CFB, + }; + _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; +- ESYS_TR session = ESYS_TR_NONE, pubkey_handle = ESYS_TR_NONE; + TSS2_RC rc; + int r; + +@@ -1123,18 +1154,23 @@ static int tpm2_make_policy_session( + } + #endif + ++ _cleanup_tpm2_handle_ Tpm2Handle *session = NULL; ++ r = tpm2_handle_new(c, &session); ++ if (r < 0) ++ return r; ++ + rc = sym_Esys_StartAuthSession( + c->esys_context, +- primary, ++ primary->esys_handle, + ESYS_TR_NONE, +- parent_session, ++ parent_session->esys_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + NULL, + session_type, + &symmetric, + TPM2_ALG_SHA256, +- &session); ++ &session->esys_handle); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc)); +@@ -1147,7 +1183,12 @@ static int tpm2_make_policy_session( + TPM2B_PUBLIC pubkey_tpm2; + r = openssl_pubkey_to_tpm2_pubkey(pk, &pubkey_tpm2); + if (r < 0) +- goto finish; ++ return r; ++ ++ _cleanup_tpm2_handle_ Tpm2Handle *pubkey_handle = NULL; ++ r = tpm2_handle_new(c, &pubkey_handle); ++ if (r < 0) ++ return r; + + rc = sym_Esys_LoadExternal( + c->esys_context, +@@ -1163,56 +1204,48 @@ static int tpm2_make_policy_session( + #else + TPM2_RH_OWNER, + #endif +- &pubkey_handle); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to load public key into TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ &pubkey_handle->esys_handle); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to load public key into TPM: %s", sym_Tss2_RC_Decode(rc)); + + /* Acquire the "name" of what we just loaded */ + _cleanup_(Esys_Freep) TPM2B_NAME *pubkey_name = NULL; + rc = sym_Esys_TR_GetName( + c->esys_context, +- pubkey_handle, ++ pubkey_handle->esys_handle, + &pubkey_name); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to get name of public key from TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to get name of public key from TPM: %s", sym_Tss2_RC_Decode(rc)); + + /* Put together the PCR policy we want to use */ + TPML_PCR_SELECTION pcr_selection; + tpm2_pcr_mask_to_selection(pubkey_pcr_mask, pcr_bank, &pcr_selection); + rc = sym_Esys_PolicyPCR( + c->esys_context, +- session, ++ session->esys_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + NULL, + &pcr_selection); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc)); + + /* Get the policy hash of the PCR policy */ + _cleanup_(Esys_Freep) TPM2B_DIGEST *approved_policy = NULL; + rc = sym_Esys_PolicyGetDigest( + c->esys_context, +- session, ++ session->esys_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &approved_policy); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc)); + + /* When we are unlocking and have a signature, let's pass it to the TPM */ + _cleanup_(Esys_Freep) TPMT_TK_VERIFIED *check_ticket_buffer = NULL; +@@ -1231,7 +1264,7 @@ static int tpm2_make_policy_session( + &signature_raw, + &signature_size); + if (r < 0) +- goto finish; ++ return r; + + /* TPM2_VerifySignature() will only verify the RSA part of the RSA+SHA256 signature, + * hence we need to do the SHA256 part ourselves, first */ +@@ -1248,26 +1281,22 @@ static int tpm2_make_policy_session( + .sig.size = signature_size, + }, + }; +- if (signature_size > sizeof(policy_signature.signature.rsassa.sig.buffer)) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Signature larger than buffer."); +- goto finish; +- } ++ if (signature_size > sizeof(policy_signature.signature.rsassa.sig.buffer)) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Signature larger than buffer."); + memcpy(policy_signature.signature.rsassa.sig.buffer, signature_raw, signature_size); + + rc = sym_Esys_VerifySignature( + c->esys_context, +- pubkey_handle, ++ pubkey_handle->esys_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &signature_hash, + &policy_signature, + &check_ticket_buffer); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to validate signature in TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to validate signature in TPM: %s", sym_Tss2_RC_Decode(rc)); + + check_ticket = check_ticket_buffer; + } else { +@@ -1282,7 +1311,7 @@ static int tpm2_make_policy_session( + + rc = sym_Esys_PolicyAuthorize( + c->esys_context, +- session, ++ session->esys_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -1290,11 +1319,9 @@ static int tpm2_make_policy_session( + /* policyRef= */ &(const TPM2B_NONCE) {}, + pubkey_name, + check_ticket); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to push Authorize policy into TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to push Authorize policy into TPM: %s", sym_Tss2_RC_Decode(rc)); + #else + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); + #endif +@@ -1307,17 +1334,15 @@ static int tpm2_make_policy_session( + tpm2_pcr_mask_to_selection(hash_pcr_mask, pcr_bank, &pcr_selection); + rc = sym_Esys_PolicyPCR( + c->esys_context, +- session, ++ session->esys_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + NULL, + &pcr_selection); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc)); + } + + if (use_pin) { +@@ -1325,16 +1350,14 @@ static int tpm2_make_policy_session( + + rc = sym_Esys_PolicyAuthValue( + c->esys_context, +- session, ++ session->esys_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to add authValue policy to TPM: %s", +- sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to add authValue policy to TPM: %s", ++ sym_Tss2_RC_Decode(rc)); + } + + if (DEBUG_LOGGING || ret_policy_digest) { +@@ -1342,35 +1365,29 @@ static int tpm2_make_policy_session( + + rc = sym_Esys_PolicyGetDigest( + c->esys_context, +- session, ++ session->esys_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + &policy_digest); + +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc)); + + if (DEBUG_LOGGING) { + _cleanup_free_ char *h = NULL; + + h = hexmem(policy_digest->buffer, policy_digest->size); +- if (!h) { +- r = log_oom(); +- goto finish; +- } ++ if (!h) ++ return log_oom(); + + log_debug("Session policy digest: %s", h); + } + } + +- if (ret_session) { +- *ret_session = session; +- session = ESYS_TR_NONE; +- } ++ if (ret_session) ++ *ret_session = TAKE_PTR(session); + + if (ret_policy_digest) + *ret_policy_digest = TAKE_PTR(policy_digest); +@@ -1378,12 +1395,7 @@ static int tpm2_make_policy_session( + if (ret_pcr_bank) + *ret_pcr_bank = pcr_bank; + +- r = 0; +- +-finish: +- session = tpm2_flush_context_verbose(c, session); +- pubkey_handle = tpm2_flush_context_verbose(c, pubkey_handle); +- return r; ++ return 0; + } + + int tpm2_seal(const char *device, +@@ -1407,7 +1419,6 @@ int tpm2_seal(const char *device, + static const TPML_PCR_SELECTION creation_pcr = {}; + _cleanup_(erase_and_freep) void *secret = NULL; + _cleanup_free_ void *blob = NULL, *hash = NULL; +- ESYS_TR primary = ESYS_TR_NONE, session = ESYS_TR_NONE; + TPM2B_SENSITIVE_CREATE hmac_sensitive; + TPMI_ALG_PUBLIC primary_alg; + TPM2B_PUBLIC hmac_template; +@@ -1455,14 +1466,16 @@ int tpm2_seal(const char *device, + if (r < 0) + return r; + ++ _cleanup_tpm2_handle_ Tpm2Handle *primary = NULL; + r = tpm2_make_primary(c, &primary, 0, &primary_alg); + if (r < 0) + return r; + + /* we cannot use the bind key before its created */ +- r = tpm2_make_encryption_session(c, primary, ESYS_TR_NONE, NULL, &session); ++ _cleanup_tpm2_handle_ Tpm2Handle *session = NULL; ++ r = tpm2_make_encryption_session(c, primary, &TPM2_HANDLE_NONE, NULL, &session); + if (r < 0) +- goto finish; ++ return r; + + r = tpm2_make_policy_session( + c, +@@ -1479,7 +1492,7 @@ int tpm2_seal(const char *device, + &policy_digest, + &pcr_bank); + if (r < 0) +- goto finish; ++ return r; + + /* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the + * LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it +@@ -1510,17 +1523,15 @@ int tpm2_seal(const char *device, + log_debug("Generating secret key data."); + + r = crypto_random_bytes(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size); +- if (r < 0) { +- log_error_errno(r, "Failed to generate secret key: %m"); +- goto finish; +- } ++ if (r < 0) ++ return log_error_errno(r, "Failed to generate secret key: %m"); + + log_debug("Creating HMAC key."); + + rc = sym_Esys_Create( + c->esys_context, +- primary, +- session, /* use HMAC session to enable parameter encryption */ ++ primary->esys_handle, ++ session->esys_handle, /* use HMAC session to enable parameter encryption */ + ESYS_TR_NONE, + ESYS_TR_NONE, + &hmac_sensitive, +@@ -1532,17 +1543,13 @@ int tpm2_seal(const char *device, + NULL, + NULL, + NULL); +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to generate HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to generate HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); + + secret = memdup(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size); +- if (!secret) { +- r = log_oom(); +- goto finish; +- } ++ if (!secret) ++ return log_oom(); + + log_debug("Marshalling private and public part of HMAC key."); + +@@ -1552,10 +1559,8 @@ int tpm2_seal(const char *device, + size_t offset = 0; + + buf = malloc(k); +- if (!buf) { +- r = log_oom(); +- goto finish; +- } ++ if (!buf) ++ return log_oom(); + + rc = sym_Tss2_MU_TPM2B_PRIVATE_Marshal(private, buf, k, &offset); + if (rc == TSS2_RC_SUCCESS) { +@@ -1566,16 +1571,12 @@ int tpm2_seal(const char *device, + break; + } + } +- if (rc != TSS2_MU_RC_INSUFFICIENT_BUFFER) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to marshal private/public key: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } ++ if (rc != TSS2_MU_RC_INSUFFICIENT_BUFFER) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal private/public key: %s", sym_Tss2_RC_Decode(rc)); + +- if (k > SIZE_MAX / 2) { +- r = log_oom(); +- goto finish; +- } ++ if (k > SIZE_MAX / 2) ++ return log_oom(); + + k *= 2; + } +@@ -1596,12 +1597,7 @@ int tpm2_seal(const char *device, + *ret_pcr_bank = pcr_bank; + *ret_primary_alg = primary_alg; + +- r = 0; +- +-finish: +- primary = tpm2_flush_context_verbose(c, primary); +- session = tpm2_flush_context_verbose(c, session); +- return r; ++ return 0; + } + + #define RETRY_UNSEAL_MAX 30u +@@ -1622,8 +1618,6 @@ int tpm2_unseal(const char *device, + void **ret_secret, + size_t *ret_secret_size) { + +- ESYS_TR primary = ESYS_TR_NONE, session = ESYS_TR_NONE, hmac_session = ESYS_TR_NONE, +- hmac_key = ESYS_TR_NONE; + _cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL; + _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; + _cleanup_(erase_and_freep) char *secret = NULL; +@@ -1677,6 +1671,7 @@ int tpm2_unseal(const char *device, + if (r < 0) + return r; + ++ _cleanup_tpm2_handle_ Tpm2Handle *primary = NULL; + r = tpm2_make_primary(c, &primary, primary_alg, NULL); + if (r < 0) + return r; +@@ -1689,35 +1684,41 @@ int tpm2_unseal(const char *device, + * is provided. If an attacker gives back a bad key, we already lost since + * primary key is not verified and they could attack there as well. + */ ++ _cleanup_tpm2_handle_ Tpm2Handle *hmac_key = NULL; ++ r = tpm2_handle_new(c, &hmac_key); ++ if (r < 0) ++ return r; ++ + rc = sym_Esys_Load( + c->esys_context, +- primary, ++ primary->esys_handle, + ESYS_TR_PASSWORD, + ESYS_TR_NONE, + ESYS_TR_NONE, + &private, + &public, +- &hmac_key); ++ &hmac_key->esys_handle); + if (rc != TSS2_RC_SUCCESS) { + /* If we're in dictionary attack lockout mode, we should see a lockout error here, which we + * need to translate for the caller. */ + if (rc == TPM2_RC_LOCKOUT) +- r = log_error_errno( ++ return log_error_errno( + SYNTHETIC_ERRNO(ENOLCK), + "TPM2 device is in dictionary attack lockout mode."); + else +- r = log_error_errno( ++ return log_error_errno( + SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to load HMAC key in TPM: %s", + sym_Tss2_RC_Decode(rc)); +- goto finish; + } + ++ _cleanup_tpm2_handle_ Tpm2Handle *hmac_session = NULL; + r = tpm2_make_encryption_session(c, primary, hmac_key, pin, &hmac_session); + if (r < 0) +- goto finish; ++ return r; + + for (unsigned i = RETRY_UNSEAL_MAX;; i--) { ++ _cleanup_tpm2_handle_ Tpm2Handle *policy_session = NULL; + r = tpm2_make_policy_session( + c, + primary, +@@ -1729,11 +1730,11 @@ int tpm2_unseal(const char *device, + pubkey_pcr_mask, + signature, + !!pin, +- &session, ++ &policy_session, + &policy_digest, + /* ret_pcr_bank= */ NULL); + if (r < 0) +- goto finish; ++ return r; + + /* If we know the policy hash to expect, and it doesn't match, we can shortcut things here, and not + * wait until the TPM2 tells us to go away. */ +@@ -1747,31 +1748,23 @@ int tpm2_unseal(const char *device, + + rc = sym_Esys_Unseal( + c->esys_context, +- hmac_key, +- session, +- hmac_session, /* use HMAC session to enable parameter encryption */ ++ hmac_key->esys_handle, ++ policy_session->esys_handle, ++ hmac_session->esys_handle, /* use HMAC session to enable parameter encryption */ + ESYS_TR_NONE, + &unsealed); +- if (rc == TPM2_RC_PCR_CHANGED && i > 0) { +- log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i); +- session = tpm2_flush_context_verbose(c, session); +- continue; +- } +- if (rc != TSS2_RC_SUCCESS) { +- r = log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); +- goto finish; +- } +- +- break; ++ if (rc == TSS2_RC_SUCCESS) ++ break; ++ if (rc != TPM2_RC_PCR_CHANGED || i == 0) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); ++ log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i); + } + + secret = memdup(unsealed->buffer, unsealed->size); + explicit_bzero_safe(unsealed->buffer, unsealed->size); +- if (!secret) { +- r = log_oom(); +- goto finish; +- } ++ if (!secret) ++ return log_oom(); + + if (DEBUG_LOGGING) + log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); +@@ -1779,13 +1772,7 @@ int tpm2_unseal(const char *device, + *ret_secret = TAKE_PTR(secret); + *ret_secret_size = unsealed->size; + +- r = 0; +- +-finish: +- primary = tpm2_flush_context_verbose(c, primary); +- session = tpm2_flush_context_verbose(c, session); +- hmac_key = tpm2_flush_context_verbose(c, hmac_key); +- return r; ++ return 0; + } + + #endif +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 0266f8128f..9819a33569 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -66,7 +66,18 @@ Tpm2Context *tpm2_context_unref(Tpm2Context *context); + DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref); + #define _cleanup_tpm2_context_ _cleanup_(tpm2_context_unrefp) + +-ESYS_TR tpm2_flush_context_verbose(Tpm2Context *c, ESYS_TR handle); ++typedef struct { ++ Tpm2Context *tpm2_context; ++ ESYS_TR esys_handle; ++} Tpm2Handle; ++ ++#define _tpm2_handle(c, h) { .tpm2_context = (c), .esys_handle = (h), } ++static const Tpm2Handle TPM2_HANDLE_NONE = _tpm2_handle(NULL, ESYS_TR_NONE); ++ ++int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle); ++Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle); ++DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free); ++#define _cleanup_tpm2_handle_ _cleanup_(tpm2_handle_freep) + + void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret); + +@@ -82,6 +93,7 @@ int tpm2_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const vo + + #else /* HAVE_TPM2 */ + typedef struct {} Tpm2Context; ++typedef struct {} Tpm2Handle; + #endif /* HAVE_TPM2 */ + + int tpm2_list_devices(void); diff --git a/SOURCES/0502-tpm2-simplify-tpm2_seal-blob-creation.patch b/SOURCES/0502-tpm2-simplify-tpm2_seal-blob-creation.patch new file mode 100644 index 0000000..198dba8 --- /dev/null +++ b/SOURCES/0502-tpm2-simplify-tpm2_seal-blob-creation.patch @@ -0,0 +1,81 @@ +From 039b8bc3887c7b052aa24453ac1212a6072459d2 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 19 Dec 2022 09:58:05 -0500 +Subject: [PATCH] tpm2: simplify tpm2_seal() blob creation + +TPM2 marshalling will never increase the total size, only possibly decrease. +There is no need for checking for insufficient size if the buffer size +is set to the sizeof both objects to be marshalled. + +(cherry picked from commit e8858f1104d87179abd8d9c413292e42f1eaf7c0) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 40 ++++++++++++++-------------------------- + 1 file changed, 14 insertions(+), 26 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index a01c6537b5..6620f365d9 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1418,12 +1418,11 @@ int tpm2_seal(const char *device, + _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; + static const TPML_PCR_SELECTION creation_pcr = {}; + _cleanup_(erase_and_freep) void *secret = NULL; +- _cleanup_free_ void *blob = NULL, *hash = NULL; ++ _cleanup_free_ void *hash = NULL; + TPM2B_SENSITIVE_CREATE hmac_sensitive; + TPMI_ALG_PUBLIC primary_alg; + TPM2B_PUBLIC hmac_template; + TPMI_ALG_HASH pcr_bank; +- size_t k, blob_size; + usec_t start; + TSS2_RC rc; + int r; +@@ -1553,33 +1552,22 @@ int tpm2_seal(const char *device, + + log_debug("Marshalling private and public part of HMAC key."); + +- k = ALIGN8(sizeof(*private)) + ALIGN8(sizeof(*public)); /* Some roughly sensible start value */ +- for (;;) { +- _cleanup_free_ void *buf = NULL; +- size_t offset = 0; +- +- buf = malloc(k); +- if (!buf) +- return log_oom(); ++ _cleanup_free_ void *blob = NULL; ++ size_t max_size = sizeof(*private) + sizeof(*public), blob_size = 0; + +- rc = sym_Tss2_MU_TPM2B_PRIVATE_Marshal(private, buf, k, &offset); +- if (rc == TSS2_RC_SUCCESS) { +- rc = sym_Tss2_MU_TPM2B_PUBLIC_Marshal(public, buf, k, &offset); +- if (rc == TSS2_RC_SUCCESS) { +- blob = TAKE_PTR(buf); +- blob_size = offset; +- break; +- } +- } +- if (rc != TSS2_MU_RC_INSUFFICIENT_BUFFER) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to marshal private/public key: %s", sym_Tss2_RC_Decode(rc)); ++ blob = malloc0(max_size); ++ if (!blob) ++ return log_oom(); + +- if (k > SIZE_MAX / 2) +- return log_oom(); ++ rc = sym_Tss2_MU_TPM2B_PRIVATE_Marshal(private, blob, max_size, &blob_size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal private key: %s", sym_Tss2_RC_Decode(rc)); + +- k *= 2; +- } ++ rc = sym_Tss2_MU_TPM2B_PUBLIC_Marshal(public, blob, max_size, &blob_size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal public key: %s", sym_Tss2_RC_Decode(rc)); + + hash = memdup(policy_digest->buffer, policy_digest->size); + if (!hash) diff --git a/SOURCES/0503-tpm2-add-salt-to-pin.patch b/SOURCES/0503-tpm2-add-salt-to-pin.patch new file mode 100644 index 0000000..a8bbd52 --- /dev/null +++ b/SOURCES/0503-tpm2-add-salt-to-pin.patch @@ -0,0 +1,620 @@ +From ec44604785b325481cb7d25d29dc991d0e4109e2 Mon Sep 17 00:00:00 2001 +From: William Roberts +Date: Wed, 18 Jan 2023 08:45:53 -0600 +Subject: [PATCH] tpm2: add salt to pin + +Add a salt to the pin and store it in the TPM2 LUKS header for future +this. This adds entropy to user supplied pins and helps brute forcing +the passphrase on the key residing in the TPM or brute forcing bind key +encrypted sessions with low entropy passphrases. + +Signed-off-by: malikabhi05 +Signed-off-by: William Roberts +(cherry picked from commit aae6eb96117acd54ce5ac572aac6a11b34c4ad99) + +Related: RHEL-16182 +--- + src/cryptenroll/cryptenroll-tpm2.c | 28 +++++++ + .../cryptsetup-token-systemd-tpm2.c | 15 +++- + src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 21 +++++ + src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h | 2 + + src/cryptsetup/cryptsetup-tpm2.c | 31 ++++++- + src/cryptsetup/cryptsetup-tpm2.h | 8 ++ + src/cryptsetup/cryptsetup.c | 7 +- + src/partition/repart.c | 1 + + src/shared/tpm2-util.c | 80 ++++++++++++++++++- + src/shared/tpm2-util.h | 11 ++- + src/test/test-tpm2.c | 41 ++++++++++ + 11 files changed, 230 insertions(+), 15 deletions(-) + +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index 96d5fc0695..3098b2e7ac 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -8,6 +8,8 @@ + #include "hexdecoct.h" + #include "json.h" + #include "memory-util.h" ++#include "random-util.h" ++#include "sha256.h" + #include "tpm2-util.h" + + static int search_policy_hash( +@@ -148,6 +150,14 @@ int enroll_tpm2(struct crypt_device *cd, + ssize_t base64_encoded_size; + int r, keyslot; + TPM2Flags flags = 0; ++ uint8_t binary_salt[SHA256_DIGEST_SIZE] = {}; ++ /* ++ * erase the salt, we'd rather attempt to not have this in a coredump ++ * as an attacker would have all the parameters but pin used to create ++ * the session key. This problem goes away when we move to a trusted ++ * primary key, aka the SRK. ++ */ ++ CLEANUP_ERASE(binary_salt); + + assert(cd); + assert(volume_key); +@@ -161,6 +171,22 @@ int enroll_tpm2(struct crypt_device *cd, + r = get_pin(&pin_str, &flags); + if (r < 0) + return r; ++ ++ r = crypto_random_bytes(binary_salt, sizeof(binary_salt)); ++ if (r < 0) ++ return log_error_errno(r, "Failed to acquire random salt: %m"); ++ ++ uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; ++ CLEANUP_ERASE(salted_pin); ++ r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), binary_salt, sizeof(binary_salt), salted_pin); ++ if (r < 0) ++ return log_error_errno(r, "Failed to perform PBKDF2: %m"); ++ ++ pin_str = erase_and_free(pin_str); ++ /* re-stringify pin_str */ ++ base64_encoded_size = base64mem(salted_pin, sizeof(salted_pin), &pin_str); ++ if (base64_encoded_size < 0) ++ return log_error_errno(base64_encoded_size, "Failed to base64 encode salted pin: %m"); + } + + r = tpm2_load_pcr_public_key(pubkey_path, &pubkey, &pubkey_size); +@@ -258,6 +284,8 @@ int enroll_tpm2(struct crypt_device *cd, + primary_alg, + blob, blob_size, + hash, hash_size, ++ use_pin ? binary_salt : NULL, ++ use_pin ? sizeof(binary_salt) : 0, + flags, + &v); + if (r < 0) +diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +index 98bcaac4d8..319b0ca64d 100644 +--- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +@@ -42,8 +42,8 @@ _public_ int cryptsetup_token_open_pin( + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + _cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL; +- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL; +- size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size; ++ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL; ++ size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0; + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + uint32_t hash_pcr_mask, pubkey_pcr_mask; +@@ -90,6 +90,8 @@ _public_ int cryptsetup_token_open_pin( + &blob_size, + &policy_hash, + &policy_hash_size, ++ &salt, ++ &salt_size, + &flags); + if (r < 0) + return log_debug_open_error(cd, r); +@@ -110,6 +112,8 @@ _public_ int cryptsetup_token_open_pin( + blob_size, + policy_hash, + policy_hash_size, ++ salt, ++ salt_size, + flags, + &decrypted_key, + &decrypted_key_size); +@@ -168,9 +172,9 @@ _public_ void cryptsetup_token_dump( + const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) { + + _cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL; +- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL; ++ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; +- size_t blob_size, policy_hash_size, pubkey_size; ++ size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + uint16_t pcr_bank, primary_alg; + TPM2Flags flags = 0; +@@ -195,6 +199,8 @@ _public_ void cryptsetup_token_dump( + &blob_size, + &policy_hash, + &policy_hash_size, ++ &salt, ++ &salt_size, + &flags); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m"); +@@ -227,6 +233,7 @@ _public_ void cryptsetup_token_dump( + crypt_log(cd, "\ttpm2-blob: %s\n", blob_str); + crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str); + crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN)); ++ crypt_log(cd, "\ttpm2-salt: %s\n", true_false(salt)); + } + + /* +diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +index be496d4949..80a2c0d316 100644 +--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +@@ -9,6 +9,7 @@ + #include "luks2-tpm2.h" + #include "parse-util.h" + #include "random-util.h" ++#include "sha256.h" + #include "strv.h" + #include "tpm2-util.h" + +@@ -26,12 +27,15 @@ int acquire_luks2_key( + size_t key_data_size, + const void *policy_hash, + size_t policy_hash_size, ++ const void *salt, ++ size_t salt_size, + TPM2Flags flags, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size) { + + _cleanup_(json_variant_unrefp) JsonVariant *signature_json = NULL; + _cleanup_free_ char *auto_device = NULL; ++ _cleanup_(erase_and_freep) char *b64_salted_pin = NULL; + int r; + + assert(ret_decrypted_key); +@@ -50,6 +54,23 @@ int acquire_luks2_key( + if ((flags & TPM2_FLAGS_USE_PIN) && !pin) + return -ENOANO; + ++ /* If we're using a PIN, and the luks header has a salt, it better have a pin too */ ++ if ((flags & TPM2_FLAGS_USE_PIN) && salt && !pin) ++ return -ENOANO; ++ ++ if (pin) { ++ uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; ++ CLEANUP_ERASE(salted_pin); ++ r = tpm2_util_pbkdf2_hmac_sha256(pin, strlen(pin), salt, salt_size, salted_pin); ++ if (r < 0) ++ return log_error_errno(r, "Failed to perform PBKDF2: %m"); ++ ++ r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin); ++ if (r < 0) ++ return log_error_errno(r, "Failed to base64 encode salted pin: %m"); ++ pin = b64_salted_pin; ++ } ++ + if (pubkey_pcr_mask != 0) { + r = tpm2_load_pcr_signature(signature_path, &signature_json); + if (r < 0) +diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h +index f3625124e5..36d514caa0 100644 +--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h ++++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h +@@ -20,6 +20,8 @@ int acquire_luks2_key( + size_t key_data_size, + const void *policy_hash, + size_t policy_hash_size, ++ const void *salt, ++ size_t salt_size, + TPM2Flags flags, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size); +diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c +index 838c02bfc9..2a8a38c593 100644 +--- a/src/cryptsetup/cryptsetup-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tpm2.c +@@ -9,6 +9,7 @@ + #include "json.h" + #include "parse-util.h" + #include "random-util.h" ++#include "sha256.h" + #include "tpm2-util.h" + + static int get_pin(usec_t until, AskPasswordFlags ask_password_flags, bool headless, char **ret_pin_str) { +@@ -69,6 +70,8 @@ int acquire_tpm2_key( + size_t key_data_size, + const void *policy_hash, + size_t policy_hash_size, ++ const void *salt, ++ size_t salt_size, + TPM2Flags flags, + usec_t until, + bool headless, +@@ -140,7 +143,7 @@ int acquire_tpm2_key( + ret_decrypted_key_size); + + for (int i = 5;; i--) { +- _cleanup_(erase_and_freep) char *pin_str = NULL; ++ _cleanup_(erase_and_freep) char *pin_str = NULL, *b64_salted_pin = NULL; + + if (i <= 0) + return -EACCES; +@@ -149,13 +152,28 @@ int acquire_tpm2_key( + if (r < 0) + return r; + ++ if (salt) { ++ uint8_t salted_pin[SHA256_DIGEST_SIZE] = {}; ++ CLEANUP_ERASE(salted_pin); ++ ++ r = tpm2_util_pbkdf2_hmac_sha256(pin_str, strlen(pin_str), salt, salt_size, salted_pin); ++ if (r < 0) ++ return log_error_errno(r, "Failed to perform PBKDF2: %m"); ++ ++ r = base64mem(salted_pin, sizeof(salted_pin), &b64_salted_pin); ++ if (r < 0) ++ return log_error_errno(r, "Failed to base64 encode salted pin: %m"); ++ } else ++ /* no salting needed, backwards compat with non-salted pins */ ++ b64_salted_pin = TAKE_PTR(pin_str); ++ + r = tpm2_unseal(device, + hash_pcr_mask, + pcr_bank, + pubkey, pubkey_size, + pubkey_pcr_mask, + signature_json, +- pin_str, ++ b64_salted_pin, + primary_alg, + blob, + blob_size, +@@ -188,6 +206,8 @@ int find_tpm2_auto_data( + size_t *ret_blob_size, + void **ret_policy_hash, + size_t *ret_policy_hash_size, ++ void **ret_salt, ++ size_t *ret_salt_size, + TPM2Flags *ret_flags, + int *ret_keyslot, + int *ret_token) { +@@ -197,9 +217,9 @@ int find_tpm2_auto_data( + assert(cd); + + for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { +- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL; ++ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; +- size_t blob_size, policy_hash_size, pubkey_size; ++ size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + uint16_t pcr_bank, primary_alg; + TPM2Flags flags; +@@ -221,6 +241,7 @@ int find_tpm2_auto_data( + &primary_alg, + &blob, &blob_size, + &policy_hash, &policy_hash_size, ++ &salt, &salt_size, + &flags); + if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */ + continue; +@@ -243,6 +264,8 @@ int find_tpm2_auto_data( + *ret_blob_size = blob_size; + *ret_policy_hash = TAKE_PTR(policy_hash); + *ret_policy_hash_size = policy_hash_size; ++ *ret_salt = TAKE_PTR(salt); ++ *ret_salt_size = salt_size; + *ret_keyslot = keyslot; + *ret_token = token; + *ret_flags = flags; +diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h +index a34eb8443d..f6549b7d1d 100644 +--- a/src/cryptsetup/cryptsetup-tpm2.h ++++ b/src/cryptsetup/cryptsetup-tpm2.h +@@ -28,6 +28,8 @@ int acquire_tpm2_key( + size_t key_data_size, + const void *policy_hash, + size_t policy_hash_size, ++ const void *salt, ++ size_t salt_size, + TPM2Flags flags, + usec_t until, + bool headless, +@@ -49,6 +51,8 @@ int find_tpm2_auto_data( + size_t *ret_blob_size, + void **ret_policy_hash, + size_t *ret_policy_hash_size, ++ void **ret_salt, ++ size_t *ret_salt_size, + TPM2Flags *ret_flags, + int *ret_keyslot, + int *ret_token); +@@ -72,6 +76,8 @@ static inline int acquire_tpm2_key( + size_t key_data_size, + const void *policy_hash, + size_t policy_hash_size, ++ const void *salt, ++ size_t salt_size, + TPM2Flags flags, + usec_t until, + bool headless, +@@ -97,6 +103,8 @@ static inline int find_tpm2_auto_data( + size_t *ret_blob_size, + void **ret_policy_hash, + size_t *ret_policy_hash_size, ++ void **ret_salt, ++ size_t *ret_salt_size, + TPM2Flags *ret_flags, + int *ret_keyslot, + int *ret_token) { +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index a78272bc11..d5ce252e57 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -1674,6 +1674,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( + key_file, arg_keyfile_size, arg_keyfile_offset, + key_data, key_data_size, + /* policy_hash= */ NULL, /* policy_hash_size= */ 0, /* we don't know the policy hash */ ++ /* salt= */ NULL, /* salt_size= */ 0, + arg_tpm2_pin ? TPM2_FLAGS_USE_PIN : 0, + until, + arg_headless, +@@ -1719,8 +1720,8 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( + * works. */ + + for (;;) { +- _cleanup_free_ void *pubkey = NULL; +- size_t pubkey_size = 0; ++ _cleanup_free_ void *pubkey = NULL, *salt = NULL; ++ size_t pubkey_size = 0, salt_size = 0; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + uint16_t pcr_bank, primary_alg; + TPM2Flags tpm2_flags; +@@ -1736,6 +1737,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( + &primary_alg, + &blob, &blob_size, + &policy_hash, &policy_hash_size, ++ &salt, &salt_size, + &tpm2_flags, + &keyslot, + &token); +@@ -1765,6 +1767,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( + /* key_file= */ NULL, /* key_file_size= */ 0, /* key_file_offset= */ 0, /* no key file */ + blob, blob_size, + policy_hash, policy_hash_size, ++ salt, salt_size, + tpm2_flags, + until, + arg_headless, +diff --git a/src/partition/repart.c b/src/partition/repart.c +index cbd900969e..0075932c09 100644 +--- a/src/partition/repart.c ++++ b/src/partition/repart.c +@@ -3077,6 +3077,7 @@ static int partition_encrypt( + primary_alg, + blob, blob_size, + hash, hash_size, ++ NULL, 0, /* no salt because tpm2_seal has no pin */ + 0, + &v); + if (r < 0) +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 6620f365d9..c22a200a5c 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -12,6 +12,7 @@ + #include "format-table.h" + #include "fs-util.h" + #include "hexdecoct.h" ++#include "hmac.h" + #include "memory-util.h" + #include "openssl-util.h" + #include "parse-util.h" +@@ -2080,6 +2081,8 @@ int tpm2_make_luks2_json( + size_t blob_size, + const void *policy_hash, + size_t policy_hash_size, ++ const void *salt, ++ size_t salt_size, + TPM2Flags flags, + JsonVariant **ret) { + +@@ -2119,7 +2122,8 @@ int tpm2_make_luks2_json( + JSON_BUILD_PAIR("tpm2-policy-hash", JSON_BUILD_HEX(policy_hash, policy_hash_size)), + JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)), + JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)), +- JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)))); ++ JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)), ++ JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size)))); + if (r < 0) + return r; + +@@ -2142,10 +2146,12 @@ int tpm2_parse_luks2_json( + size_t *ret_blob_size, + void **ret_policy_hash, + size_t *ret_policy_hash_size, ++ void **ret_salt, ++ size_t *ret_salt_size, + TPM2Flags *ret_flags) { + +- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL; +- size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0; ++ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL; ++ size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0; + uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0; + uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */ + uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */ +@@ -2230,6 +2236,13 @@ int tpm2_parse_luks2_json( + SET_FLAG(flags, TPM2_FLAGS_USE_PIN, json_variant_boolean(w)); + } + ++ w = json_variant_by_key(v, "tpm2_salt"); ++ if (w) { ++ r = json_variant_unbase64(w, &salt, &salt_size); ++ if (r < 0) ++ return log_debug_errno(r, "Invalid base64 data in 'tpm2_salt' field."); ++ } ++ + w = json_variant_by_key(v, "tpm2_pubkey_pcrs"); + if (w) { + r = tpm2_parse_pcr_json_array(w, &pubkey_pcr_mask); +@@ -2267,6 +2280,10 @@ int tpm2_parse_luks2_json( + *ret_policy_hash = TAKE_PTR(policy_hash); + if (ret_policy_hash_size) + *ret_policy_hash_size = policy_hash_size; ++ if (ret_salt) ++ *ret_salt = TAKE_PTR(salt); ++ if (ret_salt_size) ++ *ret_salt_size = salt_size; + if (ret_flags) + *ret_flags = flags; + +@@ -2431,3 +2448,60 @@ int pcr_mask_to_string(uint32_t mask, char **ret) { + *ret = TAKE_PTR(buf); + return 0; + } ++ ++#define PBKDF2_HMAC_SHA256_ITERATIONS 10000 ++ ++/* ++ * Implements PBKDF2 HMAC SHA256 for a derived keylen of 32 ++ * bytes and for PBKDF2_HMAC_SHA256_ITERATIONS count. ++ * I found the wikipedia entry relevant and it contains links to ++ * relevant RFCs: ++ * - https://en.wikipedia.org/wiki/PBKDF2 ++ * - https://www.rfc-editor.org/rfc/rfc2898#section-5.2 ++ */ ++int tpm2_util_pbkdf2_hmac_sha256(const void *pass, ++ size_t passlen, ++ const void *salt, ++ size_t saltlen, ++ uint8_t ret_key[static SHA256_DIGEST_SIZE]) { ++ ++ uint8_t _cleanup_(erase_and_freep) *buffer = NULL; ++ uint8_t u[SHA256_DIGEST_SIZE]; ++ ++ /* To keep this simple, since derived KeyLen (dkLen in docs) ++ * Is the same as the hash output, we don't need multiple ++ * blocks. Part of the algorithm is to add the block count ++ * in, but this can be hardcoded to 1. ++ */ ++ static const uint8_t block_cnt[] = { 0, 0, 0, 1 }; ++ ++ assert (saltlen > 0); ++ assert (saltlen <= (SIZE_MAX - sizeof(block_cnt))); ++ assert (passlen > 0); ++ ++ /* ++ * Build a buffer of salt + block_cnt and hmac_sha256 it we ++ * do this as we don't have a context builder for HMAC_SHA256. ++ */ ++ buffer = malloc(saltlen + sizeof(block_cnt)); ++ if (!buffer) ++ return -ENOMEM; ++ ++ memcpy(buffer, salt, saltlen); ++ memcpy(&buffer[saltlen], block_cnt, sizeof(block_cnt)); ++ ++ hmac_sha256(pass, passlen, buffer, saltlen + sizeof(block_cnt), u); ++ ++ /* dk needs to be an unmodified u as u gets modified in the loop */ ++ memcpy(ret_key, u, SHA256_DIGEST_SIZE); ++ uint8_t *dk = ret_key; ++ ++ for (size_t i = 1; i < PBKDF2_HMAC_SHA256_ITERATIONS; i++) { ++ hmac_sha256(pass, passlen, u, sizeof(u), u); ++ ++ for (size_t j=0; j < sizeof(u); j++) ++ dk[j] ^= u[j]; ++ } ++ ++ return 0; ++} +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 9819a33569..d26a945a90 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -5,6 +5,7 @@ + + #include "json.h" + #include "macro.h" ++#include "sha256.h" + + typedef enum TPM2Flags { + TPM2_FLAGS_USE_PIN = 1 << 0, +@@ -104,8 +105,8 @@ int tpm2_parse_pcrs(const char *s, uint32_t *ret); + int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret); + int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret); + +-int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, TPM2Flags flags, JsonVariant **ret); +-int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, TPM2Flags *ret_flags); ++int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, TPM2Flags flags, JsonVariant **ret); ++int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, TPM2Flags *ret_flags); + + #define TPM2_PCRS_MAX 24U + +@@ -173,3 +174,9 @@ int tpm2_load_pcr_signature(const char *path, JsonVariant **ret); + int tpm2_load_pcr_public_key(const char *path, void **ret_pubkey, size_t *ret_pubkey_size); + + int pcr_mask_to_string(uint32_t mask, char **ret); ++ ++int tpm2_util_pbkdf2_hmac_sha256(const void *pass, ++ size_t passlen, ++ const void *salt, ++ size_t saltlen, ++ uint8_t res[static SHA256_DIGEST_SIZE]); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index c5f3d41da9..04e08490b3 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -28,4 +28,45 @@ TEST(tpm2_parse_pcrs) { + test_tpm2_parse_pcrs_one("foo", 0, -EINVAL); + } + ++TEST(tpm2_util_pbkdf2_hmac_sha256) { ++ ++ /* ++ * The test vectors from RFC 6070 [1] are for dkLen of 20 as it's SHA1 ++ * other RFCs I bumped into had various differing dkLen and iter counts, ++ * so this was generated using Python's hmacmodule. ++ * ++ * 1. https://www.rfc-editor.org/rfc/rfc6070.html#page-2 ++ */ ++ static const struct { ++ const uint8_t pass[256]; ++ size_t passlen; ++ const uint8_t salt[256]; ++ size_t saltlen; ++ uint8_t expected[SHA256_DIGEST_SIZE]; ++ } test_vectors[] = { ++ { .pass={'f', 'o', 'o', 'p', 'a', 's', 's'}, .passlen=7, .salt={'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5'}, .saltlen=16, .expected={0xCB, 0xEA, 0x27, 0x23, 0x9A, 0x65, 0x99, 0xF6, 0x8C, 0x26, 0x54, 0x80, 0x5C, 0x63, 0x61, 0xD2, 0x91, 0x0A, 0x60, 0x3F, 0xC2, 0xF5, 0xF0, 0xAB, 0x55, 0x8B, 0x46, 0x07, 0x60, 0x93, 0xAB, 0xCB} }, ++ { .pass={'f', 'o', 'o', 'p', 'a', 's', 's'}, .passlen=7, .salt={0x00, 'h', 'f', 's', 'd', 'j', 'h', 'f', 'd', 'j', 'h', 'j', 'd', 'f', 's'}, .saltlen=15, .expected={0x2B, 0xDF, 0x52, 0x29, 0x48, 0x3F, 0x98, 0x25, 0x01, 0x19, 0xB4, 0x42, 0xBC, 0xA7, 0x38, 0x5D, 0xCD, 0x08, 0xBD, 0xDC, 0x33, 0xBF, 0x32, 0x5E, 0x31, 0x87, 0x54, 0xFF, 0x2C, 0x23, 0x68, 0xFF} }, ++ { .pass={'f', 'o', 'o', 'p', 'a', 's', 's'}, .passlen=7, .salt={'m', 'y', 's', 'a', 0x00, 'l', 't'}, .saltlen=7, .expected={0x7C, 0x24, 0xB4, 0x4D, 0x30, 0x11, 0x53, 0x24, 0x87, 0x56, 0x24, 0x10, 0xBA, 0x9F, 0xF2, 0x4E, 0xBB, 0xF5, 0x03, 0x56, 0x2B, 0xB1, 0xA1, 0x92, 0x8B, 0x5F, 0x32, 0x02, 0x23, 0x1F, 0x79, 0xE6} }, ++ { .pass={'p', 'a', 's', 's', 'w', 'i', 't', 'h', 'n', 'u', 'l', 'l', 0x00, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}, .passlen=21, .salt={'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5'}, .saltlen=16, .expected={0xE9, 0x53, 0xB7, 0x1D, 0xAB, 0xD1, 0xC1, 0xF3, 0xC4, 0x7F, 0x18, 0x96, 0xDD, 0xD7, 0x6B, 0xC6, 0x6A, 0xBD, 0xFB, 0x12, 0x7C, 0xF8, 0x68, 0xDC, 0x6E, 0xEF, 0x29, 0xCC, 0x1B, 0x30, 0x5B, 0x74} }, ++ { .pass={'p', 'a', 's', 's', 'w', 'i', 't', 'h', 'n', 'u', 'l', 'l', 0x00, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}, .passlen=21, .salt={0x00, 'h', 'f', 's', 'd', 'j', 'h', 'f', 'd', 'j', 'h', 'j', 'd', 'f', 's'}, .saltlen=15, .expected={0x51, 0xA3, 0x82, 0xA5, 0x2F, 0x48, 0x84, 0xB3, 0x02, 0x0D, 0xC2, 0x42, 0x9A, 0x8F, 0x86, 0xCC, 0x66, 0xFD, 0x65, 0x87, 0x89, 0x07, 0x2B, 0x07, 0x82, 0x42, 0xD6, 0x6D, 0x43, 0xB8, 0xFD, 0xCF} }, ++ { .pass={'p', 'a', 's', 's', 'w', 'i', 't', 'h', 'n', 'u', 'l', 'l', 0x00, 'p', 'a', 's', 's', 'w', 'o', 'r', 'd'}, .passlen=21, .salt={'m', 'y', 's', 'a', 0x00, 'l', 't'}, .saltlen=7, .expected={0xEC, 0xFB, 0x5D, 0x5F, 0xF6, 0xA6, 0xE0, 0x79, 0x50, 0x64, 0x36, 0x64, 0xA3, 0x9A, 0x5C, 0xF3, 0x7A, 0x87, 0x0B, 0x64, 0x51, 0x59, 0x75, 0x64, 0x8B, 0x78, 0x2B, 0x62, 0x8F, 0x68, 0xD9, 0xCC} }, ++ { .pass={0x00, 'p', 'a', 's', 's'}, .passlen=5, .salt={'0', '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', '1', '2', '3', '4', '5'}, .saltlen=16, .expected={0x8A, 0x9A, 0x47, 0x9A, 0x91, 0x22, 0x2F, 0x56, 0x29, 0x4F, 0x26, 0x00, 0xE7, 0xB3, 0xEB, 0x63, 0x6D, 0x51, 0xF2, 0x60, 0x17, 0x08, 0x20, 0x70, 0x82, 0x8F, 0xA3, 0xD7, 0xBE, 0x2B, 0xD5, 0x5D} }, ++ { .pass={0x00, 'p', 'a', 's', 's'}, .passlen=5, .salt={0x00, 'h', 'f', 's', 'd', 'j', 'h', 'f', 'd', 'j', 'h', 'j', 'd', 'f', 's'}, .saltlen=15, .expected={0x72, 0x3A, 0xF5, 0xF7, 0xCD, 0x6C, 0x12, 0xDD, 0x53, 0x28, 0x46, 0x0C, 0x19, 0x0E, 0xF2, 0x91, 0xDE, 0xEA, 0xF9, 0x6F, 0x74, 0x32, 0x34, 0x3F, 0x84, 0xED, 0x8D, 0x2A, 0xDE, 0xC9, 0xC6, 0x34} }, ++ { .pass={0x00, 'p', 'a', 's', 's'}, .passlen=5, .salt={'m', 'y', 's', 'a', 0x00, 'l', 't'}, .saltlen=7, .expected={0xE3, 0x07, 0x12, 0xBE, 0xEE, 0xF5, 0x5D, 0x18, 0x72, 0xF4, 0xCF, 0xF1, 0x20, 0x6B, 0xD6, 0x66, 0xCD, 0x7C, 0xE7, 0x4F, 0xC2, 0x16, 0x70, 0x5B, 0x9B, 0x2F, 0x7D, 0xE2, 0x3B, 0x42, 0x3A, 0x1B} }, ++ }; ++ ++ uint8_t res[SHA256_DIGEST_SIZE]; ++ for(size_t i = 0; i < sizeof(test_vectors)/sizeof(test_vectors[0]); i++) { ++ ++ int rc = tpm2_util_pbkdf2_hmac_sha256( ++ test_vectors[i].pass, ++ test_vectors[i].passlen, ++ test_vectors[i].salt, ++ test_vectors[i].saltlen, ++ res); ++ assert_se(rc == 0); ++ assert_se(memcmp(test_vectors[i].expected, res, SHA256_DIGEST_SIZE) == 0); ++ } ++} ++ + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0504-basic-macro-add-macro-to-iterate-variadic-args.patch b/SOURCES/0504-basic-macro-add-macro-to-iterate-variadic-args.patch new file mode 100644 index 0000000..ad23347 --- /dev/null +++ b/SOURCES/0504-basic-macro-add-macro-to-iterate-variadic-args.patch @@ -0,0 +1,30 @@ +From 01f312e212e8ad095800856a7247a0a2d85cc99d Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 2 Feb 2023 15:58:10 -0500 +Subject: [PATCH] basic/macro: add macro to iterate variadic args + +(cherry picked from commit e179f2d89c9f0c951636d74de00136b4075cd1ac) + +Related: RHEL-16182 +--- + src/basic/macro.h | 9 +++++++++ + 1 file changed, 9 insertions(+) + +diff --git a/src/basic/macro.h b/src/basic/macro.h +index 72a2c7267e..9c36683ef9 100644 +--- a/src/basic/macro.h ++++ b/src/basic/macro.h +@@ -458,4 +458,13 @@ assert_cc(sizeof(dummy_t) == 0); + _q && _q > (base) ? &_q[-1] : NULL; \ + }) + ++/* Iterate through each variadic arg. All must be the same type as 'entry' or must be implicitly ++ * convertable. The iteration variable 'entry' must already be defined. */ ++#define VA_ARGS_FOREACH(entry, ...) \ ++ _VA_ARGS_FOREACH(entry, UNIQ_T(_entries_, UNIQ), UNIQ_T(_current_, UNIQ), ##__VA_ARGS__) ++#define _VA_ARGS_FOREACH(entry, _entries_, _current_, ...) \ ++ for (typeof(entry) _entries_[] = { __VA_ARGS__ }, *_current_ = _entries_; \ ++ ((long)(_current_ - _entries_) < (long)ELEMENTSOF(_entries_)) && ({ entry = *_current_; true; }); \ ++ _current_++) ++ + #include "log.h" diff --git a/SOURCES/0505-test-test-macro-add-tests-for-FOREACH_VA_ARGS.patch b/SOURCES/0505-test-test-macro-add-tests-for-FOREACH_VA_ARGS.patch new file mode 100644 index 0000000..2157bd4 --- /dev/null +++ b/SOURCES/0505-test-test-macro-add-tests-for-FOREACH_VA_ARGS.patch @@ -0,0 +1,207 @@ +From 354d5e213410a9ec5627bae8243f69fc1bba7b3c Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Sun, 26 Feb 2023 08:02:16 -0500 +Subject: [PATCH] test/test-macro: add tests for FOREACH_VA_ARGS() + +(cherry picked from commit 326ef267004bf8362cf0b0066af8ce56e0e2941e) + +Related: RHEL-16182 +--- + src/test/test-macro.c | 184 ++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 184 insertions(+) + +diff --git a/src/test/test-macro.c b/src/test/test-macro.c +index 6a5f4bbeb7..bb79ea0dbe 100644 +--- a/src/test/test-macro.c ++++ b/src/test/test-macro.c +@@ -290,6 +290,190 @@ TEST(foreach_pointer) { + assert_se(k == 11); + } + ++TEST(foreach_va_args) { ++ size_t i; ++ ++ i = 0; ++ uint8_t u8, u8_1 = 1, u8_2 = 2, u8_3 = 3; ++ VA_ARGS_FOREACH(u8, u8_2, 8, 0xff, u8_1, u8_3, 0, 1) { ++ switch(i++) { ++ case 0: assert_se(u8 == u8_2); break; ++ case 1: assert_se(u8 == 8); break; ++ case 2: assert_se(u8 == 0xff); break; ++ case 3: assert_se(u8 == u8_1); break; ++ case 4: assert_se(u8 == u8_3); break; ++ case 5: assert_se(u8 == 0); break; ++ case 6: assert_se(u8 == 1); break; ++ default: assert_se(false); ++ } ++ } ++ assert_se(i == 7); ++ i = 0; ++ VA_ARGS_FOREACH(u8, 0) { ++ assert_se(u8 == 0); ++ assert_se(i++ == 0); ++ } ++ assert_se(i == 1); ++ i = 0; ++ VA_ARGS_FOREACH(u8, 0xff) { ++ assert_se(u8 == 0xff); ++ assert_se(i++ == 0); ++ } ++ assert_se(i == 1); ++ VA_ARGS_FOREACH(u8) ++ assert_se(false); ++ ++ i = 0; ++ uint32_t u32, u32_1 = 0xffff0000, u32_2 = 10, u32_3 = 0xffff; ++ VA_ARGS_FOREACH(u32, 1, 100, u32_2, 1000, u32_3, u32_1, 1, 0) { ++ switch(i++) { ++ case 0: assert_se(u32 == 1); break; ++ case 1: assert_se(u32 == 100); break; ++ case 2: assert_se(u32 == u32_2); break; ++ case 3: assert_se(u32 == 1000); break; ++ case 4: assert_se(u32 == u32_3); break; ++ case 5: assert_se(u32 == u32_1); break; ++ case 6: assert_se(u32 == 1); break; ++ case 7: assert_se(u32 == 0); break; ++ default: assert_se(false); ++ } ++ } ++ assert_se(i == 8); ++ i = 0; ++ VA_ARGS_FOREACH(u32, 0) { ++ assert_se(u32 == 0); ++ assert_se(i++ == 0); ++ } ++ assert_se(i == 1); ++ i = 0; ++ VA_ARGS_FOREACH(u32, 1000) { ++ assert_se(u32 == 1000); ++ assert_se(i++ == 0); ++ } ++ assert_se(i == 1); ++ VA_ARGS_FOREACH(u32) ++ assert_se(false); ++ ++ i = 0; ++ uint64_t u64, u64_1 = 0xffffffffffffffff, u64_2 = 50, u64_3 = 0xffff; ++ VA_ARGS_FOREACH(u64, 44, 0, u64_3, 100, u64_2, u64_1, 50000) { ++ switch(i++) { ++ case 0: assert_se(u64 == 44); break; ++ case 1: assert_se(u64 == 0); break; ++ case 2: assert_se(u64 == u64_3); break; ++ case 3: assert_se(u64 == 100); break; ++ case 4: assert_se(u64 == u64_2); break; ++ case 5: assert_se(u64 == u64_1); break; ++ case 6: assert_se(u64 == 50000); break; ++ default: assert_se(false); ++ } ++ } ++ assert_se(i == 7); ++ i = 0; ++ VA_ARGS_FOREACH(u64, 0) { ++ assert_se(u64 == 0); ++ assert_se(i++ == 0); ++ } ++ assert_se(i == 1); ++ i = 0; ++ VA_ARGS_FOREACH(u64, 0xff00ff00000000) { ++ assert_se(u64 == 0xff00ff00000000); ++ assert_se(i++ == 0); ++ } ++ assert_se(i == 1); ++ VA_ARGS_FOREACH(u64) ++ assert_se(false); ++ ++ struct test { ++ int a; ++ char b; ++ }; ++ ++ i = 0; ++ struct test s, ++ s_1 = { .a = 0, .b = 'c', }, ++ s_2 = { .a = 100000, .b = 'z', }, ++ s_3 = { .a = 0xff, .b = 'q', }, ++ s_4 = { .a = 1, .b = 'x', }; ++ VA_ARGS_FOREACH(s, s_1, (struct test){ .a = 10, .b = 'd', }, s_2, (struct test){}, s_3, s_4) { ++ switch(i++) { ++ case 0: assert_se(s.a == 0 ); assert_se(s.b == 'c'); break; ++ case 1: assert_se(s.a == 10 ); assert_se(s.b == 'd'); break; ++ case 2: assert_se(s.a == 100000); assert_se(s.b == 'z'); break; ++ case 3: assert_se(s.a == 0 ); assert_se(s.b == 0 ); break; ++ case 4: assert_se(s.a == 0xff ); assert_se(s.b == 'q'); break; ++ case 5: assert_se(s.a == 1 ); assert_se(s.b == 'x'); break; ++ default: assert_se(false); ++ } ++ } ++ assert_se(i == 6); ++ i = 0; ++ VA_ARGS_FOREACH(s, (struct test){ .a = 1, .b = 'A', }) { ++ assert_se(s.a == 1); ++ assert_se(s.b == 'A'); ++ assert_se(i++ == 0); ++ } ++ assert_se(i == 1); ++ VA_ARGS_FOREACH(s) ++ assert_se(false); ++ ++ i = 0; ++ struct test *p, *p_1 = &s_1, *p_2 = &s_2, *p_3 = &s_3, *p_4 = &s_4; ++ VA_ARGS_FOREACH(p, p_1, NULL, p_2, p_3, NULL, p_4, NULL) { ++ switch(i++) { ++ case 0: assert_se(p == p_1); break; ++ case 1: assert_se(p == NULL); break; ++ case 2: assert_se(p == p_2); break; ++ case 3: assert_se(p == p_3); break; ++ case 4: assert_se(p == NULL); break; ++ case 5: assert_se(p == p_4); break; ++ case 6: assert_se(p == NULL); break; ++ default: assert_se(false); ++ } ++ } ++ assert_se(i == 7); ++ i = 0; ++ VA_ARGS_FOREACH(p, p_3) { ++ assert_se(p == p_3); ++ assert_se(i++ == 0); ++ } ++ assert_se(i == 1); ++ VA_ARGS_FOREACH(p) ++ assert_se(false); ++ ++ i = 0; ++ void *v, *v_1 = p_1, *v_2 = p_2, *v_3 = p_3; ++ uint32_t *u32p = &u32; ++ VA_ARGS_FOREACH(v, v_1, NULL, u32p, v_3, p_2, p_4, v_2, NULL) { ++ switch(i++) { ++ case 0: assert_se(v == v_1); break; ++ case 1: assert_se(v == NULL); break; ++ case 2: assert_se(v == u32p); break; ++ case 3: assert_se(v == v_3); break; ++ case 4: assert_se(v == p_2); break; ++ case 5: assert_se(v == p_4); break; ++ case 6: assert_se(v == v_2); break; ++ case 7: assert_se(v == NULL); break; ++ default: assert_se(false); ++ } ++ } ++ assert_se(i == 8); ++ i = 0; ++ VA_ARGS_FOREACH(v, NULL) { ++ assert_se(v == NULL); ++ assert_se(i++ == 0); ++ } ++ assert_se(i == 1); ++ i = 0; ++ VA_ARGS_FOREACH(v, v_1) { ++ assert_se(v == v_1); ++ assert_se(i++ == 0); ++ } ++ assert_se(i == 1); ++ VA_ARGS_FOREACH(v) ++ assert_se(false); ++} ++ + TEST(align_to) { + assert_se(ALIGN_TO(0, 1) == 0); + assert_se(ALIGN_TO(1, 1) == 1); diff --git a/SOURCES/0506-basic-bitfield-add-bitfield-operations.patch b/SOURCES/0506-basic-bitfield-add-bitfield-operations.patch new file mode 100644 index 0000000..25054d4 --- /dev/null +++ b/SOURCES/0506-basic-bitfield-add-bitfield-operations.patch @@ -0,0 +1,98 @@ +From 55a262020c0a61b639c17a1be4f7b5b6fd3f5087 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 2 Feb 2023 15:58:10 -0500 +Subject: [PATCH] basic/bitfield: add bitfield operations + +Add macros to manage bits in a bitfield (e.g. uint32_t, uint64_t, etc), +such as setting, clearing, checking bits, and iterating all set bits. + +These are similiar to the bitmap operations, but operate on basic types +instead of requiring a Bitmap object. + +(cherry picked from commit 33d9beed07b3dab05d07d57e8af13bb19b9e3095) + +Related: RHEL-16182 +--- + src/basic/bitfield.h | 73 ++++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 73 insertions(+) + create mode 100644 src/basic/bitfield.h + +diff --git a/src/basic/bitfield.h b/src/basic/bitfield.h +new file mode 100644 +index 0000000000..25bc0ebda7 +--- /dev/null ++++ b/src/basic/bitfield.h +@@ -0,0 +1,73 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++#pragma once ++ ++#include "macro.h" ++ ++/* Bit index (0-based) to mask of specified type. Assertion failure if index is out of range. */ ++#define _INDEX_TO_MASK(type, i, uniq) \ ++ ({ \ ++ int UNIQ_T(_i, uniq) = (i); \ ++ assert(UNIQ_T(_i, uniq) < (int)sizeof(type) * 8); \ ++ ((type)1) << UNIQ_T(_i, uniq); \ ++ }) ++#define INDEX_TO_MASK(type, i) \ ++ ({ \ ++ assert_cc(sizeof(type) <= sizeof(unsigned long long)); \ ++ assert_cc(__builtin_choose_expr(__builtin_constant_p(i), i, 0) < (int)(sizeof(type) * 8)); \ ++ __builtin_choose_expr(__builtin_constant_p(i), \ ++ ((type)1) << (i), \ ++ _INDEX_TO_MASK(type, i, UNIQ)); \ ++ }) ++ ++/* Builds a mask of specified type with multiple bits set. Note the result will not be constant, even if all ++ * indexes are constant. */ ++#define INDEXES_TO_MASK(type, ...) \ ++ UNIQ_INDEXES_TO_MASK(type, UNIQ, ##__VA_ARGS__) ++#define UNIQ_INDEXES_TO_MASK(type, uniq, ...) \ ++ ({ \ ++ typeof(type) UNIQ_T(_mask, uniq) = (type)0; \ ++ int UNIQ_T(_i, uniq); \ ++ VA_ARGS_FOREACH(UNIQ_T(_i, uniq), ##__VA_ARGS__) \ ++ UNIQ_T(_mask, uniq) |= INDEX_TO_MASK(type, UNIQ_T(_i, uniq)); \ ++ UNIQ_T(_mask, uniq); \ ++ }) ++ ++/* Same as the FLAG macros, but accept a 0-based bit index instead of a mask. Results in assertion failure if ++ * index is out of range for the type. */ ++#define SET_BIT(bits, i) SET_FLAG(bits, INDEX_TO_MASK(typeof(bits), i), true) ++#define CLEAR_BIT(bits, i) SET_FLAG(bits, INDEX_TO_MASK(typeof(bits), i), false) ++#define BIT_SET(bits, i) FLAGS_SET(bits, INDEX_TO_MASK(typeof(bits), i)) ++ ++/* As above, but accepts multiple indexes. Note the result will not be constant, even if all indexes are ++ * constant. */ ++#define SET_BITS(bits, ...) SET_FLAG(bits, INDEXES_TO_MASK(typeof(bits), ##__VA_ARGS__), true) ++#define CLEAR_BITS(bits, ...) SET_FLAG(bits, INDEXES_TO_MASK(typeof(bits), ##__VA_ARGS__), false) ++#define BITS_SET(bits, ...) FLAGS_SET(bits, INDEXES_TO_MASK(typeof(bits), ##__VA_ARGS__)) ++ ++/* Iterate through each set bit. Index is 0-based and type int. */ ++#define BIT_FOREACH(index, bits) _BIT_FOREACH(index, bits, UNIQ) ++#define _BIT_FOREACH(index, bits, uniq) \ ++ for (int UNIQ_T(_last, uniq) = -1, index; \ ++ (index = BIT_NEXT_SET(bits, UNIQ_T(_last, uniq))) >= 0; \ ++ UNIQ_T(_last, uniq) = index) ++ ++/* Find the next set bit after 0-based index 'prev'. Result is 0-based index of next set bit, or -1 if no ++ * more bits are set. */ ++#define BIT_FIRST_SET(bits) BIT_NEXT_SET(bits, -1) ++#define BIT_NEXT_SET(bits, prev) \ ++ UNIQ_BIT_NEXT_SET(bits, prev, UNIQ) ++#define UNIQ_BIT_NEXT_SET(bits, prev, uniq) \ ++ ({ \ ++ typeof(bits) UNIQ_T(_bits, uniq) = (bits); \ ++ int UNIQ_T(_prev, uniq) = (prev); \ ++ int UNIQ_T(_next, uniq); \ ++ _BIT_NEXT_SET(UNIQ_T(_bits, uniq), \ ++ UNIQ_T(_prev, uniq), \ ++ UNIQ_T(_next, uniq)); \ ++ }) ++#define _BIT_NEXT_SET(bits, prev, next) \ ++ ((int)(prev + 1) == (int)sizeof(bits) * 8 \ ++ ? -1 /* Prev index was msb. */ \ ++ : ((next = __builtin_ffsll(((unsigned long long)(bits)) >> (prev + 1))) == 0 \ ++ ? -1 /* No more bits set. */ \ ++ : prev + next)) diff --git a/SOURCES/0507-test-test-bitfield-add-tests-for-bitfield-macros.patch b/SOURCES/0507-test-test-bitfield-add-tests-for-bitfield-macros.patch new file mode 100644 index 0000000..9452199 --- /dev/null +++ b/SOURCES/0507-test-test-bitfield-add-tests-for-bitfield-macros.patch @@ -0,0 +1,260 @@ +From 8d96572fadee034a80ff4ffe5b901cb55c22edaf Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 2 Feb 2023 16:00:11 -0500 +Subject: [PATCH] test/test-bitfield: add tests for bitfield macros + +(cherry picked from commit 5e31ddd11e28beb0348dda2afd2e9082599bdf46) + +Related: RHEL-16182 +--- + src/test/meson.build | 2 + + src/test/test-bitfield.c | 227 +++++++++++++++++++++++++++++++++++++++ + 2 files changed, 229 insertions(+) + create mode 100644 src/test/test-bitfield.c + +diff --git a/src/test/meson.build b/src/test/meson.build +index 536ab08652..976794b22b 100644 +--- a/src/test/meson.build ++++ b/src/test/meson.build +@@ -683,6 +683,8 @@ tests += [ + [files('test-hmac.c')], + + [files('test-sha256.c')], ++ ++ [files('test-bitfield.c')], + ] + + ############################################################ +diff --git a/src/test/test-bitfield.c b/src/test/test-bitfield.c +new file mode 100644 +index 0000000000..74ebd5cc48 +--- /dev/null ++++ b/src/test/test-bitfield.c +@@ -0,0 +1,227 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++#include ++ ++#include "bitfield.h" ++#include "log.h" ++#include "tests.h" ++ ++#define TEST_BITS(bits, v, ...) \ ++ ({ \ ++ assert_se((!!BITS_SET(bits, ##__VA_ARGS__)) == v); \ ++ assert_se((!!BITS_SET(~(bits), ##__VA_ARGS__)) == !v); \ ++ }) ++#define TEST_BIT(bits, v, i) \ ++ ({ \ ++ assert_se((!!BIT_SET(bits, i)) == v); \ ++ assert_se((!!BIT_SET(~(bits), i)) == !v); \ ++ TEST_BITS(bits, v, i); \ ++ }) ++ ++#define TEST_BIT_SET(bits, i) TEST_BIT(bits, 1, i) ++#define TEST_BIT_CLEAR(bits, i) TEST_BIT(bits, 0, i) ++ ++#define TEST_BITS_SET(bits, ...) TEST_BITS(bits, 1, ##__VA_ARGS__) ++#define TEST_BITS_CLEAR(bits, ...) TEST_BITS(bits, 0, ##__VA_ARGS__) ++ ++TEST(bits) { ++ int count; ++ ++ /* Test uint8_t */ ++ TEST_BIT_SET(0x81, 0); ++ TEST_BIT_SET(0x81, 7); ++ TEST_BITS_SET(0x81, 0, 7); ++ TEST_BIT_CLEAR(0x81, 4); ++ TEST_BIT_CLEAR(0x81, 6); ++ TEST_BITS_CLEAR(0x81, 1, 2, 3, 4, 5, 6); ++ uint8_t expected8 = 0; ++ BIT_FOREACH(i, 0x81) ++ expected8 |= UINT8_C(1) << i; ++ assert_se(expected8 == 0x81); ++ uint8_t u8 = 0x91; ++ TEST_BIT_SET(u8, 4); ++ TEST_BITS_SET(u8, 0, 4, 7); ++ TEST_BIT_CLEAR(u8, 2); ++ TEST_BITS_CLEAR(u8, 1, 2, 3, 5, 6); ++ SET_BIT(u8, 1); ++ TEST_BITS_SET(u8, 0, 1, 4, 7); ++ TEST_BITS_CLEAR(u8, 2, 3, 5, 6); ++ SET_BITS(u8, 3, 5); ++ TEST_BITS_SET(u8, 0, 1, 3, 4, 5, 7); ++ TEST_BITS_CLEAR(u8, 2, 6); ++ CLEAR_BIT(u8, 4); ++ TEST_BITS_SET(u8, 0, 1, 3, 5, 7); ++ TEST_BITS_CLEAR(u8, 2, 4, 6); ++ CLEAR_BITS(u8, 1); ++ CLEAR_BITS(u8, 0, 7); ++ TEST_BITS_SET(u8, 3, 5); ++ TEST_BITS_CLEAR(u8, 0, 1, 2, 4, 6, 7); ++ expected8 = 0; ++ BIT_FOREACH(i, u8) ++ expected8 |= UINT8_C(1) << i; ++ assert_se(expected8 == u8); ++ u8 = 0; ++ TEST_BITS_CLEAR(u8, 0, 1, 2, 3, 4, 5, 6, 7); ++ BIT_FOREACH(i, u8) ++ assert_se(0); ++ u8 = ~u8; ++ TEST_BITS_SET(u8, 0, 1, 2, 3, 4, 5, 6, 7); ++ count = 0; ++ BIT_FOREACH(i, u8) ++ count++; ++ assert_se(count == 8); ++ uint8_t _u8 = u8; ++ SET_BITS(u8); ++ assert_se(_u8 == u8); ++ CLEAR_BITS(u8); ++ assert_se(_u8 == u8); ++ ++ /* Test uint16_t */ ++ TEST_BIT_SET(0x1f81, 10); ++ TEST_BITS_SET(0x1f81, 0, 7, 8, 9, 10, 11, 12); ++ TEST_BIT_CLEAR(0x1f81, 13); ++ TEST_BITS_CLEAR(0x1f81, 1, 2, 3, 4, 5, 6, 13, 14, 15); ++ uint16_t expected16 = 0; ++ BIT_FOREACH(i, 0x1f81) ++ expected16 |= UINT16_C(1) << i; ++ assert_se(expected16 == 0x1f81); ++ uint16_t u16 = 0xf060; ++ TEST_BIT_SET(u16, 12); ++ TEST_BITS_SET(u16, 5, 6, 12, 13, 14, 15); ++ TEST_BIT_CLEAR(u16, 9); ++ TEST_BITS_CLEAR(u16, 0, 1, 2, 3, 4, 7, 8, 9, 10, 11); ++ SET_BITS(u16, 1, 8); ++ TEST_BITS_SET(u16, 1, 5, 6, 8, 12, 13, 14, 15); ++ TEST_BITS_CLEAR(u16, 0, 2, 3, 4, 7, 9, 10, 11); ++ CLEAR_BITS(u16, 13, 14); ++ TEST_BITS_SET(u16, 1, 5, 6, 8, 12, 15); ++ TEST_BITS_CLEAR(u16, 0, 2, 3, 4, 7, 9, 10, 11, 13, 14); ++ expected16 = 0; ++ BIT_FOREACH(i, u16) ++ expected16 |= UINT16_C(1) << i; ++ assert_se(expected16 == u16); ++ u16 = 0; ++ TEST_BITS_CLEAR(u16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); ++ BIT_FOREACH(i, u16) ++ assert_se(0); ++ u16 = ~u16; ++ TEST_BITS_SET(u16, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15); ++ count = 0; ++ BIT_FOREACH(i, u16) ++ count++; ++ assert_se(count == 16); ++ uint16_t _u16 = u16; ++ SET_BITS(u16); ++ assert_se(_u16 == u16); ++ CLEAR_BITS(u16); ++ assert_se(_u16 == u16); ++ ++ /* Test uint32_t */ ++ TEST_BIT_SET(0x80224f10, 11); ++ TEST_BITS_SET(0x80224f10, 4, 8, 9, 10, 11, 14, 17, 21, 31); ++ TEST_BIT_CLEAR(0x80224f10, 28); ++ TEST_BITS_CLEAR(0x80224f10, 0, 1, 2, 3, 5, 6, 7, 12, 13, 15, 16, 18, 19, 20, 22, 23, 24, 25, 26, 27, 28, 29, 30); ++ uint32_t expected32 = 0; ++ BIT_FOREACH(i, 0x80224f10) ++ expected32 |= UINT32_C(1) << i; ++ assert_se(expected32 == 0x80224f10); ++ uint32_t u32 = 0x605e0388; ++ TEST_BIT_SET(u32, 3); ++ TEST_BIT_SET(u32, 30); ++ TEST_BITS_SET(u32, 3, 7, 8, 9, 17, 18, 19, 20, 22, 29, 30); ++ TEST_BIT_CLEAR(u32, 0); ++ TEST_BIT_CLEAR(u32, 31); ++ TEST_BITS_CLEAR(u32, 0, 1, 2, 4, 5, 6, 10, 11, 12, 13, 14, 15, 16, 21, 23, 24, 25, 26, 27, 28, 31); ++ SET_BITS(u32, 1, 25, 26); ++ TEST_BITS_SET(u32, 1, 3, 7, 8, 9, 17, 18, 19, 20, 22, 25, 26, 29, 30); ++ TEST_BITS_CLEAR(u32, 0, 2, 4, 5, 6, 10, 11, 12, 13, 14, 15, 16, 21, 23, 24, 27, 28, 31); ++ CLEAR_BITS(u32, 29, 17, 1); ++ TEST_BITS_SET(u32, 3, 7, 8, 9, 18, 19, 20, 22, 25, 26, 30); ++ TEST_BITS_CLEAR(u32, 0, 1, 2, 4, 5, 6, 10, 11, 12, 13, 14, 15, 16, 17, 21, 23, 24, 27, 28, 29, 31); ++ expected32 = 0; ++ BIT_FOREACH(i, u32) ++ expected32 |= UINT32_C(1) << i; ++ assert_se(expected32 == u32); ++ u32 = 0; ++ TEST_BITS_CLEAR(u32, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31); ++ BIT_FOREACH(i, u32) ++ assert_se(0); ++ u32 = ~u32; ++ TEST_BITS_SET(u32, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31); ++ count = 0; ++ BIT_FOREACH(i, u32) ++ count++; ++ assert_se(count == 32); ++ uint32_t _u32 = u32; ++ SET_BITS(u32); ++ assert_se(_u32 == u32); ++ CLEAR_BITS(u32); ++ assert_se(_u32 == u32); ++ ++ /* Test uint64_t */ ++ TEST_BIT_SET(0x18ba1400f4857460, 60); ++ TEST_BITS_SET(0x18ba1400f4857460, 5, 6, 10, 12, 13, 14, 16, 18, 23, 26, 28, 29, 30, 31, 42, 44, 49, 51, 52, 53, 55, 59, 60); ++ TEST_BIT_CLEAR(UINT64_C(0x18ba1400f4857460), 0); ++ TEST_BIT_CLEAR(UINT64_C(0x18ba1400f4857460), 63); ++ TEST_BITS_CLEAR(UINT64_C(0x18ba1400f4857460), 0, 1, 2, 3, 4, 7, 8, 9, 11, 15, 17, 19, 20, 21, 22, 24, 25, 27, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 43, 45, 46, 47, 48, 50, 54, 56, 57, 58, 61, 62, 63); ++ uint64_t expected64 = 0; ++ BIT_FOREACH(i, 0x18ba1400f4857460) ++ expected64 |= UINT64_C(1) << i; ++ assert_se(expected64 == 0x18ba1400f4857460); ++ uint64_t u64 = 0xa90e2d8507a65739; ++ TEST_BIT_SET(u64, 0); ++ TEST_BIT_SET(u64, 63); ++ TEST_BITS_SET(u64, 0, 3, 4, 5, 8, 9, 10, 12, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 63); ++ TEST_BIT_CLEAR(u64, 1); ++ TEST_BITS_CLEAR(u64, 1, 2, 6, 7, 11, 13, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 62); ++ SET_BIT(u64, 1); ++ TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 8, 9, 10, 12, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 63); ++ TEST_BITS_CLEAR(u64, 2, 6, 7, 11, 13, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 62); ++ CLEAR_BIT(u64, 63); ++ TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 8, 9, 10, 12, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61); ++ TEST_BITS_CLEAR(u64, 2, 6, 7, 11, 13, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 62, 63); ++ SET_BIT(u64, 62); ++ TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 8, 9, 10, 12, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 62); ++ TEST_BITS_CLEAR(u64, 2, 6, 7, 11, 13, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 38, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 63); ++ SET_BITS(u64, 63, 62, 7, 13, 38, 40); ++ TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 7, 8, 9, 10, 12, 13, 14, 17, 18, 21, 23, 24, 25, 26, 32, 34, 38, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 62, 63); ++ TEST_BITS_CLEAR(u64, 2, 6, 11, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 33, 35, 36, 37, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60); ++ CLEAR_BIT(u64, 32); ++ TEST_BITS_SET(u64, 0, 1, 3, 4, 5, 7, 8, 9, 10, 12, 13, 14, 17, 18, 21, 23, 24, 25, 26, 34, 38, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 62, 63); ++ TEST_BITS_CLEAR(u64, 2, 6, 11, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60); ++ CLEAR_BITS(u64, 0, 2, 11, 63, 32, 58); ++ TEST_BITS_SET(u64, 1, 3, 4, 5, 7, 8, 9, 10, 12, 13, 14, 17, 18, 21, 23, 24, 25, 26, 34, 38, 39, 40, 42, 43, 45, 49, 50, 51, 56, 59, 61, 62); ++ TEST_BITS_CLEAR(u64, 0, 2, 6, 11, 15, 16, 19, 20, 22, 27, 28, 29, 30, 31, 32, 33, 35, 36, 37, 41, 44, 46, 47, 48, 52, 53, 54, 55, 57, 58, 60, 63); ++ expected64 = 0; ++ BIT_FOREACH(i, u64) ++ expected64 |= UINT64_C(1) << i; ++ assert_se(expected64 == u64); ++ u64 = 0; ++ TEST_BITS_CLEAR(u64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63); ++ BIT_FOREACH(i, u64) ++ assert_se(0); ++ u64 = ~u64; ++ TEST_BITS_SET(u64, 0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16, 17, 18, 19, 20, 21, 22, 23, 24, 25, 26, 27, 28, 29, 30, 31, 32, 33, 34, 35, 36, 37, 38, 39, 40, 41, 42, 43, 44, 45, 46, 47, 48, 49, 50, 51, 52, 53, 54, 55, 56, 57, 58, 59, 60, 61, 62, 63); ++ count = 0; ++ BIT_FOREACH(i, u64) ++ count++; ++ assert_se(count == 64); ++ uint64_t _u64 = u64; ++ SET_BITS(u64); ++ assert_se(_u64 == u64); ++ CLEAR_BITS(u64); ++ assert_se(_u64 == u64); ++ ++ /* Verify these use cases are constant-folded. */ ++ assert_cc(__builtin_constant_p(INDEX_TO_MASK(uint8_t, 1))); ++ assert_cc(__builtin_constant_p(INDEX_TO_MASK(uint16_t, 1))); ++ assert_cc(__builtin_constant_p(INDEX_TO_MASK(uint32_t, 1))); ++ assert_cc(__builtin_constant_p(INDEX_TO_MASK(uint64_t, 1))); ++ ++ assert_cc(__builtin_constant_p(BIT_SET((uint8_t)2, 1))); ++ assert_cc(__builtin_constant_p(BIT_SET((uint16_t)2, 1))); ++ assert_cc(__builtin_constant_p(BIT_SET((uint32_t)2, 1))); ++ assert_cc(__builtin_constant_p(BIT_SET((uint64_t)2, 1))); ++} ++ ++DEFINE_TEST_MAIN(LOG_INFO); diff --git a/SOURCES/0508-tpm2-add-tpm2_get_policy_digest.patch b/SOURCES/0508-tpm2-add-tpm2_get_policy_digest.patch new file mode 100644 index 0000000..66f2b0e --- /dev/null +++ b/SOURCES/0508-tpm2-add-tpm2_get_policy_digest.patch @@ -0,0 +1,182 @@ +From ba5a271dbb71edc1985b7d9f837d7904ad2a4529 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 16 Dec 2022 16:33:08 -0500 +Subject: [PATCH] tpm2: add tpm2_get_policy_digest() + +(cherry picked from commit 23b972d571650014ab5f22610da80a62f53f2245) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 99 ++++++++++++++++++++++++------------------ + 1 file changed, 57 insertions(+), 42 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index c22a200a5c..b5eabb8159 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -476,6 +476,54 @@ void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION + }; + } + ++static void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg) { ++ if (!DEBUG_LOGGING || !buffer || size == 0) ++ return; ++ ++ _cleanup_free_ char *h = hexmem(buffer, size); ++ log_debug("%s: %s", msg ?: "Buffer", strna(h)); ++} ++ ++static void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg) { ++ if (digest) ++ tpm2_log_debug_buffer(digest->buffer, digest->size, msg ?: "Digest"); ++} ++ ++static int tpm2_get_policy_digest( ++ Tpm2Context *c, ++ const Tpm2Handle *session, ++ TPM2B_DIGEST **ret_policy_digest) { ++ ++ TSS2_RC rc; ++ ++ if (!DEBUG_LOGGING && !ret_policy_digest) ++ return 0; ++ ++ assert(c); ++ assert(session); ++ ++ log_debug("Acquiring policy digest."); ++ ++ _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; ++ rc = sym_Esys_PolicyGetDigest( ++ c->esys_context, ++ session->esys_handle, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ &policy_digest); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc)); ++ ++ tpm2_log_debug_digest(policy_digest, "Session policy digest"); ++ ++ if (ret_policy_digest) ++ *ret_policy_digest = TAKE_PTR(policy_digest); ++ ++ return 0; ++} ++ + static unsigned find_nth_bit(uint32_t mask, unsigned n) { + uint32_t bit = 1; + +@@ -1100,7 +1148,6 @@ static int tpm2_make_policy_session( + .keyBits.aes = 128, + .mode.aes = TPM2_ALG_CFB, + }; +- _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; + TSS2_RC rc; + int r; + +@@ -1237,16 +1284,9 @@ static int tpm2_make_policy_session( + + /* Get the policy hash of the PCR policy */ + _cleanup_(Esys_Freep) TPM2B_DIGEST *approved_policy = NULL; +- rc = sym_Esys_PolicyGetDigest( +- c->esys_context, +- session->esys_handle, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &approved_policy); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc)); ++ r = tpm2_get_policy_digest(c, session, &approved_policy); ++ if (r < 0) ++ return r; + + /* When we are unlocking and have a signature, let's pass it to the TPM */ + _cleanup_(Esys_Freep) TPMT_TK_VERIFIED *check_ticket_buffer = NULL; +@@ -1361,38 +1401,13 @@ static int tpm2_make_policy_session( + sym_Tss2_RC_Decode(rc)); + } + +- if (DEBUG_LOGGING || ret_policy_digest) { +- log_debug("Acquiring policy digest."); +- +- rc = sym_Esys_PolicyGetDigest( +- c->esys_context, +- session->esys_handle, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &policy_digest); +- +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc)); +- +- if (DEBUG_LOGGING) { +- _cleanup_free_ char *h = NULL; +- +- h = hexmem(policy_digest->buffer, policy_digest->size); +- if (!h) +- return log_oom(); +- +- log_debug("Session policy digest: %s", h); +- } +- } ++ r = tpm2_get_policy_digest(c, session, ret_policy_digest); ++ if (r < 0) ++ return r; + + if (ret_session) + *ret_session = TAKE_PTR(session); + +- if (ret_policy_digest) +- *ret_policy_digest = TAKE_PTR(policy_digest); +- + if (ret_pcr_bank) + *ret_pcr_bank = pcr_bank; + +@@ -1414,7 +1429,6 @@ int tpm2_seal(const char *device, + uint16_t *ret_pcr_bank, + uint16_t *ret_primary_alg) { + +- _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; + _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; + _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; + static const TPML_PCR_SELECTION creation_pcr = {}; +@@ -1423,7 +1437,6 @@ int tpm2_seal(const char *device, + TPM2B_SENSITIVE_CREATE hmac_sensitive; + TPMI_ALG_PUBLIC primary_alg; + TPM2B_PUBLIC hmac_template; +- TPMI_ALG_HASH pcr_bank; + usec_t start; + TSS2_RC rc; + int r; +@@ -1477,6 +1490,8 @@ int tpm2_seal(const char *device, + if (r < 0) + return r; + ++ _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; ++ TPMI_ALG_HASH pcr_bank; + r = tpm2_make_policy_session( + c, + primary, +@@ -1608,7 +1623,6 @@ int tpm2_unseal(const char *device, + size_t *ret_secret_size) { + + _cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL; +- _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; + _cleanup_(erase_and_freep) char *secret = NULL; + TPM2B_PRIVATE private = {}; + TPM2B_PUBLIC public = {}; +@@ -1708,6 +1722,7 @@ int tpm2_unseal(const char *device, + + for (unsigned i = RETRY_UNSEAL_MAX;; i--) { + _cleanup_tpm2_handle_ Tpm2Handle *policy_session = NULL; ++ _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; + r = tpm2_make_policy_session( + c, + primary, diff --git a/SOURCES/0509-tpm2-add-TPM2_PCR_VALID.patch b/SOURCES/0509-tpm2-add-TPM2_PCR_VALID.patch new file mode 100644 index 0000000..c5283ad --- /dev/null +++ b/SOURCES/0509-tpm2-add-TPM2_PCR_VALID.patch @@ -0,0 +1,90 @@ +From 926160f00356c562eea2d87bb88c1cd3febe0655 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 27 Feb 2023 06:44:13 -0500 +Subject: [PATCH] tpm2: add TPM2_PCR_VALID() + +(cherry picked from commit aa07a4fa353d758562c4bec8c7d3b1d44b55e573) + +Related: RHEL-16182 +--- + .../cryptsetup-token-systemd-tpm2.c | 2 +- + src/cryptsetup/cryptsetup.c | 2 +- + src/shared/tpm2-util.c | 2 +- + src/shared/tpm2-util.h | 18 ++++++++++++------ + 4 files changed, 15 insertions(+), 9 deletions(-) + +diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +index 319b0ca64d..e8bc091191 100644 +--- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +@@ -271,7 +271,7 @@ _public_ int cryptsetup_token_validate( + } + + u = json_variant_unsigned(e); +- if (u >= TPM2_PCRS_MAX) { ++ if (!TPM2_PCR_VALID(u)) { + crypt_log_debug(cd, "TPM2 PCR number out of range."); + return 1; + } +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index d5ce252e57..d46a88c9fb 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -438,7 +438,7 @@ static int parse_one_option(const char *option) { + } + + pcr = r ? TPM_PCR_INDEX_VOLUME_KEY : UINT_MAX; +- } else if (pcr >= TPM2_PCRS_MAX) { ++ } else if (!TPM2_PCR_VALID(pcr)) { + log_error("Selected TPM index for measurement %u outside of allowed range 0…%u, ignoring.", pcr, TPM2_PCRS_MAX-1); + return 0; + } +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index b5eabb8159..0cbb32f819 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -462,7 +462,7 @@ void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION + assert(ret); + + /* We only do 24bit here, as that's what PC TPMs are supposed to support */ +- assert(mask <= 0xFFFFFFU); ++ assert(TPM2_PCR_MASK_VALID(mask)); + + *ret = (TPML_PCR_SELECTION) { + .count = 1, +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index d26a945a90..07a8a89800 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -11,6 +11,18 @@ typedef enum TPM2Flags { + TPM2_FLAGS_USE_PIN = 1 << 0, + } TPM2Flags; + ++ ++/* As per https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a ++ * TPM2 on a Client PC must have at least 24 PCRs. This hardcodes our expectation of 24. */ ++#define TPM2_PCRS_MAX 24U ++#define TPM2_PCRS_MASK ((UINT32_C(1) << TPM2_PCRS_MAX) - 1) ++static inline bool TPM2_PCR_VALID(unsigned pcr) { ++ return pcr < TPM2_PCRS_MAX; ++} ++static inline bool TPM2_PCR_MASK_VALID(uint32_t pcr_mask) { ++ return pcr_mask <= TPM2_PCRS_MASK; ++} ++ + #if HAVE_TPM2 + + #include +@@ -108,12 +120,6 @@ int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret); + int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, TPM2Flags flags, JsonVariant **ret); + int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, TPM2Flags *ret_flags); + +-#define TPM2_PCRS_MAX 24U +- +-static inline bool TPM2_PCR_MASK_VALID(uint64_t pcr_mask) { +- return pcr_mask < (UINT64_C(1) << TPM2_PCRS_MAX); /* Support 24 PCR banks */ +-} +- + /* Default to PCR 7 only */ + #define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7) + diff --git a/SOURCES/0510-tpm2-add-rename-functions-to-manage-pcr-selections.patch b/SOURCES/0510-tpm2-add-rename-functions-to-manage-pcr-selections.patch new file mode 100644 index 0000000..e38e705 --- /dev/null +++ b/SOURCES/0510-tpm2-add-rename-functions-to-manage-pcr-selections.patch @@ -0,0 +1,629 @@ +From 90d9f2996c10a2be090fd89be7409c81eabceb4c Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 6 Feb 2023 11:31:59 -0500 +Subject: [PATCH] tpm2: add/rename functions to manage pcr selections + +This renames some functions to match other to/from_string() naming, +and allows better management of TPML_PCR_SELECTION and TPMS_PCR_SELECTION +structs. + +(cherry picked from commit c69bd0abdbd06ee89068227c67890358f5764c3d) + +Related: RHEL-16182 +--- + src/boot/measure.c | 4 +- + .../cryptsetup-token-systemd-tpm2.c | 12 +- + src/shared/tpm2-util.c | 350 +++++++++++++++--- + src/shared/tpm2-util.h | 31 +- + src/test/test-tpm2.c | 32 +- + 5 files changed, 358 insertions(+), 71 deletions(-) + +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 65a48a01cd..86edf77c52 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -844,7 +844,9 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + return log_error_errno(tpmalg, "Unsupported PCR bank"); + + TPML_PCR_SELECTION pcr_selection; +- tpm2_pcr_mask_to_selection(1 << TPM_PCR_INDEX_KERNEL_IMAGE, tpmalg, &pcr_selection); ++ tpm2_tpml_pcr_selection_from_mask(1 << TPM_PCR_INDEX_KERNEL_IMAGE, ++ tpmalg, ++ &pcr_selection); + + rc = sym_Esys_PolicyPCR( + c->esys_context, +diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +index e8bc091191..b5d66e389d 100644 +--- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +@@ -205,13 +205,13 @@ _public_ void cryptsetup_token_dump( + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m"); + +- r = pcr_mask_to_string(hash_pcr_mask, &hash_pcrs_str); +- if (r < 0) +- return (void) crypt_log_debug_errno(cd, r, "Cannot format PCR hash mask: %m"); ++ hash_pcrs_str = tpm2_pcr_mask_to_string(hash_pcr_mask); ++ if (!hash_pcrs_str) ++ return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m"); + +- r = pcr_mask_to_string(pubkey_pcr_mask, &pubkey_pcrs_str); +- if (r < 0) +- return (void) crypt_log_debug_errno(cd, r, "Cannot format PCR hash mask: %m"); ++ pubkey_pcrs_str = tpm2_pcr_mask_to_string(pubkey_pcr_mask); ++ if (!pubkey_pcrs_str) ++ return (void) crypt_log_debug_errno(cd, ENOMEM, "Cannot format PCR hash mask: %m"); + + r = crypt_dump_buffer_to_hex_string(blob, blob_size, &blob_str); + if (r < 0) +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 0cbb32f819..cf62524e34 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -458,24 +458,292 @@ static int tpm2_make_primary( + return 0; + } + +-void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret) { ++/* Utility functions for TPMS_PCR_SELECTION. */ ++ ++/* Convert a TPMS_PCR_SELECTION object to a mask. */ ++void tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s, uint32_t *ret) { ++ assert(s); ++ assert(s->sizeofSelect <= sizeof(s->pcrSelect)); + assert(ret); + +- /* We only do 24bit here, as that's what PC TPMs are supposed to support */ +- assert(TPM2_PCR_MASK_VALID(mask)); ++ uint32_t mask = 0; ++ for (unsigned i = 0; i < s->sizeofSelect; i++) ++ SET_FLAG(mask, (uint32_t)s->pcrSelect[i] << (i * 8), true); ++ *ret = mask; ++} + +- *ret = (TPML_PCR_SELECTION) { +- .count = 1, +- .pcrSelections[0] = { +- .hash = bank, +- .sizeofSelect = 3, +- .pcrSelect[0] = mask & 0xFF, +- .pcrSelect[1] = (mask >> 8) & 0xFF, +- .pcrSelect[2] = (mask >> 16) & 0xFF, ++/* Convert a mask and hash alg to a TPMS_PCR_SELECTION object. */ ++void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash_alg, TPMS_PCR_SELECTION *ret) { ++ assert(ret); ++ ++ /* This is currently hardcoded at 24 PCRs, above. */ ++ if (!TPM2_PCR_MASK_VALID(mask)) ++ log_warning("PCR mask selections (%x) out of range, ignoring.", ++ mask & ~((uint32_t)TPM2_PCRS_MASK)); ++ ++ *ret = (TPMS_PCR_SELECTION){ ++ .hash = hash_alg, ++ .sizeofSelect = TPM2_PCRS_MAX / 8, ++ .pcrSelect[0] = mask & 0xff, ++ .pcrSelect[1] = (mask >> 8) & 0xff, ++ .pcrSelect[2] = (mask >> 16) & 0xff, ++ }; ++} ++ ++/* Add all PCR selections in 'b' to 'a'. Both must have the same hash alg. */ ++void tpm2_tpms_pcr_selection_add(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b) { ++ assert(a); ++ assert(b); ++ assert(a->hash == b->hash); ++ ++ uint32_t maska, maskb; ++ tpm2_tpms_pcr_selection_to_mask(a, &maska); ++ tpm2_tpms_pcr_selection_to_mask(b, &maskb); ++ tpm2_tpms_pcr_selection_from_mask(maska | maskb, a->hash, a); ++} ++ ++/* Remove all PCR selections in 'b' from 'a'. Both must have the same hash alg. */ ++void tpm2_tpms_pcr_selection_sub(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b) { ++ assert(a); ++ assert(b); ++ assert(a->hash == b->hash); ++ ++ uint32_t maska, maskb; ++ tpm2_tpms_pcr_selection_to_mask(a, &maska); ++ tpm2_tpms_pcr_selection_to_mask(b, &maskb); ++ tpm2_tpms_pcr_selection_from_mask(maska & ~maskb, a->hash, a); ++} ++ ++/* Move all PCR selections in 'b' to 'a'. Both must have the same hash alg. */ ++void tpm2_tpms_pcr_selection_move(TPMS_PCR_SELECTION *a, TPMS_PCR_SELECTION *b) { ++ if (a == b) ++ return; ++ ++ tpm2_tpms_pcr_selection_add(a, b); ++ tpm2_tpms_pcr_selection_from_mask(0, b->hash, b); ++} ++ ++#define FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms) \ ++ _FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms, UNIQ) ++#define _FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms, uniq) \ ++ FOREACH_PCR_IN_MASK(pcr, \ ++ ({ uint32_t UNIQ_T(_mask, uniq); \ ++ tpm2_tpms_pcr_selection_to_mask(tpms, &UNIQ_T(_mask, uniq)); \ ++ UNIQ_T(_mask, uniq); \ ++ })) ++ ++#define FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml) \ ++ UNIQ_FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml, UNIQ) ++#define UNIQ_FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml, uniq) \ ++ for (TPML_PCR_SELECTION *UNIQ_T(_tpml, uniq) = (TPML_PCR_SELECTION*)(tpml); \ ++ UNIQ_T(_tpml, uniq); UNIQ_T(_tpml, uniq) = NULL) \ ++ _FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, UNIQ_T(_tpml, uniq)) ++#define _FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml) \ ++ for (TPMS_PCR_SELECTION *tpms = tpml->pcrSelections; \ ++ (uint32_t)(tpms - tpml->pcrSelections) < tpml->count; \ ++ tpms++) ++ ++#define FOREACH_PCR_IN_TPML_PCR_SELECTION(pcr, tpms, tpml) \ ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml) \ ++ FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms) ++ ++char *tpm2_tpms_pcr_selection_to_string(const TPMS_PCR_SELECTION *s) { ++ assert(s); ++ ++ const char *algstr = strna(tpm2_hash_alg_to_string(s->hash)); ++ ++ uint32_t mask; ++ tpm2_tpms_pcr_selection_to_mask(s, &mask); ++ _cleanup_free_ char *maskstr = tpm2_pcr_mask_to_string(mask); ++ if (!maskstr) ++ return NULL; ++ ++ return strjoin(algstr, "(", maskstr, ")"); ++} ++ ++size_t tpm2_tpms_pcr_selection_weight(const TPMS_PCR_SELECTION *s) { ++ assert(s); ++ ++ uint32_t mask; ++ tpm2_tpms_pcr_selection_to_mask(s, &mask); ++ return (size_t)__builtin_popcount(mask); ++} ++ ++/* Utility functions for TPML_PCR_SELECTION. */ ++ ++/* Remove the (0-based) index entry from 'l', shift all following entries, and update the count. */ ++static void tpm2_tpml_pcr_selection_remove_index(TPML_PCR_SELECTION *l, uint32_t index) { ++ assert(l); ++ assert(l->count <= sizeof(l->pcrSelections)); ++ assert(index < l->count); ++ ++ size_t s = l->count - (index + 1); ++ memmove(&l->pcrSelections[index], &l->pcrSelections[index + 1], s * sizeof(l->pcrSelections[0])); ++ l->count--; ++} ++ ++/* Get a TPMS_PCR_SELECTION from a TPML_PCR_SELECTION for the given hash alg. Returns NULL if there is no ++ * entry for the hash alg. This guarantees the returned entry contains all the PCR selections for the given ++ * hash alg, which may require modifying the TPML_PCR_SELECTION by removing duplicate entries. */ ++static TPMS_PCR_SELECTION *tpm2_tpml_pcr_selection_get_tpms_pcr_selection( ++ TPML_PCR_SELECTION *l, ++ TPMI_ALG_HASH hash_alg) { ++ ++ assert(l); ++ ++ TPMS_PCR_SELECTION *selection = NULL; ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(s, l) ++ if (s->hash == hash_alg) { ++ selection = s; ++ break; ++ } ++ ++ if (!selection) ++ return NULL; ++ ++ /* Iterate backwards through the entries, removing any other entries for the hash alg. */ ++ for (uint32_t i = l->count - 1; i > 0; i--) { ++ TPMS_PCR_SELECTION *s = &l->pcrSelections[i]; ++ ++ if (selection == s) ++ break; ++ ++ if (s->hash == hash_alg) { ++ tpm2_tpms_pcr_selection_move(selection, s); ++ tpm2_tpml_pcr_selection_remove_index(l, i); + } ++ } ++ ++ return selection; ++} ++ ++/* Convert a TPML_PCR_SELECTION object to a mask. Returns -ENOENT if 'hash_alg' is not in the object. */ ++int tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash_alg, uint32_t *ret) { ++ assert(l); ++ assert(ret); ++ ++ /* Make a copy, as tpm2_tpml_pcr_selection_get_tpms_pcr_selection() will modify the object if there ++ * are multiple entries with the requested hash alg. */ ++ TPML_PCR_SELECTION lcopy = *l; ++ ++ TPMS_PCR_SELECTION *s; ++ s = tpm2_tpml_pcr_selection_get_tpms_pcr_selection(&lcopy, hash_alg); ++ if (!s) ++ return SYNTHETIC_ERRNO(ENOENT); ++ ++ tpm2_tpms_pcr_selection_to_mask(s, ret); ++ return 0; ++} ++ ++/* Convert a mask and hash alg to a TPML_PCR_SELECTION object. */ ++void tpm2_tpml_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash_alg, TPML_PCR_SELECTION *ret) { ++ assert(ret); ++ ++ TPMS_PCR_SELECTION s; ++ tpm2_tpms_pcr_selection_from_mask(mask, hash_alg, &s); ++ ++ *ret = (TPML_PCR_SELECTION){ ++ .count = 1, ++ .pcrSelections[0] = s, + }; + } + ++/* Combine all duplicate (same hash alg) TPMS_PCR_SELECTION entries in 'l'. */ ++static void tpm2_tpml_pcr_selection_cleanup(TPML_PCR_SELECTION *l) { ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(s, l) ++ /* This removes all duplicates for s->hash. */ ++ (void) tpm2_tpml_pcr_selection_get_tpms_pcr_selection(l, s->hash); ++} ++ ++/* Add the PCR selections in 's' to the corresponding hash alg TPMS_PCR_SELECTION entry in 'l'. Adds a new ++ * TPMS_PCR_SELECTION entry for the hash alg if needed. This may modify the TPML_PCR_SELECTION by combining ++ * entries with the same hash alg. */ ++void tpm2_tpml_pcr_selection_add_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s) { ++ assert(l); ++ assert(s); ++ ++ if (tpm2_tpms_pcr_selection_is_empty(s)) ++ return; ++ ++ TPMS_PCR_SELECTION *selection = tpm2_tpml_pcr_selection_get_tpms_pcr_selection(l, s->hash); ++ if (selection) { ++ tpm2_tpms_pcr_selection_add(selection, s); ++ return; ++ } ++ ++ /* It's already broken if the count is higher than the array has size for. */ ++ assert(!(l->count > sizeof(l->pcrSelections))); ++ ++ /* If full, the cleanup should result in at least one available entry. */ ++ if (l->count == sizeof(l->pcrSelections)) ++ tpm2_tpml_pcr_selection_cleanup(l); ++ ++ assert(l->count < sizeof(l->pcrSelections)); ++ l->pcrSelections[l->count++] = *s; ++} ++ ++/* Remove the PCR selections in 's' from the corresponding hash alg TPMS_PCR_SELECTION entry in 'l'. This ++ * will combine all entries for 's->hash' in 'l'. */ ++void tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s) { ++ assert(l); ++ assert(s); ++ ++ if (tpm2_tpms_pcr_selection_is_empty(s)) ++ return; ++ ++ TPMS_PCR_SELECTION *selection = tpm2_tpml_pcr_selection_get_tpms_pcr_selection(l, s->hash); ++ if (selection) ++ tpm2_tpms_pcr_selection_sub(selection, s); ++} ++ ++/* Add all PCR selections in 'b' to 'a'. */ ++void tpm2_tpml_pcr_selection_add(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION *b) { ++ assert(a); ++ assert(b); ++ ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection_b, (TPML_PCR_SELECTION*) b) ++ tpm2_tpml_pcr_selection_add_tpms_pcr_selection(a, selection_b); ++} ++ ++/* Remove all PCR selections in 'b' from 'a'. */ ++void tpm2_tpml_pcr_selection_sub(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION *b) { ++ assert(a); ++ assert(b); ++ ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection_b, (TPML_PCR_SELECTION*) b) ++ tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(a, selection_b); ++} ++ ++char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l) { ++ assert(l); ++ ++ _cleanup_free_ char *banks = NULL; ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(s, (TPML_PCR_SELECTION*) l) { ++ if (tpm2_tpms_pcr_selection_is_empty(s)) ++ continue; ++ ++ _cleanup_free_ char *str = tpm2_tpms_pcr_selection_to_string(s); ++ if (!str || !strextend_with_separator(&banks, ",", str)) ++ return NULL; ++ } ++ ++ return strjoin("[", strempty(banks), "]"); ++} ++ ++size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l) { ++ assert(l); ++ assert(l->count <= sizeof(l->pcrSelections)); ++ ++ size_t weight = 0; ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(s, l) { ++ size_t w = tpm2_tpms_pcr_selection_weight(s); ++ assert(weight <= SIZE_MAX - w); ++ weight += w; ++ } ++ ++ return weight; ++} ++ + static void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg) { + if (!DEBUG_LOGGING || !buffer || size == 0) + return; +@@ -562,7 +830,7 @@ static int tpm2_pcr_mask_good( + * actually measure into them, or only into a suboptimal bank. If so, the PCRs should be all zero or + * all 0xFF. Detect that, so that we can warn and maybe pick a better bank. */ + +- tpm2_pcr_mask_to_selection(mask, bank, &selection); ++ tpm2_tpml_pcr_selection_from_mask(mask, bank, &selection); + + rc = sym_Esys_PCR_Read( + c->esys_context, +@@ -1269,7 +1537,7 @@ static int tpm2_make_policy_session( + + /* Put together the PCR policy we want to use */ + TPML_PCR_SELECTION pcr_selection; +- tpm2_pcr_mask_to_selection(pubkey_pcr_mask, pcr_bank, &pcr_selection); ++ tpm2_tpml_pcr_selection_from_mask(pubkey_pcr_mask, (TPMI_ALG_HASH)pcr_bank, &pcr_selection); + rc = sym_Esys_PolicyPCR( + c->esys_context, + session->esys_handle, +@@ -1372,7 +1640,7 @@ static int tpm2_make_policy_session( + log_debug("Configuring hash-based PCR policy."); + + TPML_PCR_SELECTION pcr_selection; +- tpm2_pcr_mask_to_selection(hash_pcr_mask, pcr_bank, &pcr_selection); ++ tpm2_tpml_pcr_selection_from_mask(hash_pcr_mask, (TPMI_ALG_HASH)pcr_bank, &pcr_selection); + rc = sym_Esys_PolicyPCR( + c->esys_context, + session->esys_handle, +@@ -1989,13 +2257,28 @@ int tpm2_extend_bytes( + } + #endif + +-int tpm2_parse_pcrs(const char *s, uint32_t *ret) { +- const char *p = ASSERT_PTR(s); ++char *tpm2_pcr_mask_to_string(uint32_t mask) { ++ _cleanup_free_ char *s = NULL; ++ ++ FOREACH_PCR_IN_MASK(n, mask) ++ if (strextendf_with_separator(&s, "+", "%d", n) < 0) ++ return NULL; ++ ++ if (!s) ++ return strdup(""); ++ ++ return TAKE_PTR(s); ++} ++ ++int tpm2_pcr_mask_from_string(const char *arg, uint32_t *ret_mask) { + uint32_t mask = 0; + int r; + +- if (isempty(s)) { +- *ret = 0; ++ assert(arg); ++ assert(ret_mask); ++ ++ if (isempty(arg)) { ++ *ret_mask = 0; + return 0; + } + +@@ -2004,6 +2287,7 @@ int tpm2_parse_pcrs(const char *s, uint32_t *ret) { + * /etc/crypttab the "," is already used to separate options, hence a different separator is nice to + * avoid escaping. */ + ++ const char *p = arg; + for (;;) { + _cleanup_free_ char *pcr = NULL; + unsigned n; +@@ -2012,19 +2296,20 @@ int tpm2_parse_pcrs(const char *s, uint32_t *ret) { + if (r == 0) + break; + if (r < 0) +- return log_error_errno(r, "Failed to parse PCR list: %s", s); ++ return log_error_errno(r, "Failed to parse PCR list: %s", arg); + + r = safe_atou(pcr, &n); + if (r < 0) + return log_error_errno(r, "Failed to parse PCR number: %s", pcr); + if (n >= TPM2_PCRS_MAX) + return log_error_errno(SYNTHETIC_ERRNO(ERANGE), +- "PCR number out of range (valid range 0…23): %u", n); ++ "PCR number out of range (valid range 0…%u): %u", ++ TPM2_PCRS_MAX - 1, n); + +- mask |= UINT32_C(1) << n; ++ SET_BIT(mask, n);; + } + +- *ret = mask; ++ *ret_mask = mask; + return 0; + } + +@@ -2389,7 +2674,7 @@ int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask) { + return 0; + } + +- r = tpm2_parse_pcrs(arg, &m); ++ r = tpm2_pcr_mask_from_string(arg, &m); + if (r < 0) + return r; + +@@ -2445,25 +2730,6 @@ int tpm2_load_pcr_public_key(const char *path, void **ret_pubkey, size_t *ret_pu + return 0; + } + +-int pcr_mask_to_string(uint32_t mask, char **ret) { +- _cleanup_free_ char *buf = NULL; +- int r; +- +- assert(ret); +- +- for (unsigned i = 0; i < TPM2_PCRS_MAX; i++) { +- if (!(mask & (UINT32_C(1) << i))) +- continue; +- +- r = strextendf_with_separator(&buf, "+", "%u", i); +- if (r < 0) +- return r; +- } +- +- *ret = TAKE_PTR(buf); +- return 0; +-} +- + #define PBKDF2_HMAC_SHA256_ITERATIONS 10000 + + /* +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 07a8a89800..c2532c61c2 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -3,6 +3,7 @@ + + #include + ++#include "bitfield.h" + #include "json.h" + #include "macro.h" + #include "sha256.h" +@@ -23,6 +24,8 @@ static inline bool TPM2_PCR_MASK_VALID(uint32_t pcr_mask) { + return pcr_mask <= TPM2_PCRS_MASK; + } + ++#define FOREACH_PCR_IN_MASK(pcr, mask) BIT_FOREACH(pcr, mask) ++ + #if HAVE_TPM2 + + #include +@@ -92,8 +95,6 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle); + DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free); + #define _cleanup_tpm2_handle_ _cleanup_(tpm2_handle_freep) + +-void tpm2_pcr_mask_to_selection(uint32_t mask, uint16_t bank, TPML_PCR_SELECTION *ret); +- + static inline void Esys_Freep(void *p) { + if (*(void**) p) + sym_Esys_Free(*(void**) p); +@@ -104,6 +105,25 @@ int tpm2_get_good_pcr_banks_strv(Tpm2Context *c, uint32_t pcr_mask, char ***ret) + + int tpm2_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size); + ++void tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s, uint32_t *ret); ++void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPMS_PCR_SELECTION *ret); ++void tpm2_tpms_pcr_selection_add(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b); ++void tpm2_tpms_pcr_selection_sub(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b); ++void tpm2_tpms_pcr_selection_move(TPMS_PCR_SELECTION *a, TPMS_PCR_SELECTION *b); ++char *tpm2_tpms_pcr_selection_to_string(const TPMS_PCR_SELECTION *s); ++size_t tpm2_tpms_pcr_selection_weight(const TPMS_PCR_SELECTION *s); ++#define tpm2_tpms_pcr_selection_is_empty(s) (tpm2_tpms_pcr_selection_weight(s) == 0) ++ ++int tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t *ret); ++void tpm2_tpml_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPML_PCR_SELECTION *ret); ++void tpm2_tpml_pcr_selection_add_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s); ++void tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s); ++void tpm2_tpml_pcr_selection_add(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION *b); ++void tpm2_tpml_pcr_selection_sub(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION *b); ++char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l); ++size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l); ++#define tpm2_tpml_pcr_selection_is_empty(l) (tpm2_tpml_pcr_selection_weight(l) == 0) ++ + #else /* HAVE_TPM2 */ + typedef struct {} Tpm2Context; + typedef struct {} Tpm2Handle; +@@ -112,8 +132,6 @@ typedef struct {} Tpm2Handle; + int tpm2_list_devices(void); + int tpm2_find_device_auto(int log_level, char **ret); + +-int tpm2_parse_pcrs(const char *s, uint32_t *ret); +- + int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret); + int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret); + +@@ -155,6 +173,9 @@ int tpm2_hash_alg_from_string(const char *alg); + const char *tpm2_asym_alg_to_string(uint16_t alg); + int tpm2_asym_alg_from_string(const char *alg); + ++char *tpm2_pcr_mask_to_string(uint32_t mask); ++int tpm2_pcr_mask_from_string(const char *arg, uint32_t *mask); ++ + typedef struct { + uint32_t search_pcr_mask; + const char *device; +@@ -179,8 +200,6 @@ int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask); + int tpm2_load_pcr_signature(const char *path, JsonVariant **ret); + int tpm2_load_pcr_public_key(const char *path, void **ret_pubkey, size_t *ret_pubkey_size); + +-int pcr_mask_to_string(uint32_t mask, char **ret); +- + int tpm2_util_pbkdf2_hmac_sha256(const void *pass, + size_t passlen, + const void *salt, +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 04e08490b3..23277449b5 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -3,29 +3,29 @@ + #include "tpm2-util.h" + #include "tests.h" + +-static void test_tpm2_parse_pcrs_one(const char *s, uint32_t mask, int ret) { ++static void test_tpm2_pcr_mask_from_string_one(const char *s, uint32_t mask, int ret) { + uint32_t m; + +- assert_se(tpm2_parse_pcrs(s, &m) == ret); ++ assert_se(tpm2_pcr_mask_from_string(s, &m) == ret); + + if (ret >= 0) + assert_se(m == mask); + } + +-TEST(tpm2_parse_pcrs) { +- test_tpm2_parse_pcrs_one("", 0, 0); +- test_tpm2_parse_pcrs_one("0", 1, 0); +- test_tpm2_parse_pcrs_one("1", 2, 0); +- test_tpm2_parse_pcrs_one("0,1", 3, 0); +- test_tpm2_parse_pcrs_one("0+1", 3, 0); +- test_tpm2_parse_pcrs_one("0-1", 0, -EINVAL); +- test_tpm2_parse_pcrs_one("0,1,2", 7, 0); +- test_tpm2_parse_pcrs_one("0+1+2", 7, 0); +- test_tpm2_parse_pcrs_one("0+1,2", 7, 0); +- test_tpm2_parse_pcrs_one("0,1+2", 7, 0); +- test_tpm2_parse_pcrs_one("0,2", 5, 0); +- test_tpm2_parse_pcrs_one("0+2", 5, 0); +- test_tpm2_parse_pcrs_one("foo", 0, -EINVAL); ++TEST(tpm2_mask_from_string) { ++ test_tpm2_pcr_mask_from_string_one("", 0, 0); ++ test_tpm2_pcr_mask_from_string_one("0", 1, 0); ++ test_tpm2_pcr_mask_from_string_one("1", 2, 0); ++ test_tpm2_pcr_mask_from_string_one("0,1", 3, 0); ++ test_tpm2_pcr_mask_from_string_one("0+1", 3, 0); ++ test_tpm2_pcr_mask_from_string_one("0-1", 0, -EINVAL); ++ test_tpm2_pcr_mask_from_string_one("0,1,2", 7, 0); ++ test_tpm2_pcr_mask_from_string_one("0+1+2", 7, 0); ++ test_tpm2_pcr_mask_from_string_one("0+1,2", 7, 0); ++ test_tpm2_pcr_mask_from_string_one("0,1+2", 7, 0); ++ test_tpm2_pcr_mask_from_string_one("0,2", 5, 0); ++ test_tpm2_pcr_mask_from_string_one("0+2", 5, 0); ++ test_tpm2_pcr_mask_from_string_one("foo", 0, -EINVAL); + } + + TEST(tpm2_util_pbkdf2_hmac_sha256) { diff --git a/SOURCES/0511-test-test-tpm2-add-tests-for-pcr-selection-functions.patch b/SOURCES/0511-test-test-tpm2-add-tests-for-pcr-selection-functions.patch new file mode 100644 index 0000000..c0ba06a --- /dev/null +++ b/SOURCES/0511-test-test-tpm2-add-tests-for-pcr-selection-functions.patch @@ -0,0 +1,363 @@ +From cfaacedcf9e263f8291f13f2dde187a46e8a3f31 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 28 Feb 2023 17:16:43 -0500 +Subject: [PATCH] test/test-tpm2: add tests for pcr selection functions + +(cherry picked from commit e067a49fd1180ff1104b3978c92d11784c67800f) + +Related: RHEL-16182 +--- + src/test/test-tpm2.c | 342 +++++++++++++++++++++++++++++++++++++++++++ + 1 file changed, 342 insertions(+) + +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 23277449b5..20baa0f261 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -69,4 +69,346 @@ TEST(tpm2_util_pbkdf2_hmac_sha256) { + } + } + ++#if HAVE_TPM2 ++ ++#define POISON(type) \ ++ ({ \ ++ type _p; \ ++ memset(&_p, 0xaa, sizeof(_p)); \ ++ _p; \ ++ }) ++#define POISON_TPML POISON(TPML_PCR_SELECTION) ++#define POISON_TPMS POISON(TPMS_PCR_SELECTION) ++#define POISON_U32 POISON(uint32_t) ++ ++static void assert_tpms_pcr_selection_eq(TPMS_PCR_SELECTION *a, TPMS_PCR_SELECTION *b) { ++ assert_se(a); ++ assert_se(b); ++ ++ assert_se(a->hash == b->hash); ++ assert_se(a->sizeofSelect == b->sizeofSelect); ++ ++ for (size_t i = 0; i < a->sizeofSelect; i++) ++ assert_se(a->pcrSelect[i] == b->pcrSelect[i]); ++} ++ ++static void assert_tpml_pcr_selection_eq(TPML_PCR_SELECTION *a, TPML_PCR_SELECTION *b) { ++ assert_se(a); ++ assert_se(b); ++ ++ assert_se(a->count == b->count); ++ for (size_t i = 0; i < a->count; i++) ++ assert_tpms_pcr_selection_eq(&a->pcrSelections[i], &b->pcrSelections[i]); ++} ++ ++static void verify_tpms_pcr_selection(TPMS_PCR_SELECTION *s, uint32_t mask, TPMI_ALG_HASH hash) { ++ assert_se(s->hash == hash); ++ assert_se(s->sizeofSelect == 3); ++ assert_se(s->pcrSelect[0] == (mask & 0xff)); ++ assert_se(s->pcrSelect[1] == ((mask >> 8) & 0xff)); ++ assert_se(s->pcrSelect[2] == ((mask >> 16) & 0xff)); ++ assert_se(s->pcrSelect[3] == 0); ++ ++ uint32_t m = POISON_U32; ++ tpm2_tpms_pcr_selection_to_mask(s, &m); ++ assert_se(m == mask); ++} ++ ++static void verify_tpml_pcr_selection(TPML_PCR_SELECTION *l, TPMS_PCR_SELECTION s[], size_t count) { ++ assert_se(l->count == count); ++ for (size_t i = 0; i < count; i++) { ++ assert_tpms_pcr_selection_eq(&s[i], &l->pcrSelections[i]); ++ ++ uint32_t mask = POISON_U32; ++ TPMI_ALG_HASH hash = l->pcrSelections[i].hash; ++ assert_se(tpm2_tpml_pcr_selection_to_mask(l, hash, &mask) == 0); ++ verify_tpms_pcr_selection(&l->pcrSelections[i], mask, hash); ++ } ++} ++ ++static void _test_pcr_selection_mask_hash(uint32_t mask, TPMI_ALG_HASH hash) { ++ TPMS_PCR_SELECTION s = POISON_TPMS; ++ tpm2_tpms_pcr_selection_from_mask(mask, hash, &s); ++ verify_tpms_pcr_selection(&s, mask, hash); ++ ++ TPML_PCR_SELECTION l = POISON_TPML; ++ tpm2_tpml_pcr_selection_from_mask(mask, hash, &l); ++ verify_tpml_pcr_selection(&l, &s, 1); ++ verify_tpms_pcr_selection(&l.pcrSelections[0], mask, hash); ++ ++ uint32_t test_masks[] = { ++ 0x0, 0x1, 0x100, 0x10000, 0xf0f0f0, 0xaaaaaa, 0xffffff, ++ }; ++ for (unsigned i = 0; i < ELEMENTSOF(test_masks); i++) { ++ uint32_t test_mask = test_masks[i]; ++ ++ TPMS_PCR_SELECTION a = POISON_TPMS, b = POISON_TPMS, test_s = POISON_TPMS; ++ tpm2_tpms_pcr_selection_from_mask(test_mask, hash, &test_s); ++ ++ a = s; ++ b = test_s; ++ tpm2_tpms_pcr_selection_add(&a, &b); ++ verify_tpms_pcr_selection(&a, UPDATE_FLAG(mask, test_mask, true), hash); ++ verify_tpms_pcr_selection(&b, test_mask, hash); ++ ++ a = s; ++ b = test_s; ++ tpm2_tpms_pcr_selection_sub(&a, &b); ++ verify_tpms_pcr_selection(&a, UPDATE_FLAG(mask, test_mask, false), hash); ++ verify_tpms_pcr_selection(&b, test_mask, hash); ++ ++ a = s; ++ b = test_s; ++ tpm2_tpms_pcr_selection_move(&a, &b); ++ verify_tpms_pcr_selection(&a, UPDATE_FLAG(mask, test_mask, true), hash); ++ verify_tpms_pcr_selection(&b, 0, hash); ++ } ++} ++ ++TEST(tpms_pcr_selection_mask_and_hash) { ++ TPMI_ALG_HASH HASH_ALGS[] = { TPM2_ALG_SHA1, TPM2_ALG_SHA256, }; ++ ++ for (unsigned i = 0; i < ELEMENTSOF(HASH_ALGS); i++) ++ for (uint32_t m2 = 0; m2 <= 0xffffff; m2 += 0x30000) ++ for (uint32_t m1 = 0; m1 <= 0xffff; m1 += 0x300) ++ for (uint32_t m0 = 0; m0 <= 0xff; m0 += 0x3) ++ _test_pcr_selection_mask_hash(m0 | m1 | m2, HASH_ALGS[i]); ++} ++ ++static void _test_tpms_sw( ++ TPMI_ALG_HASH hash, ++ uint32_t mask, ++ const char *expected_str, ++ size_t expected_weight) { ++ ++ TPMS_PCR_SELECTION s = POISON_TPMS; ++ tpm2_tpms_pcr_selection_from_mask(mask, hash, &s); ++ ++ _cleanup_free_ char *tpms_str = tpm2_tpms_pcr_selection_to_string(&s); ++ assert_se(streq(tpms_str, expected_str)); ++ ++ assert_se(tpm2_tpms_pcr_selection_weight(&s) == expected_weight); ++ assert_se(tpm2_tpms_pcr_selection_is_empty(&s) == (expected_weight == 0)); ++} ++ ++TEST(tpms_pcr_selection_string_and_weight) { ++ TPMI_ALG_HASH sha1 = TPM2_ALG_SHA1, sha256 = TPM2_ALG_SHA256; ++ ++ _test_tpms_sw(sha1, 0, "sha1()", 0); ++ _test_tpms_sw(sha1, 1, "sha1(0)", 1); ++ _test_tpms_sw(sha1, 0xf, "sha1(0+1+2+3)", 4); ++ _test_tpms_sw(sha1, 0x00ff00, "sha1(8+9+10+11+12+13+14+15)", 8); ++ _test_tpms_sw(sha1, 0xffffff, "sha1(0+1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23)", 24); ++ _test_tpms_sw(sha256, 0, "sha256()", 0); ++ _test_tpms_sw(sha256, 1, "sha256(0)", 1); ++ _test_tpms_sw(sha256, 7, "sha256(0+1+2)", 3); ++ _test_tpms_sw(sha256, 0xf00000, "sha256(20+21+22+23)", 4); ++ _test_tpms_sw(sha256, 0xffffff, "sha256(0+1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23)", 24); ++} ++ ++static void _tpml_pcr_selection_add_tpms(TPMS_PCR_SELECTION s[], size_t count, TPML_PCR_SELECTION *ret) { ++ for (size_t i = 0; i < count; i++) ++ tpm2_tpml_pcr_selection_add_tpms_pcr_selection(ret, &s[i]); ++} ++ ++static void _tpml_pcr_selection_sub_tpms(TPMS_PCR_SELECTION s[], size_t count, TPML_PCR_SELECTION *ret) { ++ for (size_t i = 0; i < count; i++) ++ tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(ret, &s[i]); ++} ++ ++static void _test_tpml_sw( ++ TPMS_PCR_SELECTION s[], ++ size_t count, ++ size_t expected_count, ++ const char *expected_str, ++ size_t expected_weight) { ++ ++ TPML_PCR_SELECTION l = {}; ++ _tpml_pcr_selection_add_tpms(s, count, &l); ++ assert_se(l.count == expected_count); ++ ++ _cleanup_free_ char *tpml_str = tpm2_tpml_pcr_selection_to_string(&l); ++ assert_se(streq(tpml_str, expected_str)); ++ ++ assert_se(tpm2_tpml_pcr_selection_weight(&l) == expected_weight); ++ assert_se(tpm2_tpml_pcr_selection_is_empty(&l) == (expected_weight == 0)); ++} ++ ++TEST(tpml_pcr_selection_string_and_weight) { ++ size_t size = 0xaa; ++ TPMI_ALG_HASH sha1 = TPM2_ALG_SHA1, ++ sha256 = TPM2_ALG_SHA256, ++ sha384 = TPM2_ALG_SHA384, ++ sha512 = TPM2_ALG_SHA512; ++ TPMS_PCR_SELECTION s[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, }; ++ ++ size = 0; ++ tpm2_tpms_pcr_selection_from_mask(0x000002, sha1 , &s[size++]); ++ tpm2_tpms_pcr_selection_from_mask(0x0080f0, sha384, &s[size++]); ++ tpm2_tpms_pcr_selection_from_mask(0x010100, sha512, &s[size++]); ++ tpm2_tpms_pcr_selection_from_mask(0xff0000, sha256, &s[size++]); ++ _test_tpml_sw(s, ++ size, ++ /* expected_count= */ 4, ++ "[sha1(1),sha384(4+5+6+7+15),sha512(8+16),sha256(16+17+18+19+20+21+22+23)]", ++ /* expected_weight= */ 16); ++ ++ size = 0; ++ tpm2_tpms_pcr_selection_from_mask(0x0403aa, sha512, &s[size++]); ++ tpm2_tpms_pcr_selection_from_mask(0x0080f0, sha256, &s[size++]); ++ _test_tpml_sw(s, ++ size, ++ /* expected_count= */ 2, ++ "[sha512(1+3+5+7+8+9+18),sha256(4+5+6+7+15)]", ++ /* expected_weight= */ 12); ++ ++ size = 0; ++ /* Empty hashes should be ignored */ ++ tpm2_tpms_pcr_selection_from_mask(0x0300ce, sha384, &s[size++]); ++ tpm2_tpms_pcr_selection_from_mask(0xffffff, sha512, &s[size++]); ++ tpm2_tpms_pcr_selection_from_mask(0x000000, sha1 , &s[size++]); ++ tpm2_tpms_pcr_selection_from_mask(0x330010, sha256, &s[size++]); ++ _test_tpml_sw(s, ++ size, ++ /* expected_count= */ 3, ++ "[sha384(1+2+3+6+7+16+17),sha512(0+1+2+3+4+5+6+7+8+9+10+11+12+13+14+15+16+17+18+19+20+21+22+23),sha256(4+16+17+20+21)]", ++ /* expected_weight= */ 36); ++ ++ size = 0; ++ /* Verify same-hash entries are properly combined. */ ++ tpm2_tpms_pcr_selection_from_mask(0x000001, sha1 , &s[size++]); ++ tpm2_tpms_pcr_selection_from_mask(0x000001, sha256, &s[size++]); ++ tpm2_tpms_pcr_selection_from_mask(0x000010, sha1 , &s[size++]); ++ tpm2_tpms_pcr_selection_from_mask(0x000010, sha256, &s[size++]); ++ _test_tpml_sw(s, ++ size, ++ /* expected_count= */ 2, ++ "[sha1(0+4),sha256(0+4)]", ++ /* expected_weight= */ 4); ++} ++ ++/* Test tpml add/sub by changing the tpms individually */ ++static void _test_tpml_addsub_tpms( ++ TPML_PCR_SELECTION *start, ++ TPMS_PCR_SELECTION add[], ++ size_t add_count, ++ TPMS_PCR_SELECTION expected1[], ++ size_t expected1_count, ++ TPMS_PCR_SELECTION sub[], ++ size_t sub_count, ++ TPMS_PCR_SELECTION expected2[], ++ size_t expected2_count) { ++ ++ TPML_PCR_SELECTION l = *start; ++ ++ _tpml_pcr_selection_add_tpms(add, add_count, &l); ++ verify_tpml_pcr_selection(&l, expected1, expected1_count); ++ ++ _tpml_pcr_selection_sub_tpms(sub, sub_count, &l); ++ verify_tpml_pcr_selection(&l, expected2, expected2_count); ++} ++ ++/* Test tpml add/sub by creating new tpmls */ ++static void _test_tpml_addsub_tpml( ++ TPML_PCR_SELECTION *start, ++ TPMS_PCR_SELECTION add[], ++ size_t add_count, ++ TPMS_PCR_SELECTION expected1[], ++ size_t expected1_count, ++ TPMS_PCR_SELECTION sub[], ++ size_t sub_count, ++ TPMS_PCR_SELECTION expected2[], ++ size_t expected2_count) { ++ ++ TPML_PCR_SELECTION l = {}; ++ tpm2_tpml_pcr_selection_add(&l, start); ++ assert_tpml_pcr_selection_eq(&l, start); ++ ++ TPML_PCR_SELECTION addl = {}; ++ _tpml_pcr_selection_add_tpms(add, add_count, &addl); ++ tpm2_tpml_pcr_selection_add(&l, &addl); ++ ++ TPML_PCR_SELECTION e1 = {}; ++ _tpml_pcr_selection_add_tpms(expected1, expected1_count, &e1); ++ assert_tpml_pcr_selection_eq(&l, &e1); ++ ++ TPML_PCR_SELECTION subl = {}; ++ _tpml_pcr_selection_add_tpms(sub, sub_count, &subl); ++ tpm2_tpml_pcr_selection_sub(&l, &subl); ++ ++ TPML_PCR_SELECTION e2 = {}; ++ _tpml_pcr_selection_add_tpms(expected2, expected2_count, &e2); ++ assert_tpml_pcr_selection_eq(&l, &e2); ++} ++ ++#define _test_tpml_addsub(...) \ ++ ({ \ ++ _test_tpml_addsub_tpms(__VA_ARGS__); \ ++ _test_tpml_addsub_tpml(__VA_ARGS__); \ ++ }) ++ ++TEST(tpml_pcr_selection_add_sub) { ++ size_t add_count = 0xaa, expected1_count = 0xaa, sub_count = 0xaa, expected2_count = 0xaa; ++ TPMI_ALG_HASH sha1 = TPM2_ALG_SHA1, ++ sha256 = TPM2_ALG_SHA256, ++ sha384 = TPM2_ALG_SHA384, ++ sha512 = TPM2_ALG_SHA512; ++ TPML_PCR_SELECTION l = POISON_TPML; ++ TPMS_PCR_SELECTION add[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, }, ++ sub[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, }, ++ expected1[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, }, ++ expected2[4] = { POISON_TPMS, POISON_TPMS, POISON_TPMS, POISON_TPMS, }; ++ ++ l = (TPML_PCR_SELECTION){}; ++ add_count = 0; ++ expected1_count = 0; ++ sub_count = 0; ++ expected2_count = 0; ++ tpm2_tpms_pcr_selection_from_mask(0x010101, sha256, &add[add_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0x101010, sha256, &add[add_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &add[add_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0x111111, sha256, &expected1[expected1_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &expected1[expected1_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0x000001, sha256, &sub[sub_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0xff0000, sha512, &sub[sub_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0x111110, sha256, &expected2[expected2_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &expected2[expected2_count++]); ++ _test_tpml_addsub(&l, ++ add, add_count, ++ expected1, expected1_count, ++ sub, sub_count, ++ expected2, expected2_count); ++ ++ l = (TPML_PCR_SELECTION){ ++ .count = 1, ++ .pcrSelections[0].hash = sha1, ++ .pcrSelections[0].sizeofSelect = 3, ++ .pcrSelections[0].pcrSelect[0] = 0xf0, ++ }; ++ add_count = 0; ++ expected1_count = 0; ++ sub_count = 0; ++ expected2_count = 0; ++ tpm2_tpms_pcr_selection_from_mask(0xff0000, sha256, &add[add_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0xffff00, sha384, &add[add_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &add[add_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0xf00000, sha1 , &add[add_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0xf000f0, sha1 , &expected1[expected1_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0xff0000, sha256, &expected1[expected1_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0xffff00, sha384, &expected1[expected1_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &expected1[expected1_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0x00ffff, sha256, &sub[sub_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0xf000f0, sha1 , &expected2[expected2_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0xff0000, sha256, &expected2[expected2_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0xffff00, sha384, &expected2[expected2_count++]); ++ tpm2_tpms_pcr_selection_from_mask(0x0000ff, sha512, &expected2[expected2_count++]); ++ _test_tpml_addsub(&l, ++ add, add_count, ++ expected1, expected1_count, ++ sub, sub_count, ++ expected2, expected2_count); ++} ++ ++#endif /* HAVE_TPM2 */ ++ + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0512-tpm2-add-tpm2_pcr_read.patch b/SOURCES/0512-tpm2-add-tpm2_pcr_read.patch new file mode 100644 index 0000000..cb7965d --- /dev/null +++ b/SOURCES/0512-tpm2-add-tpm2_pcr_read.patch @@ -0,0 +1,189 @@ +From 8aeeb8bb9c280de69a4d7ae46894304aaad73870 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 21 Feb 2023 16:31:59 -0500 +Subject: [PATCH] tpm2: add tpm2_pcr_read() + +(cherry picked from commit c57d8bc8717110ff343358be9fdfea1472fc360f) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 129 ++++++++++++++++++++++++++++------------- + 1 file changed, 89 insertions(+), 40 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index cf62524e34..722ae3ca9c 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -744,6 +744,14 @@ size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l) { + return weight; + } + ++static void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg) { ++ if (!DEBUG_LOGGING || !l) ++ return; ++ ++ _cleanup_free_ char *s = tpm2_tpml_pcr_selection_to_string(l); ++ log_debug("%s: %s", msg ?: "PCR selection", strna(s)); ++} ++ + static void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg) { + if (!DEBUG_LOGGING || !buffer || size == 0) + return; +@@ -792,26 +800,82 @@ static int tpm2_get_policy_digest( + return 0; + } + +-static unsigned find_nth_bit(uint32_t mask, unsigned n) { +- uint32_t bit = 1; ++static int tpm2_pcr_read( ++ Tpm2Context *c, ++ const TPML_PCR_SELECTION *pcr_selection, ++ TPML_PCR_SELECTION *ret_pcr_selection, ++ TPM2B_DIGEST **ret_pcr_values, ++ size_t *ret_pcr_values_size) { ++ ++ _cleanup_free_ TPM2B_DIGEST *pcr_values = NULL; ++ TPML_PCR_SELECTION remaining, total_read = {}; ++ size_t pcr_values_size = 0; ++ TSS2_RC rc; + +- assert(n < 32); ++ assert(c); ++ assert(pcr_selection); + +- /* Returns the bit index of the nth set bit, e.g. mask=0b101001, n=3 → 5 */ ++ remaining = *pcr_selection; ++ while (!tpm2_tpml_pcr_selection_is_empty(&remaining)) { ++ _cleanup_(Esys_Freep) TPML_PCR_SELECTION *current_read = NULL; ++ _cleanup_(Esys_Freep) TPML_DIGEST *current_values = NULL; + +- for (unsigned i = 0; i < sizeof(mask)*8; i++) { ++ tpm2_log_debug_tpml_pcr_selection(&remaining, "Reading PCR selection"); + +- if (bit & mask) { +- if (n == 0) +- return i; ++ /* Unfortunately, PCR_Read will not return more than 8 values. */ ++ rc = sym_Esys_PCR_Read( ++ c->esys_context, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ &remaining, ++ NULL, ++ ¤t_read, ++ ¤t_values); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to read TPM2 PCRs: %s", sym_Tss2_RC_Decode(rc)); + +- n--; ++ if (tpm2_tpml_pcr_selection_is_empty(current_read)) { ++ log_warning("TPM2 refused to read possibly unimplemented PCRs, ignoring."); ++ break; + } + +- bit <<= 1; ++ tpm2_tpml_pcr_selection_sub(&remaining, current_read); ++ tpm2_tpml_pcr_selection_add(&total_read, current_read); ++ ++ if (!GREEDY_REALLOC(pcr_values, pcr_values_size + current_values->count)) ++ return log_oom(); ++ ++ memcpy_safe(&pcr_values[pcr_values_size], current_values->digests, ++ current_values->count * sizeof(TPM2B_DIGEST)); ++ pcr_values_size += current_values->count; ++ ++ if (DEBUG_LOGGING) { ++ unsigned i = 0; ++ FOREACH_PCR_IN_TPML_PCR_SELECTION(pcr, s, current_read) { ++ assert(i < current_values->count); ++ ++ TPM2B_DIGEST *d = ¤t_values->digests[i]; ++ i++; ++ ++ TPML_PCR_SELECTION l; ++ tpm2_tpml_pcr_selection_from_mask(INDEX_TO_MASK(uint32_t, pcr), s->hash, &l); ++ ++ _cleanup_free_ char *desc = tpm2_tpml_pcr_selection_to_string(&l); ++ tpm2_log_debug_digest(d, strna(desc)); ++ } ++ } + } + +- return UINT_MAX; ++ if (ret_pcr_selection) ++ *ret_pcr_selection = total_read; ++ if (ret_pcr_values) ++ *ret_pcr_values = TAKE_PTR(pcr_values); ++ if (ret_pcr_values_size) ++ *ret_pcr_values_size = pcr_values_size; ++ ++ return 0; + } + + static int tpm2_pcr_mask_good( +@@ -819,10 +883,10 @@ static int tpm2_pcr_mask_good( + TPMI_ALG_HASH bank, + uint32_t mask) { + +- _cleanup_(Esys_Freep) TPML_DIGEST *pcr_values = NULL; ++ _cleanup_free_ TPM2B_DIGEST *pcr_values = NULL; + TPML_PCR_SELECTION selection; +- bool good = false; +- TSS2_RC rc; ++ size_t pcr_values_size = 0; ++ int r; + + assert(c); + +@@ -832,38 +896,23 @@ static int tpm2_pcr_mask_good( + + tpm2_tpml_pcr_selection_from_mask(mask, bank, &selection); + +- rc = sym_Esys_PCR_Read( +- c->esys_context, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &selection, +- NULL, +- NULL, +- &pcr_values); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to read TPM2 PCRs: %s", sym_Tss2_RC_Decode(rc)); ++ r = tpm2_pcr_read(c, &selection, &selection, &pcr_values, &pcr_values_size); ++ if (r < 0) ++ return r; + + /* If at least one of the selected PCR values is something other than all 0x00 or all 0xFF we are happy. */ +- for (unsigned i = 0; i < pcr_values->count; i++) { +- if (DEBUG_LOGGING) { +- _cleanup_free_ char *h = NULL; +- unsigned j; ++ unsigned i = 0; ++ FOREACH_PCR_IN_TPML_PCR_SELECTION(pcr, s, &selection) { ++ assert(i < pcr_values_size); + +- h = hexmem(pcr_values->digests[i].buffer, pcr_values->digests[i].size); +- j = find_nth_bit(mask, i); +- assert(j != UINT_MAX); +- +- log_debug("PCR %u value: %s", j, strna(h)); +- } ++ if (!memeqbyte(0x00, pcr_values[i].buffer, pcr_values[i].size) && ++ !memeqbyte(0xFF, pcr_values[i].buffer, pcr_values[i].size)) ++ return true; + +- if (!memeqbyte(0x00, pcr_values->digests[i].buffer, pcr_values->digests[i].size) && +- !memeqbyte(0xFF, pcr_values->digests[i].buffer, pcr_values->digests[i].size)) +- good = true; ++ i++; + } + +- return good; ++ return false; + } + + static int tpm2_bank_has24(const TPMS_PCR_SELECTION *selection) { diff --git a/SOURCES/0513-tpm2-move-openssl-required-ifdef-code-out-of-policy-.patch b/SOURCES/0513-tpm2-move-openssl-required-ifdef-code-out-of-policy-.patch new file mode 100644 index 0000000..d3d79a7 --- /dev/null +++ b/SOURCES/0513-tpm2-move-openssl-required-ifdef-code-out-of-policy-.patch @@ -0,0 +1,204 @@ +From 42b51de62cf4f4bbb92ef63deaa4cd9181f21beb Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 8 Dec 2022 16:57:47 -0500 +Subject: [PATCH] tpm2: move openssl-required ifdef code out of policy-building + function + +(cherry picked from commit 958982415808eeec956e79c4f7ca030af5af1b71) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 98 +++++++++++++++++++++++++----------------- + 1 file changed, 58 insertions(+), 40 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 722ae3ca9c..ea04d0a892 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1262,21 +1262,38 @@ static int tpm2_make_encryption_session( + return 0; + } + ++static int openssl_pubkey_to_tpm2_pubkey( ++ const void *pubkey, ++ size_t pubkey_size, ++ TPM2B_PUBLIC *output, ++ void **ret_fp, ++ size_t *ret_fp_size) { ++ + #if HAVE_OPENSSL +-static int openssl_pubkey_to_tpm2_pubkey(EVP_PKEY *input, TPM2B_PUBLIC *output) { + #if OPENSSL_VERSION_MAJOR >= 3 + _cleanup_(BN_freep) BIGNUM *n = NULL, *e = NULL; + #else + const BIGNUM *n = NULL, *e = NULL; + const RSA *rsa = NULL; + #endif +- int n_bytes, e_bytes; ++ int r, n_bytes, e_bytes; + +- assert(input); ++ assert(pubkey); ++ assert(pubkey_size > 0); + assert(output); + + /* Converts an OpenSSL public key to a structure that the TPM chip can process. */ + ++ _cleanup_fclose_ FILE *f = NULL; ++ f = fmemopen((void*) pubkey, pubkey_size, "r"); ++ if (!f) ++ return log_oom(); ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *input = NULL; ++ input = PEM_read_PUBKEY(f, NULL, NULL, NULL); ++ if (!input) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse PEM public key."); ++ + if (EVP_PKEY_base_id(input) != EVP_PKEY_RSA) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provided public key is not an RSA key."); + +@@ -1343,22 +1360,38 @@ static int openssl_pubkey_to_tpm2_pubkey(EVP_PKEY *input, TPM2B_PUBLIC *output) + if (BN_bn2bin(e, (unsigned char*) &output->publicArea.parameters.rsaDetail.exponent) <= 0) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to convert RSA exponent."); + ++ if (ret_fp) { ++ _cleanup_free_ void *fp = NULL; ++ size_t fp_size; ++ ++ assert(ret_fp_size); ++ ++ r = pubkey_fingerprint(input, EVP_sha256(), &fp, &fp_size); ++ if (r < 0) ++ return log_error_errno(r, "Failed to calculate public key fingerprint: %m"); ++ ++ *ret_fp = TAKE_PTR(fp); ++ *ret_fp_size = fp_size; ++ } ++ + return 0; ++#else /* HAVE_OPENSSL */ ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); ++#endif + } + + static int find_signature( + JsonVariant *v, +- uint16_t pcr_bank, +- uint32_t pcr_mask, +- EVP_PKEY *pk, ++ const TPML_PCR_SELECTION *pcr_selection, ++ const void *fp, ++ size_t fp_size, + const void *policy, + size_t policy_size, + void *ret_signature, + size_t *ret_signature_size) { + +- _cleanup_free_ void *fp = NULL; ++#if HAVE_OPENSSL + JsonVariant *b, *i; +- size_t fp_size; + const char *k; + int r; + +@@ -1368,6 +1401,12 @@ static int find_signature( + if (!json_variant_is_object(v)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Signature is not a JSON object."); + ++ uint16_t pcr_bank = pcr_selection->pcrSelections[0].hash; ++ uint32_t pcr_mask; ++ r = tpm2_tpml_pcr_selection_to_mask(pcr_selection, pcr_bank, &pcr_mask); ++ if (r < 0) ++ return r; ++ + k = tpm2_hash_alg_to_string(pcr_bank); + if (!k) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Don't know PCR bank %" PRIu16, pcr_bank); +@@ -1411,12 +1450,6 @@ static int find_signature( + if (r < 0) + return log_error_errno(r, "Failed to decode fingerprint in JSON data: %m"); + +- if (!fp) { +- r = pubkey_fingerprint(pk, EVP_sha256(), &fp, &fp_size); +- if (r < 0) +- return log_error_errno(r, "Failed to calculate public key fingerprint: %m"); +- } +- + if (memcmp_nn(fp, fp_size, fpj_data, fpj_size) != 0) + continue; /* Not for this public key */ + +@@ -1441,8 +1474,10 @@ static int find_signature( + } + + return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Couldn't find signature for this PCR bank, PCR index and public key."); +-} ++#else /* HAVE_OPENSSL */ ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); + #endif ++} + + static int tpm2_make_policy_session( + Tpm2Context *c, +@@ -1504,21 +1539,6 @@ static int tpm2_make_policy_session( + } + } + +-#if HAVE_OPENSSL +- _cleanup_(EVP_PKEY_freep) EVP_PKEY *pk = NULL; +- if (pubkey_size > 0) { +- /* If a pubkey is specified, load it to validate it, even if the PCR mask for this is +- * actually zero, and we are thus not going to use it. */ +- _cleanup_fclose_ FILE *f = fmemopen((void*) pubkey, pubkey_size, "r"); +- if (!f) +- return log_oom(); +- +- pk = PEM_read_PUBKEY(f, NULL, NULL, NULL); +- if (!pk) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse PEM public key."); +- } +-#endif +- + _cleanup_tpm2_handle_ Tpm2Handle *session = NULL; + r = tpm2_handle_new(c, &session); + if (r < 0) +@@ -1541,12 +1561,14 @@ static int tpm2_make_policy_session( + "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc)); + + if (pubkey_pcr_mask != 0) { +-#if HAVE_OPENSSL ++ _cleanup_free_ void *fp = NULL; ++ size_t fp_size = 0; ++ TPM2B_PUBLIC pubkey_tpm2; ++ + log_debug("Configuring public key based PCR policy."); + +- /* First: load public key into the TPM */ +- TPM2B_PUBLIC pubkey_tpm2; +- r = openssl_pubkey_to_tpm2_pubkey(pk, &pubkey_tpm2); ++ /* Convert the PEM key to TPM2 format */ ++ r = openssl_pubkey_to_tpm2_pubkey(pubkey, pubkey_size, &pubkey_tpm2, &fp, &fp_size); + if (r < 0) + return r; + +@@ -1614,9 +1636,8 @@ static int tpm2_make_policy_session( + + r = find_signature( + signature_json, +- pcr_bank, +- pubkey_pcr_mask, +- pk, ++ &pcr_selection, ++ fp, fp_size, + approved_policy->buffer, + approved_policy->size, + &signature_raw, +@@ -1680,9 +1701,6 @@ static int tpm2_make_policy_session( + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to push Authorize policy into TPM: %s", sym_Tss2_RC_Decode(rc)); +-#else +- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +-#endif + } + + if (hash_pcr_mask != 0) { diff --git a/SOURCES/0514-tpm2-add-tpm2_is_encryption_session.patch b/SOURCES/0514-tpm2-add-tpm2_is_encryption_session.patch new file mode 100644 index 0000000..38fd105 --- /dev/null +++ b/SOURCES/0514-tpm2-add-tpm2_is_encryption_session.patch @@ -0,0 +1,66 @@ +From 5f280658b78d0fd80c520da9612043be2fd597a8 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 7 Dec 2022 11:23:59 -0500 +Subject: [PATCH] tpm2: add tpm2_is_encryption_session() + +(cherry picked from commit e976445d035e21afec2f64a7c825be5df1f664a0) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 16 ++++++++++++++++ + src/shared/tpm2-util.h | 1 + + 2 files changed, 17 insertions(+) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index ea04d0a892..b4c620ec53 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -46,6 +46,7 @@ TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySes + TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL; + TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL; + TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL; ++TSS2_RC (*sym_Esys_TRSess_GetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION *flags); + TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask); + TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name); + TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL; +@@ -82,6 +83,7 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Esys_PolicyPCR), + DLSYM_ARG(Esys_StartAuthSession), + DLSYM_ARG(Esys_Startup), ++ DLSYM_ARG(Esys_TRSess_GetAttributes), + DLSYM_ARG(Esys_TRSess_SetAttributes), + DLSYM_ARG(Esys_TR_GetName), + DLSYM_ARG(Esys_TR_SetAuth), +@@ -1179,6 +1181,20 @@ static void hash_pin(const char *pin, size_t len, TPM2B_AUTH *auth) { + sha256_finish_ctx(&hash, auth->buffer); + } + ++static bool tpm2_is_encryption_session(Tpm2Context *c, const Tpm2Handle *session) { ++ TPMA_SESSION flags = 0; ++ TSS2_RC rc; ++ ++ assert(c); ++ assert(session); ++ ++ rc = sym_Esys_TRSess_GetAttributes(c->esys_context, session->esys_handle, &flags); ++ if (rc != TSS2_RC_SUCCESS) ++ return false; ++ ++ return (flags & TPMA_SESSION_DECRYPT) && (flags & TPMA_SESSION_ENCRYPT); ++} ++ + static int tpm2_make_encryption_session( + Tpm2Context *c, + const Tpm2Handle *primary, +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index c2532c61c2..cc43bbfbfb 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -50,6 +50,7 @@ extern TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR po + extern TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs); + extern TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle); + extern TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType); ++extern TSS2_RC (*sym_Esys_TRSess_GetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION *flags); + extern TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask); + extern TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name); + extern TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue); diff --git a/SOURCES/0515-tpm2-move-policy-building-out-of-policy-session-crea.patch b/SOURCES/0515-tpm2-move-policy-building-out-of-policy-session-crea.patch new file mode 100644 index 0000000..cad6361 --- /dev/null +++ b/SOURCES/0515-tpm2-move-policy-building-out-of-policy-session-crea.patch @@ -0,0 +1,329 @@ +From 1d027f4d13ed1c1fbd0766db5b4a544042f1336e Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 7 Dec 2022 11:23:59 -0500 +Subject: [PATCH] tpm2: move policy building out of policy session creation + +This retains the use of policy sessions instead of trial sessions +in most cases, based on the code comment that some TPMs do not +implement trial sessions correctly. However, it's likely that the +issue was not the TPMs, but our code's incorrect use of PolicyPCR +inside a trial session; we are not providing expected PCR values +with our call to PolicyPCR inside a trial session, but the spec +indicates that in a trial session, the TPM *may* return error if +the expected PCR value(s) are not provided. That may have been the +source of the original confusion about trial sessions. + +More details: +https://github.com/systemd/systemd/pull/26357#pullrequestreview-1409983694 + +Also, future commits will replace the use of trial sessions with +policy calculations, which avoids the problem entirely. + +(cherry picked from commit 2cd9d57548b0dadd52523df486d33aa4cf7c3b84) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 199 +++++++++++++++++++++++------------------ + 1 file changed, 112 insertions(+), 87 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index b4c620ec53..3960c0aed7 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1278,6 +1278,59 @@ static int tpm2_make_encryption_session( + return 0; + } + ++static int tpm2_make_policy_session( ++ Tpm2Context *c, ++ const Tpm2Handle *primary, ++ const Tpm2Handle *encryption_session, ++ bool trial, ++ Tpm2Handle **ret_session) { ++ ++ static const TPMT_SYM_DEF symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }; ++ TPM2_SE session_type = trial ? TPM2_SE_TRIAL : TPM2_SE_POLICY; ++ TSS2_RC rc; ++ int r; ++ ++ assert(c); ++ assert(primary); ++ assert(encryption_session); ++ assert(ret_session); ++ ++ if (!tpm2_is_encryption_session(c, encryption_session)) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Missing encryption session"); ++ ++ log_debug("Starting policy session."); ++ ++ _cleanup_tpm2_handle_ Tpm2Handle *session = NULL; ++ r = tpm2_handle_new(c, &session); ++ if (r < 0) ++ return r; ++ ++ rc = sym_Esys_StartAuthSession( ++ c->esys_context, ++ primary->esys_handle, ++ ESYS_TR_NONE, ++ encryption_session->esys_handle, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ NULL, ++ session_type, ++ &symmetric, ++ TPM2_ALG_SHA256, ++ &session->esys_handle); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc)); ++ ++ *ret_session = TAKE_PTR(session); ++ ++ return 0; ++} ++ + static int openssl_pubkey_to_tpm2_pubkey( + const void *pubkey, + size_t pubkey_size, +@@ -1495,87 +1548,36 @@ static int find_signature( + #endif + } + +-static int tpm2_make_policy_session( ++static int tpm2_build_sealing_policy( + Tpm2Context *c, +- const Tpm2Handle *primary, +- const Tpm2Handle *parent_session, +- TPM2_SE session_type, ++ const Tpm2Handle *session, + uint32_t hash_pcr_mask, +- uint16_t pcr_bank, /* If UINT16_MAX, pick best bank automatically, otherwise specify bank explicitly. */ ++ uint16_t pcr_bank, + const void *pubkey, + size_t pubkey_size, + uint32_t pubkey_pcr_mask, + JsonVariant *signature_json, + bool use_pin, +- Tpm2Handle **ret_session, +- TPM2B_DIGEST **ret_policy_digest, +- TPMI_ALG_HASH *ret_pcr_bank) { ++ TPM2B_DIGEST **ret_policy_digest) { + +- static const TPMT_SYM_DEF symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }; + TSS2_RC rc; + int r; + + assert(c); ++ assert(session); + assert(pubkey || pubkey_size == 0); + assert(pubkey_pcr_mask == 0 || pubkey_size > 0); + +- log_debug("Starting authentication session."); +- +- /* So apparently some TPM implementations don't implement trial mode correctly. To avoid issues let's +- * avoid it when it is easy to. At the moment we only really need trial mode for the signed PCR +- * policies (since only then we need to shove PCR values into the policy that don't match current +- * state anyway), hence if we have none of those we don't need to bother. Hence, let's patch in +- * TPM2_SE_POLICY even if trial mode is requested unless a pubkey PCR mask is specified that is +- * non-zero, i.e. signed PCR policy is requested. +- * +- * One day we should switch to calculating policy hashes client side when trial mode is requested, to +- * avoid this mess. */ +- if (session_type == TPM2_SE_TRIAL && pubkey_pcr_mask == 0) +- session_type = TPM2_SE_POLICY; ++ log_debug("Building sealing policy."); + + if ((hash_pcr_mask | pubkey_pcr_mask) != 0) { +- /* We are told to configure a PCR policy of some form, let's determine/validate the PCR bank to use. */ +- +- if (pcr_bank != UINT16_MAX) { +- r = tpm2_pcr_mask_good(c, pcr_bank, hash_pcr_mask|pubkey_pcr_mask); +- if (r < 0) +- return r; +- if (r == 0) +- log_warning("Selected TPM2 PCRs are not initialized on this system, most likely due to a firmware issue. PCR policy is effectively not enforced. Proceeding anyway."); +- } else { +- /* No bank configured, pick automatically. Some TPM2 devices only can do SHA1. If we +- * detect that use that, but preferably use SHA256 */ +- r = tpm2_get_best_pcr_bank(c, hash_pcr_mask|pubkey_pcr_mask, &pcr_bank); +- if (r < 0) +- return r; +- } ++ r = tpm2_pcr_mask_good(c, pcr_bank, hash_pcr_mask|pubkey_pcr_mask); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ log_warning("Selected TPM2 PCRs are not initialized on this system."); + } + +- _cleanup_tpm2_handle_ Tpm2Handle *session = NULL; +- r = tpm2_handle_new(c, &session); +- if (r < 0) +- return r; +- +- rc = sym_Esys_StartAuthSession( +- c->esys_context, +- primary->esys_handle, +- ESYS_TR_NONE, +- parent_session->esys_handle, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- NULL, +- session_type, +- &symmetric, +- TPM2_ALG_SHA256, +- &session->esys_handle); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc)); +- + if (pubkey_pcr_mask != 0) { + _cleanup_free_ void *fp = NULL; + size_t fp_size = 0; +@@ -1756,12 +1758,6 @@ static int tpm2_make_policy_session( + if (r < 0) + return r; + +- if (ret_session) +- *ret_session = TAKE_PTR(session); +- +- if (ret_pcr_bank) +- *ret_pcr_bank = pcr_bank; +- + return 0; + } + +@@ -1830,33 +1826,57 @@ int tpm2_seal(const char *device, + if (r < 0) + return r; + ++ TPMI_ALG_HASH pcr_bank = 0; ++ if (hash_pcr_mask | pubkey_pcr_mask) { ++ /* Some TPM2 devices only can do SHA1. Prefer SHA256 but allow SHA1. */ ++ r = tpm2_get_best_pcr_bank(c, hash_pcr_mask|pubkey_pcr_mask, &pcr_bank); ++ if (r < 0) ++ return r; ++ } ++ + _cleanup_tpm2_handle_ Tpm2Handle *primary = NULL; + r = tpm2_make_primary(c, &primary, 0, &primary_alg); + if (r < 0) + return r; + + /* we cannot use the bind key before its created */ +- _cleanup_tpm2_handle_ Tpm2Handle *session = NULL; +- r = tpm2_make_encryption_session(c, primary, &TPM2_HANDLE_NONE, NULL, &session); ++ _cleanup_tpm2_handle_ Tpm2Handle *encryption_session = NULL; ++ r = tpm2_make_encryption_session(c, primary, &TPM2_HANDLE_NONE, NULL, &encryption_session); + if (r < 0) + return r; + +- _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; +- TPMI_ALG_HASH pcr_bank; ++ /* So apparently some TPM implementations don't implement trial mode correctly. To avoid issues let's ++ * avoid it when it is easy to. At the moment we only really need trial mode for the signed PCR ++ * policies (since only then we need to shove PCR values into the policy that don't match current ++ * state anyway), hence if we have none of those we don't need to bother. Hence, let's patch in ++ * TPM2_SE_POLICY even if trial mode is requested unless a pubkey PCR mask is specified that is ++ * non-zero, i.e. signed PCR policy is requested. ++ * ++ * One day we should switch to calculating policy hashes client side when trial mode is requested, to ++ * avoid this mess. */ ++ bool trial = (pubkey_pcr_mask != 0); ++ ++ _cleanup_tpm2_handle_ Tpm2Handle *policy_session = NULL; + r = tpm2_make_policy_session( + c, + primary, +- session, +- TPM2_SE_TRIAL, ++ encryption_session, ++ trial, ++ &policy_session); ++ if (r < 0) ++ return r; ++ ++ _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; ++ r = tpm2_build_sealing_policy( ++ c, ++ policy_session, + hash_pcr_mask, +- /* pcr_bank= */ UINT16_MAX, ++ pcr_bank, + pubkey, pubkey_size, + pubkey_pcr_mask, + /* signature_json= */ NULL, + !!pin, +- /* ret_session= */ NULL, +- &policy_digest, +- &pcr_bank); ++ &policy_digest); + if (r < 0) + return r; + +@@ -1897,7 +1917,7 @@ int tpm2_seal(const char *device, + rc = sym_Esys_Create( + c->esys_context, + primary->esys_handle, +- session->esys_handle, /* use HMAC session to enable parameter encryption */ ++ encryption_session->esys_handle, /* use HMAC session to enable parameter encryption */ + ESYS_TR_NONE, + ESYS_TR_NONE, + &hmac_sensitive, +@@ -2066,8 +2086,8 @@ int tpm2_unseal(const char *device, + sym_Tss2_RC_Decode(rc)); + } + +- _cleanup_tpm2_handle_ Tpm2Handle *hmac_session = NULL; +- r = tpm2_make_encryption_session(c, primary, hmac_key, pin, &hmac_session); ++ _cleanup_tpm2_handle_ Tpm2Handle *encryption_session = NULL; ++ r = tpm2_make_encryption_session(c, primary, hmac_key, pin, &encryption_session); + if (r < 0) + return r; + +@@ -2077,17 +2097,22 @@ int tpm2_unseal(const char *device, + r = tpm2_make_policy_session( + c, + primary, +- hmac_session, +- TPM2_SE_POLICY, ++ encryption_session, ++ /* trial= */ false, ++ &policy_session); ++ if (r < 0) ++ return r; ++ ++ r = tpm2_build_sealing_policy( ++ c, ++ policy_session, + hash_pcr_mask, + pcr_bank, + pubkey, pubkey_size, + pubkey_pcr_mask, + signature, + !!pin, +- &policy_session, +- &policy_digest, +- /* ret_pcr_bank= */ NULL); ++ &policy_digest); + if (r < 0) + return r; + +@@ -2105,7 +2130,7 @@ int tpm2_unseal(const char *device, + c->esys_context, + hmac_key->esys_handle, + policy_session->esys_handle, +- hmac_session->esys_handle, /* use HMAC session to enable parameter encryption */ ++ encryption_session->esys_handle, /* use HMAC session to enable parameter encryption */ + ESYS_TR_NONE, + &unsealed); + if (rc == TSS2_RC_SUCCESS) diff --git a/SOURCES/0516-tpm2-add-support-for-a-trusted-SRK.patch b/SOURCES/0516-tpm2-add-support-for-a-trusted-SRK.patch new file mode 100644 index 0000000..dba83e2 --- /dev/null +++ b/SOURCES/0516-tpm2-add-support-for-a-trusted-SRK.patch @@ -0,0 +1,1151 @@ +From 607a98a1f7be2220654cabf9cf679ca0392853fb Mon Sep 17 00:00:00 2001 +From: William Roberts +Date: Fri, 24 Feb 2023 14:11:16 -0600 +Subject: [PATCH] tpm2: add support for a trusted SRK + +Prevent attackers from spoofing the tpmKey portion of the AuthSession by +adding a trusted key to the LUKS header metadata. Also, use a persistent +object rather than a transient object. + +This provides the following benifits: +1. No way to MITM the tpmKey portion of the session, see [1] for +details. + +2. Strengthens the encrypted sessions, note that the bindKey could be + dropped now. + +3. Speed, once it's created we just use it. + +4. Owner Auth is needed to call create primary, so using the SRK + creates a scratch space for normal users. + +This is a "first to set" model, in where the first person to set the key +in the LUKS header wins. Thus, setup should be done in a known good +state. If an SRK, which is a primary key at a special persistent +address, is found, it will use whatever is there. If not, it creates an +SRK. The SRK follows the convetions used through the tpm2-software +organization code on GitHub [2], however, a split has occured between +Windows and Linux with respect to SRK templates. The Linux SRK is +generated with the unique field size set to 0, in Windows, it properly +sets the size to key size in bytes and the unique data to all 0's of that +size. Note the proper templates for SRKs is covered in spec [3]. +However, the most important thing, is that both SRKs are passwordless, +and thus they should be interchangable. If Windows is the first to make +the SRK, systemd will gladly accept it and vice-versa. + +1. Without the bindKey being utilized, an attacker was able to intercept +this and fake a key, thus being able to decrypt and encrypt traffic as +needed. Introduction of the bindKey strengthened this, but allows for +the attacker to brute force AES128CFB using pin guesses. Introduction of +the salt increases the difficulty of this attack as well as DA attacks +on the TPM objects itself. + +2. https://github.com/tpm2-software + +3. https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf + +Fixes: #20668 +Fixes: #22637 + +Signed-off-by: William Roberts +(cherry picked from commit acbb504eaf1be51572b1c0d0d490ac478bc41c64) + +Related: RHEL-16182 +--- + TODO | 2 + + src/cryptenroll/cryptenroll-tpm2.c | 9 +- + .../cryptsetup-token-systemd-tpm2.c | 15 +- + src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 3 + + src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h | 2 + + src/cryptsetup/cryptsetup-tpm2.c | 15 +- + src/cryptsetup/cryptsetup-tpm2.h | 4 + + src/cryptsetup/cryptsetup.c | 7 +- + src/partition/repart.c | 9 +- + src/shared/creds-util.c | 10 +- + src/shared/tpm2-util.c | 372 +++++++++++++++--- + src/shared/tpm2-util.h | 17 +- + src/test/test-tpm2.c | 91 +++++ + 13 files changed, 488 insertions(+), 68 deletions(-) + +diff --git a/TODO b/TODO +index aa3f1c596c..c512bedb92 100644 +--- a/TODO ++++ b/TODO +@@ -455,6 +455,8 @@ Features: + random seed EFI variable is already set. That way, the variable set will be + set in all cases: if you just use sd-stub, or just sd-boot, or both. + ++* Add and pickup tpm2 metadata for creds structure. ++ + * sd-boot: we probably should include all BootXY EFI variable defined boot + entries in our menu, and then suppress ourselves. Benefit: instant + compatibility with all other OSes which register things there, in particular +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index 3098b2e7ac..ab43135dc7 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -142,7 +142,8 @@ int enroll_tpm2(struct crypt_device *cd, + _cleanup_(erase_and_freep) void *secret = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; +- size_t secret_size, blob_size, hash_size, pubkey_size = 0; ++ _cleanup_(freep) void *srk_buf = NULL; ++ size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0; + _cleanup_free_ void *blob = NULL, *hash = NULL, *pubkey = NULL; + uint16_t pcr_bank, primary_alg; + const char *node; +@@ -217,7 +218,9 @@ int enroll_tpm2(struct crypt_device *cd, + &blob, &blob_size, + &hash, &hash_size, + &pcr_bank, +- &primary_alg); ++ &primary_alg, ++ &srk_buf, ++ &srk_buf_size); + if (r < 0) + return r; + +@@ -248,6 +251,7 @@ int enroll_tpm2(struct crypt_device *cd, + primary_alg, + blob, blob_size, + hash, hash_size, ++ srk_buf, srk_buf_size, + &secret2, &secret2_size); + if (r < 0) + return r; +@@ -286,6 +290,7 @@ int enroll_tpm2(struct crypt_device *cd, + hash, hash_size, + use_pin ? binary_salt : NULL, + use_pin ? sizeof(binary_salt) : 0, ++ srk_buf, srk_buf_size, + flags, + &v); + if (r < 0) +diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +index b5d66e389d..aab3a4b4c0 100644 +--- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +@@ -42,8 +42,8 @@ _public_ int cryptsetup_token_open_pin( + void *usrptr /* plugin defined parameter passed to crypt_activate_by_token*() API */) { + + _cleanup_(erase_and_freep) char *base64_encoded = NULL, *pin_string = NULL; +- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL; +- size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0; ++ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL; ++ size_t blob_size, policy_hash_size, decrypted_key_size, pubkey_size, salt_size = 0, srk_buf_size = 0; + _cleanup_(erase_and_freep) void *decrypted_key = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + uint32_t hash_pcr_mask, pubkey_pcr_mask; +@@ -92,6 +92,8 @@ _public_ int cryptsetup_token_open_pin( + &policy_hash_size, + &salt, + &salt_size, ++ &srk_buf, ++ &srk_buf_size, + &flags); + if (r < 0) + return log_debug_open_error(cd, r); +@@ -114,6 +116,8 @@ _public_ int cryptsetup_token_open_pin( + policy_hash_size, + salt, + salt_size, ++ srk_buf, ++ srk_buf_size, + flags, + &decrypted_key, + &decrypted_key_size); +@@ -172,9 +176,9 @@ _public_ void cryptsetup_token_dump( + const char *json /* validated 'systemd-tpm2' token if cryptsetup_token_validate is defined */) { + + _cleanup_free_ char *hash_pcrs_str = NULL, *pubkey_pcrs_str = NULL, *blob_str = NULL, *policy_hash_str = NULL, *pubkey_str = NULL; +- _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL; ++ _cleanup_free_ void *blob = NULL, *pubkey = NULL, *policy_hash = NULL, *salt = NULL, *srk_buf = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; +- size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0; ++ size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + uint16_t pcr_bank, primary_alg; + TPM2Flags flags = 0; +@@ -201,6 +205,8 @@ _public_ void cryptsetup_token_dump( + &policy_hash_size, + &salt, + &salt_size, ++ &srk_buf, ++ &srk_buf_size, + &flags); + if (r < 0) + return (void) crypt_log_debug_errno(cd, r, "Failed to parse " TOKEN_NAME " JSON fields: %m"); +@@ -234,6 +240,7 @@ _public_ void cryptsetup_token_dump( + crypt_log(cd, "\ttpm2-policy-hash:" CRYPT_DUMP_LINE_SEP "%s\n", policy_hash_str); + crypt_log(cd, "\ttpm2-pin: %s\n", true_false(flags & TPM2_FLAGS_USE_PIN)); + crypt_log(cd, "\ttpm2-salt: %s\n", true_false(salt)); ++ crypt_log(cd, "\ttpm2-srk: %s\n", true_false(srk_buf)); + } + + /* +diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +index 80a2c0d316..af747af613 100644 +--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +@@ -29,6 +29,8 @@ int acquire_luks2_key( + size_t policy_hash_size, + const void *salt, + size_t salt_size, ++ const void *srk_buf, ++ size_t srk_buf_size, + TPM2Flags flags, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size) { +@@ -88,5 +90,6 @@ int acquire_luks2_key( + primary_alg, + key_data, key_data_size, + policy_hash, policy_hash_size, ++ srk_buf, srk_buf_size, + ret_decrypted_key, ret_decrypted_key_size); + } +diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h +index 36d514caa0..1143f5fd9f 100644 +--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h ++++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.h +@@ -22,6 +22,8 @@ int acquire_luks2_key( + size_t policy_hash_size, + const void *salt, + size_t salt_size, ++ const void *srk_buf, ++ size_t srk_buf_size, + TPM2Flags flags, + void **ret_decrypted_key, + size_t *ret_decrypted_key_size); +diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c +index 2a8a38c593..6b650b0d26 100644 +--- a/src/cryptsetup/cryptsetup-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tpm2.c +@@ -72,6 +72,8 @@ int acquire_tpm2_key( + size_t policy_hash_size, + const void *salt, + size_t salt_size, ++ const void *srk_buf, ++ size_t srk_buf_size, + TPM2Flags flags, + usec_t until, + bool headless, +@@ -139,6 +141,8 @@ int acquire_tpm2_key( + blob_size, + policy_hash, + policy_hash_size, ++ srk_buf, ++ srk_buf_size, + ret_decrypted_key, + ret_decrypted_key_size); + +@@ -179,6 +183,8 @@ int acquire_tpm2_key( + blob_size, + policy_hash, + policy_hash_size, ++ srk_buf, ++ srk_buf_size, + ret_decrypted_key, + ret_decrypted_key_size); + /* We get this error in case there is an authentication policy mismatch. This should +@@ -208,6 +214,8 @@ int find_tpm2_auto_data( + size_t *ret_policy_hash_size, + void **ret_salt, + size_t *ret_salt_size, ++ void **ret_srk_buf, ++ size_t *ret_srk_buf_size, + TPM2Flags *ret_flags, + int *ret_keyslot, + int *ret_token) { +@@ -217,9 +225,9 @@ int find_tpm2_auto_data( + assert(cd); + + for (token = start_token; token < sym_crypt_token_max(CRYPT_LUKS2); token++) { +- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL; ++ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; +- size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0; ++ size_t blob_size, policy_hash_size, pubkey_size, salt_size = 0, srk_buf_size = 0; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + uint16_t pcr_bank, primary_alg; + TPM2Flags flags; +@@ -242,6 +250,7 @@ int find_tpm2_auto_data( + &blob, &blob_size, + &policy_hash, &policy_hash_size, + &salt, &salt_size, ++ &srk_buf, &srk_buf_size, + &flags); + if (r == -EUCLEAN) /* Gracefully handle issues in JSON fields not owned by us */ + continue; +@@ -268,6 +277,8 @@ int find_tpm2_auto_data( + *ret_salt_size = salt_size; + *ret_keyslot = keyslot; + *ret_token = token; ++ *ret_srk_buf = TAKE_PTR(srk_buf); ++ *ret_srk_buf_size = srk_buf_size; + *ret_flags = flags; + return 0; + } +diff --git a/src/cryptsetup/cryptsetup-tpm2.h b/src/cryptsetup/cryptsetup-tpm2.h +index f6549b7d1d..c3d56ac979 100644 +--- a/src/cryptsetup/cryptsetup-tpm2.h ++++ b/src/cryptsetup/cryptsetup-tpm2.h +@@ -30,6 +30,8 @@ int acquire_tpm2_key( + size_t policy_hash_size, + const void *salt, + size_t salt_size, ++ const void *srk_buf, ++ size_t salt_srk_buf_size, + TPM2Flags flags, + usec_t until, + bool headless, +@@ -53,6 +55,8 @@ int find_tpm2_auto_data( + size_t *ret_policy_hash_size, + void **ret_salt, + size_t *ret_salt_size, ++ void **ret_srk_buf, ++ size_t *ret_srk_size, + TPM2Flags *ret_flags, + int *ret_keyslot, + int *ret_token); +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index d46a88c9fb..caef45637c 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -1675,6 +1675,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( + key_data, key_data_size, + /* policy_hash= */ NULL, /* policy_hash_size= */ 0, /* we don't know the policy hash */ + /* salt= */ NULL, /* salt_size= */ 0, ++ /* srk_buf= */ NULL, /* srk_buf_size= */ 0, + arg_tpm2_pin ? TPM2_FLAGS_USE_PIN : 0, + until, + arg_headless, +@@ -1720,8 +1721,8 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( + * works. */ + + for (;;) { +- _cleanup_free_ void *pubkey = NULL, *salt = NULL; +- size_t pubkey_size = 0, salt_size = 0; ++ _cleanup_free_ void *pubkey = NULL, *salt = NULL, *srk_buf = NULL; ++ size_t pubkey_size = 0, salt_size = 0, srk_buf_size = 0; + uint32_t hash_pcr_mask, pubkey_pcr_mask; + uint16_t pcr_bank, primary_alg; + TPM2Flags tpm2_flags; +@@ -1738,6 +1739,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( + &blob, &blob_size, + &policy_hash, &policy_hash_size, + &salt, &salt_size, ++ &srk_buf, &srk_buf_size, + &tpm2_flags, + &keyslot, + &token); +@@ -1768,6 +1770,7 @@ static int attach_luks_or_plain_or_bitlk_by_tpm2( + blob, blob_size, + policy_hash, policy_hash_size, + salt, salt_size, ++ srk_buf, srk_buf_size, + tpm2_flags, + until, + arg_headless, +diff --git a/src/partition/repart.c b/src/partition/repart.c +index 0075932c09..57e1a8052a 100644 +--- a/src/partition/repart.c ++++ b/src/partition/repart.c +@@ -3020,8 +3020,8 @@ static int partition_encrypt( + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_(erase_and_freep) void *secret = NULL; + _cleanup_free_ void *pubkey = NULL; +- _cleanup_free_ void *blob = NULL, *hash = NULL; +- size_t secret_size, blob_size, hash_size, pubkey_size = 0; ++ _cleanup_free_ void *blob = NULL, *hash = NULL, *srk_buf = NULL; ++ size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0; + ssize_t base64_encoded_size; + uint16_t pcr_bank, primary_alg; + int keyslot; +@@ -3046,7 +3046,9 @@ static int partition_encrypt( + &blob, &blob_size, + &hash, &hash_size, + &pcr_bank, +- &primary_alg); ++ &primary_alg, ++ &srk_buf, ++ &srk_buf_size); + if (r < 0) + return log_error_errno(r, "Failed to seal to TPM2: %m"); + +@@ -3078,6 +3080,7 @@ static int partition_encrypt( + blob, blob_size, + hash, hash_size, + NULL, 0, /* no salt because tpm2_seal has no pin */ ++ srk_buf, srk_buf_size, + 0, + &v); + if (r < 0) +diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c +index e9cafb8097..f55c4ac96e 100644 +--- a/src/shared/creds-util.c ++++ b/src/shared/creds-util.c +@@ -657,7 +657,9 @@ int encrypt_credential_and_warn( + &tpm2_blob, &tpm2_blob_size, + &tpm2_policy_hash, &tpm2_policy_hash_size, + &tpm2_pcr_bank, +- &tpm2_primary_alg); ++ &tpm2_primary_alg, ++ /* ret_srk_buf= */ NULL, ++ /* ret_srk_buf_size= */ 0); + if (r < 0) { + if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) + log_warning("Firmware reported a TPM2 being present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled."); +@@ -987,6 +989,10 @@ int decrypt_credential_and_warn( + le32toh(z->size)); + } + ++ /* ++ * TODO: Add the SRK data to the credential structure so it can be plumbed ++ * through and used to verify the TPM session. ++ */ + r = tpm2_unseal(tpm2_device, + le64toh(t->pcr_mask), + le16toh(t->pcr_bank), +@@ -1000,6 +1006,8 @@ int decrypt_credential_and_warn( + le32toh(t->blob_size), + t->policy_hash_and_blob + le32toh(t->blob_size), + le32toh(t->policy_hash_size), ++ /* srk_buf= */ NULL, ++ /* srk_buf_size= */ 0, + &tpm2_key, + &tpm2_key_size); + if (r < 0) +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 3960c0aed7..55153e79f4 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -13,6 +13,7 @@ + #include "fs-util.h" + #include "hexdecoct.h" + #include "hmac.h" ++#include "lockfile-util.h" + #include "memory-util.h" + #include "openssl-util.h" + #include "parse-util.h" +@@ -30,6 +31,7 @@ static void *libtss2_mu_dl = NULL; + + TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL; + TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL; ++TSS2_RC (*sym_Esys_EvictControl)(ESYS_CONTEXT *esysContext, ESYS_TR auth, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPMI_DH_PERSISTENT persistentHandle, ESYS_TR *newObjectHandle); + void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL; + TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL; + void (*sym_Esys_Free)(void *ptr) = NULL; +@@ -44,11 +46,15 @@ TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySes + TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL; + TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL; + TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL; ++TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_PUBLIC **outPublic, TPM2B_NAME **name, TPM2B_NAME **qualifiedName); + TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL; + TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL; + TSS2_RC (*sym_Esys_TRSess_GetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION *flags); + TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask); + TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name); ++TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle); ++TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object); ++TSS2_RC (*sym_Esys_TR_Serialize)(ESYS_CONTEXT *esys_context, ESYS_TR object, uint8_t **buffer, size_t *buffer_size); + TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL; + TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL; + TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation); +@@ -67,6 +73,7 @@ int dlopen_tpm2(void) { + &libtss2_esys_dl, "libtss2-esys.so.0", LOG_DEBUG, + DLSYM_ARG(Esys_Create), + DLSYM_ARG(Esys_CreatePrimary), ++ DLSYM_ARG(Esys_EvictControl), + DLSYM_ARG(Esys_Finalize), + DLSYM_ARG(Esys_FlushContext), + DLSYM_ARG(Esys_Free), +@@ -81,11 +88,15 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Esys_PolicyAuthValue), + DLSYM_ARG(Esys_PolicyGetDigest), + DLSYM_ARG(Esys_PolicyPCR), ++ DLSYM_ARG(Esys_ReadPublic), + DLSYM_ARG(Esys_StartAuthSession), + DLSYM_ARG(Esys_Startup), + DLSYM_ARG(Esys_TRSess_GetAttributes), + DLSYM_ARG(Esys_TRSess_SetAttributes), ++ DLSYM_ARG(Esys_TR_FromTPMPublic), + DLSYM_ARG(Esys_TR_GetName), ++ DLSYM_ARG(Esys_TR_Deserialize), ++ DLSYM_ARG(Esys_TR_Serialize), + DLSYM_ARG(Esys_TR_SetAuth), + DLSYM_ARG(Esys_Unseal), + DLSYM_ARG(Esys_VerifySignature)); +@@ -249,7 +260,7 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) { + return NULL; + + _cleanup_tpm2_context_ Tpm2Context *context = (Tpm2Context*)handle->tpm2_context; +- if (context) ++ if (context && !handle->keep) + tpm2_handle_flush(context->esys_context, handle->esys_handle); + + return mfree(handle); +@@ -332,55 +343,200 @@ static int tpm2_credit_random(Tpm2Context *c) { + return 0; + } + +-static int tpm2_make_primary( +- Tpm2Context *c, +- Tpm2Handle **ret_primary, +- TPMI_ALG_PUBLIC alg, +- TPMI_ALG_PUBLIC *ret_alg) { ++const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) { + +- static const TPM2B_SENSITIVE_CREATE primary_sensitive = {}; +- static const TPM2B_PUBLIC primary_template_ecc = { +- .size = sizeof(TPMT_PUBLIC), +- .publicArea = { +- .type = TPM2_ALG_ECC, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, +- .parameters.eccDetail = { +- .symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, ++ /* ++ * Set up array so flags can be used directly as an input. ++ * ++ * Templates for SRK come from the spec: ++ * - https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf ++ * ++ * However, note their is some lore here. On Linux, the SRK has it's unique field set to size 0 and ++ * on Windows the SRK has their unique data set to keyLen in bytes of zeros. ++ */ ++ assert(flags >= 0); ++ assert(flags <= _TPM2_SRK_TEMPLATE_MAX); ++ ++ static const TPM2B_PUBLIC templ[_TPM2_SRK_TEMPLATE_MAX + 1] = { ++ /* index 0 RSA old */ ++ [0] = { ++ .publicArea = { ++ .type = TPM2_ALG_RSA, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, ++ .parameters.rsaDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .keyBits = 2048, + }, +- .scheme.scheme = TPM2_ALG_NULL, +- .curveID = TPM2_ECC_NIST_P256, +- .kdf.scheme = TPM2_ALG_NULL, + }, + }, +- }; +- static const TPM2B_PUBLIC primary_template_rsa = { +- .size = sizeof(TPMT_PUBLIC), +- .publicArea = { +- .type = TPM2_ALG_RSA, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, +- .parameters.rsaDetail = { +- .symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, ++ [TPM2_SRK_TEMPLATE_ECC] = { ++ .publicArea = { ++ .type = TPM2_ALG_ECC, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, ++ .parameters.eccDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .curveID = TPM2_ECC_NIST_P256, ++ .kdf.scheme = TPM2_ALG_NULL, ++ }, ++ }, ++ }, ++ [TPM2_SRK_TEMPLATE_NEW_STYLE] = { ++ .publicArea = { ++ .type = TPM2_ALG_RSA, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA, ++ .parameters.rsaDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .keyBits = 2048, ++ }, ++ }, ++ }, ++ [TPM2_SRK_TEMPLATE_NEW_STYLE|TPM2_SRK_TEMPLATE_ECC] = { ++ .publicArea = { ++ .type = TPM2_ALG_ECC, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA, ++ .parameters.eccDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .curveID = TPM2_ECC_NIST_P256, ++ .kdf.scheme = TPM2_ALG_NULL, + }, +- .scheme.scheme = TPM2_ALG_NULL, +- .keyBits = 2048, + }, + }, + }; + ++ return &templ[flags]; ++} ++ ++/* ++ * Why and what is an SRK? ++ * TL;DR provides a working space for those without owner auth. The user enrolling ++ * the disk may not have access to the TPMs owner hierarchy auth, so they need a ++ * working space. This working space is at the defined address of 0x81000001. ++ * Details can be found here: ++ * - https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf ++ */ ++#define SRK_HANDLE UINT32_C(0x81000001) ++ ++/* ++ * Retrieves the SRK handle if present. Returns 0 if SRK not present, 1 if present ++ * and < 0 on error ++ */ ++static int tpm2_get_srk( ++ Tpm2Context *c, ++ TPMI_ALG_PUBLIC *ret_alg, ++ Tpm2Handle *ret_primary) { ++ ++ TPMI_YES_NO more_data; ++ ESYS_TR primary_tr = ESYS_TR_NONE; ++ _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *cap_data = NULL; ++ ++ assert(c); ++ assert(ret_primary); ++ ++ TSS2_RC rc = sym_Esys_GetCapability(c->esys_context, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ TPM2_CAP_HANDLES, ++ SRK_HANDLE, ++ 1, ++ &more_data, ++ &cap_data); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to enumerate handles searching for SRK: %s", ++ sym_Tss2_RC_Decode(rc)); ++ ++ /* Did Not find SRK, indicate this by returning 0 */ ++ if (cap_data->data.handles.count == 0 || cap_data->data.handles.handle[0] != SRK_HANDLE) { ++ ret_primary->esys_handle = ESYS_TR_NONE; ++ ++ if (ret_alg) ++ *ret_alg = 0; ++ return 0; ++ } ++ ++ log_debug("Found SRK on TPM."); ++ ++ /* convert the raw handle to an ESYS_TR */ ++ TPM2_HANDLE handle = cap_data->data.handles.handle[0]; ++ rc = sym_Esys_TR_FromTPMPublic(c->esys_context, ++ handle, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ &primary_tr); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to convert ray handle to ESYS_TR for SRK: %s", ++ sym_Tss2_RC_Decode(rc)); ++ ++ /* Get the algorithm if the caller wants it */ ++ _cleanup_(Esys_Freep) TPM2B_PUBLIC *out_public = NULL; ++ if (ret_alg) { ++ rc = sym_Esys_ReadPublic( ++ c->esys_context, ++ primary_tr, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ &out_public, ++ NULL, ++ NULL); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to convert ray handle to ESYS_TR for SRK: %s", ++ sym_Tss2_RC_Decode(rc)); ++ } ++ ++ ret_primary->esys_handle = primary_tr; ++ ++ if (ret_alg) ++ *ret_alg = out_public->publicArea.type; ++ ++ return 1; ++} ++ ++static int tpm2_make_primary( ++ Tpm2Context *c, ++ TPMI_ALG_PUBLIC alg, ++ bool use_srk_model, ++ TPMI_ALG_PUBLIC *ret_alg, ++ Tpm2Handle **ret_primary) { ++ ++ static const TPM2B_SENSITIVE_CREATE primary_sensitive = {}; + static const TPML_PCR_SELECTION creation_pcr = {}; ++ const TPM2B_PUBLIC *primary_template = NULL; ++ Tpm2SRKTemplateFlags base_flags = use_srk_model ? TPM2_SRK_TEMPLATE_NEW_STYLE : 0; ++ _cleanup_(release_lock_file) LockFile srk_lock = LOCK_FILE_INIT; + TSS2_RC rc; + usec_t ts; + int r; + +- log_debug("Creating primary key on TPM."); ++ log_debug("Creating %s on TPM.", use_srk_model ? "SRK" : "Transient Primary Key"); + + /* So apparently not all TPM2 devices support ECC. ECC is generally preferably, because it's so much + * faster, noticeably so (~10s vs. ~240ms on my system). Hence, unless explicitly configured let's +@@ -393,7 +549,42 @@ static int tpm2_make_primary( + if (r < 0) + return r; + ++ /* we only need the SRK lock when making the SRK since its not atomic, transient ++ * primary creations don't even matter if they stomp on each other, the TPM will ++ * keep kicking back the same key. ++ */ ++ if (use_srk_model) { ++ r = make_lock_file("/run/systemd/tpm2-srk-init", LOCK_EX, &srk_lock); ++ if (r < 0) ++ return log_error_errno(r, "Failed to take network zone lock: %m"); ++ } ++ ++ /* Find existing SRK and use it if present */ ++ if (use_srk_model) { ++ TPMI_ALG_PUBLIC got_alg = TPM2_ALG_NULL; ++ r = tpm2_get_srk(c, &got_alg, primary); ++ if (r < 0) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to establish if SRK is present"); ++ if (r == 1) { ++ log_debug("Discovered existing SRK"); ++ ++ if (alg != 0 && alg != got_alg) ++ log_warning("Caller asked for specific algorithm %u, but existing SRK is %u, ignoring", ++ alg, got_alg); ++ ++ if (ret_alg) ++ *ret_alg = alg; ++ if (ret_primary) ++ *ret_primary = TAKE_PTR(primary); ++ return 0; ++ } ++ log_debug("Did not find SRK, generating..."); ++ } ++ + if (IN_SET(alg, 0, TPM2_ALG_ECC)) { ++ primary_template = tpm2_get_primary_template(base_flags | TPM2_SRK_TEMPLATE_ECC); ++ + rc = sym_Esys_CreatePrimary( + c->esys_context, + ESYS_TR_RH_OWNER, +@@ -401,7 +592,7 @@ static int tpm2_make_primary( + ESYS_TR_NONE, + ESYS_TR_NONE, + &primary_sensitive, +- &primary_template_ecc, ++ primary_template, + NULL, + &creation_pcr, + &primary->esys_handle, +@@ -423,6 +614,8 @@ static int tpm2_make_primary( + } + + if (IN_SET(alg, 0, TPM2_ALG_RSA)) { ++ primary_template = tpm2_get_primary_template(base_flags); ++ + rc = sym_Esys_CreatePrimary( + c->esys_context, + ESYS_TR_RH_OWNER, +@@ -430,7 +623,7 @@ static int tpm2_make_primary( + ESYS_TR_NONE, + ESYS_TR_NONE, + &primary_sensitive, +- &primary_template_rsa, ++ primary_template, + NULL, + &creation_pcr, + &primary->esys_handle, +@@ -450,7 +643,17 @@ static int tpm2_make_primary( + log_debug("Successfully created RSA primary key on TPM."); + } + +- log_debug("Generating primary key on TPM2 took %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC)); ++ log_debug("Generating %s on the TPM2 took %s.", use_srk_model ? "SRK" : "Transient Primary Key", ++ FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC)); ++ ++ if (use_srk_model) { ++ rc = sym_Esys_EvictControl(c->esys_context, ESYS_TR_RH_OWNER, primary->esys_handle, ++ ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, SRK_HANDLE, &primary->esys_handle); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to persist SRK within TPM: %s", sym_Tss2_RC_Decode(rc)); ++ primary->keep = true; ++ } + + if (ret_primary) + *ret_primary = TAKE_PTR(primary); +@@ -1774,10 +1977,13 @@ int tpm2_seal(const char *device, + void **ret_pcr_hash, + size_t *ret_pcr_hash_size, + uint16_t *ret_pcr_bank, +- uint16_t *ret_primary_alg) { ++ uint16_t *ret_primary_alg, ++ void **ret_srk_buf, ++ size_t *ret_srk_buf_size) { + + _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; + _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; ++ _cleanup_(Esys_Freep) uint8_t *srk_buf = NULL; + static const TPML_PCR_SELECTION creation_pcr = {}; + _cleanup_(erase_and_freep) void *secret = NULL; + _cleanup_free_ void *hash = NULL; +@@ -1786,6 +1992,7 @@ int tpm2_seal(const char *device, + TPM2B_PUBLIC hmac_template; + usec_t start; + TSS2_RC rc; ++ size_t srk_buf_size; + int r; + + assert(pubkey || pubkey_size == 0); +@@ -1835,7 +2042,7 @@ int tpm2_seal(const char *device, + } + + _cleanup_tpm2_handle_ Tpm2Handle *primary = NULL; +- r = tpm2_make_primary(c, &primary, 0, &primary_alg); ++ r = tpm2_make_primary(c, /* alg = */0, !!ret_srk_buf, &primary_alg, &primary); + if (r < 0) + return r; + +@@ -1960,9 +2167,35 @@ int tpm2_seal(const char *device, + if (!hash) + return log_oom(); + ++ /* serialize the key for storage in the LUKS header. A deserialized ESYS_TR provides both ++ * the raw TPM handle as well as the object name. The object name is used to verify that ++ * the key we use later is the key we expect to establish the session with. ++ */ ++ if (ret_srk_buf) { ++ log_debug("Serializing SRK ESYS_TR reference"); ++ rc = sym_Esys_TR_Serialize(c->esys_context, primary->esys_handle, &srk_buf, &srk_buf_size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to serialize primary key: %s", sym_Tss2_RC_Decode(rc)); ++ } ++ + if (DEBUG_LOGGING) + log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); + ++ if (ret_srk_buf) { ++ /* ++ * make a copy since we don't want the caller to understand that ++ * ESYS allocated the pointer. It would make tracking what deallocator ++ * to use for srk_buf in which context a PITA. ++ */ ++ void *tmp = memdup(srk_buf, srk_buf_size); ++ if (!tmp) ++ return log_oom(); ++ ++ *ret_srk_buf = TAKE_PTR(tmp); ++ *ret_srk_buf_size = srk_buf_size; ++ } ++ + *ret_secret = TAKE_PTR(secret); + *ret_secret_size = hmac_sensitive.sensitive.data.size; + *ret_blob = TAKE_PTR(blob); +@@ -1990,6 +2223,8 @@ int tpm2_unseal(const char *device, + size_t blob_size, + const void *known_policy_hash, + size_t known_policy_hash_size, ++ const void *srk_buf, ++ size_t srk_buf_size, + void **ret_secret, + size_t *ret_secret_size) { + +@@ -2045,18 +2280,39 @@ int tpm2_unseal(const char *device, + if (r < 0) + return r; + ++ /* If their is a primary key we trust, like an SRK, use it */ + _cleanup_tpm2_handle_ Tpm2Handle *primary = NULL; +- r = tpm2_make_primary(c, &primary, primary_alg, NULL); +- if (r < 0) +- return r; ++ if (srk_buf) { ++ ++ r = tpm2_handle_new(c, &primary); ++ if (r < 0) ++ return r; ++ ++ primary->keep = true; ++ ++ log_debug("Found existing SRK key to use, deserializing ESYS_TR"); ++ rc = sym_Esys_TR_Deserialize( ++ c->esys_context, ++ srk_buf, ++ srk_buf_size, ++ &primary->esys_handle); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to deserialize primary key: %s", sym_Tss2_RC_Decode(rc)); ++ /* old callers without an SRK still need to create a key */ ++ } else { ++ r = tpm2_make_primary(c, primary_alg, false, NULL, &primary); ++ if (r < 0) ++ return r; ++ } + + log_debug("Loading HMAC key into TPM."); + + /* + * Nothing sensitive on the bus, no need for encryption. Even if an attacker +- * gives you back a different key, the session initiation will fail if a pin +- * is provided. If an attacker gives back a bad key, we already lost since +- * primary key is not verified and they could attack there as well. ++ * gives you back a different key, the session initiation will fail. In the ++ * SRK model, the tpmKey is verified. In the non-srk model, with pin, the bindKey ++ * provides protections. + */ + _cleanup_tpm2_handle_ Tpm2Handle *hmac_key = NULL; + r = tpm2_handle_new(c, &hmac_key); +@@ -2491,6 +2747,8 @@ int tpm2_make_luks2_json( + size_t policy_hash_size, + const void *salt, + size_t salt_size, ++ const void *srk_buf, ++ size_t srk_buf_size, + TPM2Flags flags, + JsonVariant **ret) { + +@@ -2531,7 +2789,8 @@ int tpm2_make_luks2_json( + JSON_BUILD_PAIR("tpm2-pin", JSON_BUILD_BOOLEAN(flags & TPM2_FLAGS_USE_PIN)), + JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey_pcrs", JSON_BUILD_VARIANT(pkmj)), + JSON_BUILD_PAIR_CONDITION(pubkey_pcr_mask != 0, "tpm2_pubkey", JSON_BUILD_BASE64(pubkey, pubkey_size)), +- JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size)))); ++ JSON_BUILD_PAIR_CONDITION(salt, "tpm2_salt", JSON_BUILD_BASE64(salt, salt_size)), ++ JSON_BUILD_PAIR_CONDITION(srk_buf, "tpm2_srk", JSON_BUILD_BASE64(srk_buf, srk_buf_size)))); + if (r < 0) + return r; + +@@ -2556,10 +2815,12 @@ int tpm2_parse_luks2_json( + size_t *ret_policy_hash_size, + void **ret_salt, + size_t *ret_salt_size, ++ void **ret_srk_buf, ++ size_t *ret_srk_buf_size, + TPM2Flags *ret_flags) { + +- _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL; +- size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0; ++ _cleanup_free_ void *blob = NULL, *policy_hash = NULL, *pubkey = NULL, *salt = NULL, *srk_buf = NULL; ++ size_t blob_size = 0, policy_hash_size = 0, pubkey_size = 0, salt_size = 0, srk_buf_size = 0; + uint32_t hash_pcr_mask = 0, pubkey_pcr_mask = 0; + uint16_t primary_alg = TPM2_ALG_ECC; /* ECC was the only supported algorithm in systemd < 250, use that as implied default, for compatibility */ + uint16_t pcr_bank = UINT16_MAX; /* default: pick automatically */ +@@ -2666,6 +2927,13 @@ int tpm2_parse_luks2_json( + } else if (pubkey_pcr_mask != 0) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Public key PCR mask set, but not public key included in JSON data, refusing."); + ++ w = json_variant_by_key(v, "tpm2_srk"); ++ if (w) { ++ r = json_variant_unbase64(w, &srk_buf, &srk_buf_size); ++ if (r < 0) ++ return log_debug_errno(r, "Invalid base64 data in 'tpm2_srk' field."); ++ } ++ + if (ret_keyslot) + *ret_keyslot = keyslot; + if (ret_hash_pcr_mask) +@@ -2694,6 +2962,10 @@ int tpm2_parse_luks2_json( + *ret_salt_size = salt_size; + if (ret_flags) + *ret_flags = flags; ++ if (ret_srk_buf) ++ *ret_srk_buf = TAKE_PTR(srk_buf); ++ if (ret_srk_buf_size) ++ *ret_srk_buf_size = srk_buf_size; + + return 0; + } +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index cc43bbfbfb..3c4d045197 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -13,6 +13,12 @@ typedef enum TPM2Flags { + } TPM2Flags; + + ++typedef enum Tpm2SRKTemplateFlags { ++ TPM2_SRK_TEMPLATE_ECC = 1 << 0, ++ TPM2_SRK_TEMPLATE_NEW_STYLE = 1 << 1, ++ _TPM2_SRK_TEMPLATE_MAX = TPM2_SRK_TEMPLATE_NEW_STYLE|TPM2_SRK_TEMPLATE_ECC, ++} Tpm2SRKTemplateFlags; ++ + /* As per https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a + * TPM2 on a Client PC must have at least 24 PCRs. This hardcodes our expectation of 24. */ + #define TPM2_PCRS_MAX 24U +@@ -66,8 +72,8 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], siz + + int dlopen_tpm2(void); + +-int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg); +-int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, void **ret_secret, size_t *ret_secret_size); ++int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); ++int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); + + typedef struct { + unsigned n_ref; +@@ -86,6 +92,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref); + typedef struct { + Tpm2Context *tpm2_context; + ESYS_TR esys_handle; ++ bool keep; + } Tpm2Handle; + + #define _tpm2_handle(c, h) { .tpm2_context = (c), .esys_handle = (h), } +@@ -125,6 +132,8 @@ char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l); + size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l); + #define tpm2_tpml_pcr_selection_is_empty(l) (tpm2_tpml_pcr_selection_weight(l) == 0) + ++const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags); ++ + #else /* HAVE_TPM2 */ + typedef struct {} Tpm2Context; + typedef struct {} Tpm2Handle; +@@ -136,8 +145,8 @@ int tpm2_find_device_auto(int log_level, char **ret); + int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret); + int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret); + +-int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, TPM2Flags flags, JsonVariant **ret); +-int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, TPM2Flags *ret_flags); ++int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *salt, size_t salt_size, const void *srk_buf, size_t srk_buf_size, TPM2Flags flags, JsonVariant **ret); ++int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, void **ret_srk_buf, size_t *ret_srk_buf_size, TPM2Flags *ret_flags); + + /* Default to PCR 7 only */ + #define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7) +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 20baa0f261..2c696e443d 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -409,6 +409,97 @@ TEST(tpml_pcr_selection_add_sub) { + expected2, expected2_count); + } + ++ ++/* this test includes TPM2 specific data structures */ ++TEST(tpm2_get_primary_template) { ++ ++ /* ++ * Verify that if someone changes the template code, they know they're breaking things. ++ * Templates MUST be changed in a backwards compatible way. ++ * ++ */ ++ static const TPM2B_PUBLIC templ[] = { ++ /* index 0 RSA old */ ++ [0] = { ++ .publicArea = { ++ .type = TPM2_ALG_RSA, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, ++ .parameters.rsaDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .keyBits = 2048, ++ }, ++ }, ++ }, ++ /* Index 1 ECC old */ ++ [TPM2_SRK_TEMPLATE_ECC] = { ++ .publicArea = { ++ .type = TPM2_ALG_ECC, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, ++ .parameters.eccDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .curveID = TPM2_ECC_NIST_P256, ++ .kdf.scheme = TPM2_ALG_NULL, ++ }, ++ }, ++ }, ++ /* index 2 RSA SRK */ ++ [TPM2_SRK_TEMPLATE_NEW_STYLE] = { ++ .publicArea = { ++ .type = TPM2_ALG_RSA, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA, ++ .parameters.rsaDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .keyBits = 2048, ++ }, ++ }, ++ }, ++ /* Index 3 ECC SRK */ ++ [TPM2_SRK_TEMPLATE_NEW_STYLE | TPM2_SRK_TEMPLATE_ECC] = { ++ .publicArea = { ++ .type = TPM2_ALG_ECC, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA, ++ .parameters.eccDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .curveID = TPM2_ECC_NIST_P256, ++ .kdf.scheme = TPM2_ALG_NULL, ++ }, ++ }, ++ }, ++ }; ++ ++ assert_cc(ELEMENTSOF(templ) == _TPM2_SRK_TEMPLATE_MAX + 1); ++ ++ for (size_t i = 0; i < ELEMENTSOF(templ); i++) { ++ /* the index counter lines up with the flags and the expected template received */ ++ const TPM2B_PUBLIC *got = tpm2_get_primary_template((Tpm2SRKTemplateFlags)i); ++ assert_se(memcmp(&templ[i], got, sizeof(*got)) == 0); ++ } ++} ++ + #endif /* HAVE_TPM2 */ + + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0517-tpm2-fix-nits-from-PR-26185.patch b/SOURCES/0517-tpm2-fix-nits-from-PR-26185.patch new file mode 100644 index 0000000..88b9f3f --- /dev/null +++ b/SOURCES/0517-tpm2-fix-nits-from-PR-26185.patch @@ -0,0 +1,76 @@ +From ec8f1d04f4f10ac1cd2de0ef750199253cba2bf2 Mon Sep 17 00:00:00 2001 +From: William Roberts +Date: Tue, 4 Apr 2023 11:14:17 -0500 +Subject: [PATCH] tpm2: fix nits from PR #26185 + +Fixes: + - Comment style + - Alignment style + - cleanup macro usage + - incorrect error message[1] + +1. Thanks to tempusfugit991@gmail.com for pointing out the error +message mistake. + +Signed-off-by: William Roberts +(cherry picked from commit 96181b7a893da444fa9adcd1e7c95769d97c2a95) + +Related: RHEL-16182 +--- + src/cryptenroll/cryptenroll-tpm2.c | 2 +- + src/shared/creds-util.c | 6 ++---- + src/shared/tpm2-util.c | 4 ++-- + 3 files changed, 5 insertions(+), 7 deletions(-) + +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index ab43135dc7..a2f57ecff4 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -142,7 +142,7 @@ int enroll_tpm2(struct crypt_device *cd, + _cleanup_(erase_and_freep) void *secret = NULL; + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; +- _cleanup_(freep) void *srk_buf = NULL; ++ _cleanup_free_ void *srk_buf = NULL; + size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0; + _cleanup_free_ void *blob = NULL, *hash = NULL, *pubkey = NULL; + uint16_t pcr_bank, primary_alg; +diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c +index f55c4ac96e..902275215a 100644 +--- a/src/shared/creds-util.c ++++ b/src/shared/creds-util.c +@@ -989,10 +989,8 @@ int decrypt_credential_and_warn( + le32toh(z->size)); + } + +- /* +- * TODO: Add the SRK data to the credential structure so it can be plumbed +- * through and used to verify the TPM session. +- */ ++ // TODO: Add the SRK data to the credential structure so it can be plumbed ++ // through and used to verify the TPM session. + r = tpm2_unseal(tpm2_device, + le64toh(t->pcr_mask), + le16toh(t->pcr_bank), +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 55153e79f4..ab88b94f1f 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -556,7 +556,7 @@ static int tpm2_make_primary( + if (use_srk_model) { + r = make_lock_file("/run/systemd/tpm2-srk-init", LOCK_EX, &srk_lock); + if (r < 0) +- return log_error_errno(r, "Failed to take network zone lock: %m"); ++ return log_error_errno(r, "Failed to take TPM SRK lock: %m"); + } + + /* Find existing SRK and use it if present */ +@@ -571,7 +571,7 @@ static int tpm2_make_primary( + + if (alg != 0 && alg != got_alg) + log_warning("Caller asked for specific algorithm %u, but existing SRK is %u, ignoring", +- alg, got_alg); ++ alg, got_alg); + + if (ret_alg) + *ret_alg = alg; diff --git a/SOURCES/0518-tpm2-replace-magic-number.patch b/SOURCES/0518-tpm2-replace-magic-number.patch new file mode 100644 index 0000000..d870604 --- /dev/null +++ b/SOURCES/0518-tpm2-replace-magic-number.patch @@ -0,0 +1,25 @@ +From ee0427588b7052bee4c9fbb42eded8187466f7d1 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 9 Dec 2022 15:05:49 -0500 +Subject: [PATCH] tpm2: replace magic number + +(cherry picked from commit 1200777b21936bf5647a90504e0ea27e3ec3e42b) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index ab88b94f1f..002e2c01da 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -2097,7 +2097,7 @@ int tpm2_seal(const char *device, + .nameAlg = TPM2_ALG_SHA256, + .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT, + .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL, +- .unique.keyedHash.size = 32, ++ .unique.keyedHash.size = SHA256_DIGEST_SIZE, + .authPolicy = *policy_digest, + }, + }; diff --git a/SOURCES/0519-tpm2-add-tpm2_digest_-functions.patch b/SOURCES/0519-tpm2-add-tpm2_digest_-functions.patch new file mode 100644 index 0000000..d86ba2c --- /dev/null +++ b/SOURCES/0519-tpm2-add-tpm2_digest_-functions.patch @@ -0,0 +1,271 @@ +From 6aa706de80fa716b7d54adfc4094884707518d95 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 14 Dec 2022 10:46:13 -0500 +Subject: [PATCH] tpm2: add tpm2_digest_*() functions + +These functions allow extending (or initializing) a TPM2B_DIGEST with additional +data, using a specified hash operation. This is needed to perform hash +calculations instead of relying on the TPM to perform the calculations in +trial sessions. + +(cherry picked from commit da92d39a8577e792075009782d419b423414ad6e) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 86 ++++++++++++++++++++++++++++++++--- + src/shared/tpm2-util.h | 13 ++++++ + src/test/test-tpm2.c | 100 +++++++++++++++++++++++++++++++++++++++++ + 3 files changed, 194 insertions(+), 5 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 002e2c01da..d397c505f5 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1384,6 +1384,83 @@ static void hash_pin(const char *pin, size_t len, TPM2B_AUTH *auth) { + sha256_finish_ctx(&hash, auth->buffer); + } + ++/* Hash data into the digest. ++ * ++ * If 'extend' is true, the hashing operation starts with the existing digest hash (and the digest is ++ * required to have a hash and its size must be correct). If 'extend' is false, the digest size is ++ * initialized to the correct size for 'alg' and the hashing operation does not include any existing digest ++ * hash. If 'extend' is false and no data is provided, the digest is initialized to a zero digest. ++ * ++ * On success, the digest hash will be updated with the hashing operation result and the digest size will be ++ * correct for 'alg'. ++ * ++ * This currently only provides SHA256, so 'alg' must be TPM2_ALG_SHA256. */ ++int tpm2_digest_many( ++ TPMI_ALG_HASH alg, ++ TPM2B_DIGEST *digest, ++ const struct iovec data[], ++ size_t n_data, ++ bool extend) { ++ ++ struct sha256_ctx ctx; ++ ++ assert(digest); ++ assert(data || n_data == 0); ++ ++ if (alg != TPM2_ALG_SHA256) ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "Hash algorithm not supported: 0x%x", alg); ++ ++ if (extend && digest->size != SHA256_DIGEST_SIZE) ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "Digest size 0x%x, require 0x%x", ++ digest->size, (unsigned)SHA256_DIGEST_SIZE); ++ ++ /* Since we're hardcoding SHA256 (for now), we can check this at compile time. */ ++ assert_cc(sizeof(digest->buffer) >= SHA256_DIGEST_SIZE); ++ ++ CLEANUP_ERASE(ctx); ++ ++ sha256_init_ctx(&ctx); ++ ++ if (extend) ++ sha256_process_bytes(digest->buffer, digest->size, &ctx); ++ else { ++ *digest = (TPM2B_DIGEST){ .size = SHA256_DIGEST_SIZE, }; ++ if (n_data == 0) /* If not extending and no data, return zero hash */ ++ return 0; ++ } ++ ++ for (size_t i = 0; i < n_data; i++) ++ sha256_process_bytes(data[i].iov_base, data[i].iov_len, &ctx); ++ ++ sha256_finish_ctx(&ctx, digest->buffer); ++ ++ return 0; ++} ++ ++/* Same as tpm2_digest_many() but data is contained in TPM2B_DIGEST[]. The digests may be any size digests. */ ++int tpm2_digest_many_digests( ++ TPMI_ALG_HASH alg, ++ TPM2B_DIGEST *digest, ++ const TPM2B_DIGEST data[], ++ size_t n_data, ++ bool extend) { ++ ++ _cleanup_free_ struct iovec *iovecs = NULL; ++ ++ assert(data || n_data == 0); ++ ++ iovecs = new(struct iovec, n_data); ++ if (!iovecs) ++ return log_oom(); ++ ++ for (size_t i = 0; i < n_data; i++) ++ iovecs[i] = IOVEC_MAKE((void*) data[i].buffer, data[i].size); ++ ++ return tpm2_digest_many(alg, digest, iovecs, n_data, extend); ++} ++ + static bool tpm2_is_encryption_session(Tpm2Context *c, const Tpm2Handle *session) { + TPMA_SESSION flags = 0; + TSS2_RC rc; +@@ -1868,11 +1945,10 @@ static int tpm2_build_sealing_policy( + + /* TPM2_VerifySignature() will only verify the RSA part of the RSA+SHA256 signature, + * hence we need to do the SHA256 part ourselves, first */ +- TPM2B_DIGEST signature_hash = { +- .size = SHA256_DIGEST_SIZE, +- }; +- assert(sizeof(signature_hash.buffer) >= SHA256_DIGEST_SIZE); +- sha256_direct(approved_policy->buffer, approved_policy->size, signature_hash.buffer); ++ TPM2B_DIGEST signature_hash = *approved_policy; ++ r = tpm2_digest_rehash(TPM2_ALG_SHA256, &signature_hash); ++ if (r < 0) ++ return r; + + TPMT_SIGNATURE policy_signature = { + .sigAlg = TPM2_ALG_RSASSA, +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 3c4d045197..2744cd13bb 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -4,6 +4,7 @@ + #include + + #include "bitfield.h" ++#include "io-util.h" + #include "json.h" + #include "macro.h" + #include "sha256.h" +@@ -72,6 +73,18 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], siz + + int dlopen_tpm2(void); + ++int tpm2_digest_many(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const struct iovec data[], size_t count, bool extend); ++static inline int tpm2_digest_buffer(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const void *data, size_t len, bool extend) { ++ return tpm2_digest_many(alg, digest, &IOVEC_MAKE((void*) data, len), 1, extend); ++} ++int tpm2_digest_many_digests(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const TPM2B_DIGEST data[], size_t count, bool extend); ++static inline int tpm2_digest_rehash(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { ++ return tpm2_digest_many(alg, digest, NULL, 0, true); ++} ++static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { ++ return tpm2_digest_many(alg, digest, NULL, 0, false); ++} ++ + int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); + int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); + +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 2c696e443d..dfbea7b19a 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -1,5 +1,6 @@ + /* SPDX-License-Identifier: LGPL-2.1-or-later */ + ++#include "hexdecoct.h" + #include "tpm2-util.h" + #include "tests.h" + +@@ -500,6 +501,105 @@ TEST(tpm2_get_primary_template) { + } + } + ++static bool digest_check(const TPM2B_DIGEST *digest, const char *expect) { ++ _cleanup_free_ char *h = NULL; ++ ++ assert_se(digest); ++ assert_se(expect); ++ ++ h = hexmem(digest->buffer, digest->size); ++ assert_se(h); ++ ++ return streq(expect, h); ++} ++ ++static void digest_init_sha256(TPM2B_DIGEST *digest, const char *hash) { ++ _cleanup_free_ void *h = NULL; ++ size_t s = 0; ++ ++ assert_se(strlen(hash) == SHA256_DIGEST_SIZE * 2); ++ assert_se(strlen(hash) <= sizeof(digest->buffer) * 2); ++ ++ assert_se(unhexmem(hash, strlen(hash), &h, &s) == 0); ++ assert_se(s == SHA256_DIGEST_SIZE); ++ ++ memcpy_safe(digest->buffer, h, s); ++ digest->size = s; ++ ++ assert_se(digest_check(digest, hash)); ++} ++ ++TEST(digest_many) { ++ TPM2B_DIGEST d, d0, d1, d2, d3, d4; ++ ++ digest_init_sha256(&d0, "0000000000000000000000000000000000000000000000000000000000000000"); ++ digest_init_sha256(&d1, "17b7703d9d00776310ba032e88c1a8c2a9c630ebdd799db622f6631530789175"); ++ digest_init_sha256(&d2, "12998c017066eb0d2a70b94e6ed3192985855ce390f321bbdb832022888bd251"); ++ digest_init_sha256(&d3, "c3a65887fedd3fb4f5d0047e906dff830bcbd1293160909eb4b05f485e7387ad"); ++ digest_init_sha256(&d4, "6491fb4bc08fc0b2ef47fc63db57e249917885e69d8c0d99667df83a59107a33"); ++ ++ /* tpm2_digest_init, tpm2_digest_rehash */ ++ d = (TPM2B_DIGEST){ .size = 1, .buffer = { 2, }, }; ++ assert_se(tpm2_digest_init(TPM2_ALG_SHA256, &d) == 0); ++ assert_se(digest_check(&d, "0000000000000000000000000000000000000000000000000000000000000000")); ++ assert_se(tpm2_digest_rehash(TPM2_ALG_SHA256, &d) == 0); ++ assert_se(digest_check(&d, "66687aadf862bd776c8fc18b8e9f8e20089714856ee233b3902a591d0d5f2925")); ++ ++ d = d1; ++ assert_se(tpm2_digest_rehash(TPM2_ALG_SHA256, &d) == 0); ++ assert_se(digest_check(&d, "ab55014b5ace12ba70c3acc887db571585a83539aad3633d252a710f268f405c")); ++ assert_se(tpm2_digest_init(TPM2_ALG_SHA256, &d) == 0); ++ assert_se(digest_check(&d, "0000000000000000000000000000000000000000000000000000000000000000")); ++ ++ /* tpm2_digest_many_digests */ ++ assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, &d2, 1, false) == 0); ++ assert_se(digest_check(&d, "56571a1be3fbeab18d215f549095915a004b5788ca0d535be668559129a76f25")); ++ assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, &d2, 1, true) == 0); ++ assert_se(digest_check(&d, "99dedaee8f4d8d10a8be184399fde8740d5e17ff783ee5c288a4486e4ce3a1fe")); ++ ++ const TPM2B_DIGEST da1[] = { d2, d3, }; ++ assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, da1, ELEMENTSOF(da1), false) == 0); ++ assert_se(digest_check(&d, "525aa13ef9a61827778ec3acf16fbb23b65ae8770b8fb2684d3a33f9457dd6d8")); ++ assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, da1, ELEMENTSOF(da1), true) == 0); ++ assert_se(digest_check(&d, "399ca2aa98963d1bd81a2b58a7e5cda24bba1be88fb4da9aa73d97706846566b")); ++ ++ const TPM2B_DIGEST da2[] = { d3, d2, d0 }; ++ assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, da2, ELEMENTSOF(da2), false) == 0); ++ assert_se(digest_check(&d, "b26fd22db74d4cd896bff01c61aa498a575e4a553a7fb5a322a5fee36954313e")); ++ assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, da2, ELEMENTSOF(da2), true) == 0); ++ assert_se(digest_check(&d, "091e79a5b09d4048df49a680f966f3ff67910afe185c3baf9704c9ca45bcf259")); ++ ++ const TPM2B_DIGEST da3[] = { d4, d4, d4, d4, d3, d4, d4, d4, d4, }; ++ assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, da3, ELEMENTSOF(da3), false) == 0); ++ assert_se(digest_check(&d, "8eca947641b6002df79dfb571a7f78b7d0a61370a366f722386dfbe444d18830")); ++ assert_se(tpm2_digest_many_digests(TPM2_ALG_SHA256, &d, da3, ELEMENTSOF(da3), true) == 0); ++ assert_se(digest_check(&d, "f9ba17bc0bbe8794e9bcbf112e4d59a11eb68fffbcd5516a746e4857829dff04")); ++ ++ /* tpm2_digest_buffer */ ++ const uint8_t b1[] = { 1, 2, 3, 4, }; ++ assert_se(tpm2_digest_buffer(TPM2_ALG_SHA256, &d, b1, ELEMENTSOF(b1), false) == 0); ++ assert_se(digest_check(&d, "9f64a747e1b97f131fabb6b447296c9b6f0201e79fb3c5356e6c77e89b6a806a")); ++ assert_se(tpm2_digest_buffer(TPM2_ALG_SHA256, &d, b1, ELEMENTSOF(b1), true) == 0); ++ assert_se(digest_check(&d, "ff3bd307b287e9b29bb572f6ccfd19deb0106d0c4c3c5cfe8a1d03a396092ed4")); ++ ++ const void *b2 = d2.buffer; ++ assert_se(tpm2_digest_buffer(TPM2_ALG_SHA256, &d, b2, d2.size, false) == 0); ++ assert_se(digest_check(&d, "56571a1be3fbeab18d215f549095915a004b5788ca0d535be668559129a76f25")); ++ assert_se(tpm2_digest_buffer(TPM2_ALG_SHA256, &d, b2, d2.size, true) == 0); ++ assert_se(digest_check(&d, "99dedaee8f4d8d10a8be184399fde8740d5e17ff783ee5c288a4486e4ce3a1fe")); ++ ++ /* tpm2_digest_many */ ++ const struct iovec iov1[] = { ++ IOVEC_MAKE((void*) b1, ELEMENTSOF(b1)), ++ IOVEC_MAKE(d2.buffer, d2.size), ++ IOVEC_MAKE(d3.buffer, d3.size), ++ }; ++ assert_se(tpm2_digest_many(TPM2_ALG_SHA256, &d, iov1, ELEMENTSOF(iov1), false) == 0); ++ assert_se(digest_check(&d, "cd7bde4a047af976b6f1b282309976229be59f96a78aa186de32a1aee488ab09")); ++ assert_se(tpm2_digest_many(TPM2_ALG_SHA256, &d, iov1, ELEMENTSOF(iov1), true) == 0); ++ assert_se(digest_check(&d, "02ecb0628264235111e0053e271092981c8b15d59cd46617836bee3149a4ecb0")); ++} ++ + #endif /* HAVE_TPM2 */ + + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0520-tpm2-replace-hash_pin-with-tpm2_digest_-functions.patch b/SOURCES/0520-tpm2-replace-hash_pin-with-tpm2_digest_-functions.patch new file mode 100644 index 0000000..e28072a --- /dev/null +++ b/SOURCES/0520-tpm2-replace-hash_pin-with-tpm2_digest_-functions.patch @@ -0,0 +1,65 @@ +From 3bd6a10eeaaa863d54573226828f6e0c204442ee Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 14 Dec 2022 10:46:13 -0500 +Subject: [PATCH] tpm2: replace hash_pin() with tpm2_digest_*() functions + +The hash_pin() function is just a specific use case of the digest functions. + +(cherry picked from commit 94a4ff2dc1e753fc5715b5d240092e38456898f0) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 26 ++++++++------------------ + 1 file changed, 8 insertions(+), 18 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index d397c505f5..f1950189d5 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1369,21 +1369,6 @@ int tpm2_get_good_pcr_banks_strv( + return 0; + } + +-static void hash_pin(const char *pin, size_t len, TPM2B_AUTH *auth) { +- struct sha256_ctx hash; +- +- assert(auth); +- assert(pin); +- +- auth->size = SHA256_DIGEST_SIZE; +- +- CLEANUP_ERASE(hash); +- +- sha256_init_ctx(&hash); +- sha256_process_bytes(pin, len, &hash); +- sha256_finish_ctx(&hash, auth->buffer); +-} +- + /* Hash data into the digest. + * + * If 'extend' is true, the hashing operation starts with the existing digest hash (and the digest is +@@ -1507,7 +1492,9 @@ static int tpm2_make_encryption_session( + + CLEANUP_ERASE(auth); + +- hash_pin(pin, strlen(pin), &auth); ++ r = tpm2_digest_buffer(TPM2_ALG_SHA256, &auth, pin, strlen(pin), /* extend= */ false); ++ if (r < 0) ++ return r; + + rc = sym_Esys_TR_SetAuth(c->esys_context, bind_key->esys_handle, &auth); + if (rc != TSS2_RC_SUCCESS) +@@ -2182,8 +2169,11 @@ int tpm2_seal(const char *device, + .size = sizeof(hmac_sensitive.sensitive), + .sensitive.data.size = 32, + }; +- if (pin) +- hash_pin(pin, strlen(pin), &hmac_sensitive.sensitive.userAuth); ++ if (pin) { ++ r = tpm2_digest_buffer(TPM2_ALG_SHA256, &hmac_sensitive.sensitive.userAuth, pin, strlen(pin), /* extend= */ false); ++ if (r < 0) ++ return r; ++ } + + assert(sizeof(hmac_sensitive.sensitive.data.buffer) >= hmac_sensitive.sensitive.data.size); + diff --git a/SOURCES/0521-tpm2-add-tpm2_set_auth.patch b/SOURCES/0521-tpm2-add-tpm2_set_auth.patch new file mode 100644 index 0000000..454deb4 --- /dev/null +++ b/SOURCES/0521-tpm2-add-tpm2_set_auth.patch @@ -0,0 +1,120 @@ +From 50375de7b310d361a71521c6abf8e4251027dd3b Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 6 Dec 2022 13:16:43 -0500 +Subject: [PATCH] tpm2: add tpm2_set_auth() + +This provides a function to perform the SetAuth TPM function, which provides +the authValue for a key. + +(cherry picked from commit 409a65f82901ace5799da0f22f10056105e062fa) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 65 ++++++++++++++++++++++++------------------ + 1 file changed, 38 insertions(+), 27 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index f1950189d5..ac8569878c 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1446,6 +1446,31 @@ int tpm2_digest_many_digests( + return tpm2_digest_many(alg, digest, iovecs, n_data, extend); + } + ++static int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *pin) { ++ TPM2B_AUTH auth = {}; ++ TSS2_RC rc; ++ int r; ++ ++ assert(c); ++ assert(handle); ++ ++ if (!pin) ++ return 0; ++ ++ CLEANUP_ERASE(auth); ++ ++ r = tpm2_digest_buffer(TPM2_ALG_SHA256, &auth, pin, strlen(pin), /* extend= */ false); ++ if (r < 0) ++ return r; ++ ++ rc = sym_Esys_TR_SetAuth(c->esys_context, handle->esys_handle, &auth); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to load PIN in TPM: %s", sym_Tss2_RC_Decode(rc)); ++ ++ return 0; ++} ++ + static bool tpm2_is_encryption_session(Tpm2Context *c, const Tpm2Handle *session) { + TPMA_SESSION flags = 0; + TSS2_RC rc; +@@ -1464,7 +1489,6 @@ static int tpm2_make_encryption_session( + Tpm2Context *c, + const Tpm2Handle *primary, + const Tpm2Handle *bind_key, +- const char *pin, + Tpm2Handle **ret_session) { + + static const TPMT_SYM_DEF symmetric = { +@@ -1480,30 +1504,6 @@ static int tpm2_make_encryption_session( + assert(c); + assert(ret_session); + +- /* +- * if a pin is set for the seal object, use it to bind the session +- * key to that object. This prevents active bus interposers from +- * faking a TPM and seeing the unsealed value. An active interposer +- * could fake a TPM, satisfying the encrypted session, and just +- * forward everything to the *real* TPM. +- */ +- if (pin) { +- TPM2B_AUTH auth = {}; +- +- CLEANUP_ERASE(auth); +- +- r = tpm2_digest_buffer(TPM2_ALG_SHA256, &auth, pin, strlen(pin), /* extend= */ false); +- if (r < 0) +- return r; +- +- rc = sym_Esys_TR_SetAuth(c->esys_context, bind_key->esys_handle, &auth); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno( +- SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to load PIN in TPM: %s", +- sym_Tss2_RC_Decode(rc)); +- } +- + log_debug("Starting HMAC encryption session."); + + /* Start a salted, unbound HMAC session with a well-known key (e.g. primary key) as tpmKey, which +@@ -2111,7 +2111,7 @@ int tpm2_seal(const char *device, + + /* we cannot use the bind key before its created */ + _cleanup_tpm2_handle_ Tpm2Handle *encryption_session = NULL; +- r = tpm2_make_encryption_session(c, primary, &TPM2_HANDLE_NONE, NULL, &encryption_session); ++ r = tpm2_make_encryption_session(c, primary, &TPM2_HANDLE_NONE, &encryption_session); + if (r < 0) + return r; + +@@ -2408,8 +2408,19 @@ int tpm2_unseal(const char *device, + sym_Tss2_RC_Decode(rc)); + } + ++ /* ++ * if a pin is set for the seal object, use it to bind the session ++ * key to that object. This prevents active bus interposers from ++ * faking a TPM and seeing the unsealed value. An active interposer ++ * could fake a TPM, satisfying the encrypted session, and just ++ * forward everything to the *real* TPM. ++ */ ++ r = tpm2_set_auth(c, hmac_key, pin); ++ if (r < 0) ++ return r; ++ + _cleanup_tpm2_handle_ Tpm2Handle *encryption_session = NULL; +- r = tpm2_make_encryption_session(c, primary, hmac_key, pin, &encryption_session); ++ r = tpm2_make_encryption_session(c, primary, hmac_key, &encryption_session); + if (r < 0) + return r; + diff --git a/SOURCES/0522-tpm2-add-tpm2_get_name.patch b/SOURCES/0522-tpm2-add-tpm2_get_name.patch new file mode 100644 index 0000000..1d442ec --- /dev/null +++ b/SOURCES/0522-tpm2-add-tpm2_get_name.patch @@ -0,0 +1,251 @@ +From 4f9f6fce5c45c8f9aabe73a428420cfb380e9974 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 14 Dec 2022 10:46:13 -0500 +Subject: [PATCH] tpm2: add tpm2_get_name() + +This adds functions to get the "name" of a key. The key "name", as defined +by the TPM2 spec, includes its entire public area (with attribute fields), +not only its key fingerprint. + +A function is added to calculate the name of a provided key public area, +as well as a function to get the name of a key which is present in the TPM. + +(cherry picked from commit dbae4b9535ceb0a94affe34eab700900f4fbd93d) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 115 ++++++++++++++++++++++++++++++++++++++--- + src/shared/tpm2-util.h | 4 ++ + src/test/test-tpm2.c | 43 +++++++++++++++ + 3 files changed, 154 insertions(+), 8 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index ac8569878c..629e1bc5ce 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -65,6 +65,8 @@ TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t b + TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL; + TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL; ++TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + + int dlopen_tpm2(void) { + int r; +@@ -114,7 +116,9 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Unmarshal), + DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Marshal), +- DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal)); ++ DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal), ++ DLSYM_ARG(Tss2_MU_TPMT_HA_Marshal), ++ DLSYM_ARG(Tss2_MU_TPMT_PUBLIC_Marshal)); + } + + static Tpm2Context *tpm2_context_free(Tpm2Context *c) { +@@ -970,6 +974,11 @@ static void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg) { + tpm2_log_debug_buffer(digest->buffer, digest->size, msg ?: "Digest"); + } + ++static void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg) { ++ if (name) ++ tpm2_log_debug_buffer(name->name, name->size, msg ?: "Name"); ++} ++ + static int tpm2_get_policy_digest( + Tpm2Context *c, + const Tpm2Handle *session, +@@ -1815,6 +1824,100 @@ static int find_signature( + #endif + } + ++/* Calculates the "name" of a public key. ++ * ++ * As specified in TPM2 spec "Part 1: Architecture", a key's "name" is its nameAlg value followed by a hash ++ * of its TPM2 public area, all properly marshalled. This allows a key's "name" to be dependent not only on ++ * the key fingerprint, but also on the TPM2-specific fields that associated with the key (i.e. all fields in ++ * TPMT_PUBLIC). Note that this means an existing key may not change any of its TPMT_PUBLIC fields, since ++ * that would also change the key name. ++ * ++ * Since we (currently) hardcode to always using SHA256 for hashing, this returns an error if the public key ++ * nameAlg is not TPM2_ALG_SHA256. */ ++int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name) { ++ TSS2_RC rc; ++ int r; ++ ++ assert(public); ++ assert(ret_name); ++ ++ r = dlopen_tpm2(); ++ if (r < 0) ++ return log_error_errno(r, "TPM2 support not installed: %m"); ++ ++ if (public->nameAlg != TPM2_ALG_SHA256) ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "Unsupported nameAlg: 0x%x", ++ public->nameAlg); ++ ++ _cleanup_free_ uint8_t *buf = NULL; ++ size_t size = 0; ++ ++ buf = (uint8_t*) new(TPMT_PUBLIC, 1); ++ if (!buf) ++ return log_oom(); ++ ++ rc = sym_Tss2_MU_TPMT_PUBLIC_Marshal(public, buf, sizeof(TPMT_PUBLIC), &size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal public key: %s", sym_Tss2_RC_Decode(rc)); ++ ++ TPM2B_DIGEST digest = {}; ++ r = tpm2_digest_buffer(TPM2_ALG_SHA256, &digest, buf, size, /* extend= */ false); ++ if (r < 0) ++ return r; ++ ++ TPMT_HA ha = { ++ .hashAlg = TPM2_ALG_SHA256, ++ }; ++ assert(digest.size <= sizeof(ha.digest.sha256)); ++ memcpy_safe(ha.digest.sha256, digest.buffer, digest.size); ++ ++ TPM2B_NAME name; ++ size = 0; ++ rc = sym_Tss2_MU_TPMT_HA_Marshal(&ha, name.name, sizeof(name.name), &size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal key name: %s", sym_Tss2_RC_Decode(rc)); ++ name.size = size; ++ ++ tpm2_log_debug_name(&name, "Calculated name"); ++ ++ *ret_name = name; ++ ++ return 0; ++} ++ ++/* Get the "name" of a key from the TPM. ++ * ++ * The "name" of a key is explained above in tpm2_calculate_name(). ++ * ++ * The handle must reference a key already present in the TPM. It may be either a public key only, or a ++ * public/private keypair. */ ++static int tpm2_get_name( ++ Tpm2Context *c, ++ const Tpm2Handle *handle, ++ TPM2B_NAME **ret_name) { ++ ++ _cleanup_(Esys_Freep) TPM2B_NAME *name = NULL; ++ TSS2_RC rc; ++ ++ assert(c); ++ assert(handle); ++ assert(ret_name); ++ ++ rc = sym_Esys_TR_GetName(c->esys_context, handle->esys_handle, &name); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to get name of public key from TPM: %s", sym_Tss2_RC_Decode(rc)); ++ ++ tpm2_log_debug_name(name, "Object name"); ++ ++ *ret_name = TAKE_PTR(name); ++ ++ return 0; ++} ++ + static int tpm2_build_sealing_policy( + Tpm2Context *c, + const Tpm2Handle *session, +@@ -1883,13 +1986,9 @@ static int tpm2_build_sealing_policy( + + /* Acquire the "name" of what we just loaded */ + _cleanup_(Esys_Freep) TPM2B_NAME *pubkey_name = NULL; +- rc = sym_Esys_TR_GetName( +- c->esys_context, +- pubkey_handle->esys_handle, +- &pubkey_name); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to get name of public key from TPM: %s", sym_Tss2_RC_Decode(rc)); ++ r = tpm2_get_name(c, pubkey_handle, &pubkey_name); ++ if (r < 0) ++ return r; + + /* Put together the PCR policy we want to use */ + TPML_PCR_SELECTION pcr_selection; +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 2744cd13bb..a23f383e5a 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -70,6 +70,8 @@ extern TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, ui + extern TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest); + extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); + extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest); ++extern TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); ++extern TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); + + int dlopen_tpm2(void); + +@@ -85,6 +87,8 @@ static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { + return tpm2_digest_many(alg, digest, NULL, 0, false); + } + ++int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); ++ + int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); + int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); + +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index dfbea7b19a..2515f79e57 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -600,6 +600,49 @@ TEST(digest_many) { + assert_se(digest_check(&d, "02ecb0628264235111e0053e271092981c8b15d59cd46617836bee3149a4ecb0")); + } + ++static void tpm2b_public_init(TPM2B_PUBLIC *public) { ++ TPMT_PUBLIC tpmt = { ++ .type = TPM2_ALG_RSA, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, ++ .parameters.rsaDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .keyBits = 2048, ++ }, ++ }; ++ ++ const char *key = "9ec7341c52093ac40a1965a5df10432513c539adcf905e30577ab6ebc88ffe53cd08cef12ed9bec6125432f4fada3629b8b96d31b8f507aa35029188fe396da823fcb236027f7fbb01b0da3d87be7f999390449ced604bdf7e26c48657cc0671000f1147da195c3861c96642e54427cb7a11572e07567ec3fd6316978abc4bd92b27bb0a0e4958e599804eeb41d682b3b7fc1f960209f80a4fb8a1b64abfd96bf5d554e73cdd6ad1c8becb4fcf5e8f0c3e621d210e5e2f308f6520ad9a966779231b99f06c5989e5a23a9415c8808ab89ce81117632e2f8461cd4428bded40979236aeadafe8de3f51660a45e1dbc87694e6a36360201cca3ff9e7263e712727"; ++ _cleanup_free_ void *mem = NULL; ++ size_t len = 0; ++ assert_se(unhexmem(key, strlen(key), &mem, &len) == 0); ++ assert_se(len <= sizeof(tpmt.unique.rsa.buffer)); ++ memcpy_safe(tpmt.unique.rsa.buffer, mem, len); ++ tpmt.unique.rsa.size = len; ++ ++ public->publicArea = tpmt; ++} ++ ++TEST(calculate_name) { ++ TPM2B_PUBLIC public; ++ TPM2B_NAME name; ++ ++ tpm2b_public_init(&public); ++ assert_se(tpm2_calculate_name(&public.publicArea, &name) == 0); ++ assert_se(name.size == SHA256_DIGEST_SIZE + 2); ++ ++ const char *expect = "000be78f74a470dd92e979ca067cdb2293a35f075e8560b436bd2ccea5da21486a07"; ++ _cleanup_free_ char *h = hexmem(name.name, name.size); ++ assert_se(h); ++ ++ assert_se(strlen(expect) == strlen(h)); ++ assert_se(streq(expect, h)); ++} ++ + #endif /* HAVE_TPM2 */ + + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0523-tpm2-rename-pcr_values_size-vars-to-n_pcr_values.patch b/SOURCES/0523-tpm2-rename-pcr_values_size-vars-to-n_pcr_values.patch new file mode 100644 index 0000000..5ee984e --- /dev/null +++ b/SOURCES/0523-tpm2-rename-pcr_values_size-vars-to-n_pcr_values.patch @@ -0,0 +1,87 @@ +From 9c49d0b64ff66bf4d8199abf7989c0a50bb79757 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 11 May 2023 15:33:31 -0400 +Subject: [PATCH] tpm2: rename pcr_values_size vars to n_pcr_values + +Using the n_ prefix is more appropriate/conventional than the _size suffix. + +No functional change, this is cosmetic only. + +(cherry picked from commit c648a4b85e9ef71098afba3c7ac36a31f9372a4d) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 20 ++++++++++---------- + 1 file changed, 10 insertions(+), 10 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 629e1bc5ce..a6fab45898 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1019,11 +1019,11 @@ static int tpm2_pcr_read( + const TPML_PCR_SELECTION *pcr_selection, + TPML_PCR_SELECTION *ret_pcr_selection, + TPM2B_DIGEST **ret_pcr_values, +- size_t *ret_pcr_values_size) { ++ size_t *ret_n_pcr_values) { + + _cleanup_free_ TPM2B_DIGEST *pcr_values = NULL; + TPML_PCR_SELECTION remaining, total_read = {}; +- size_t pcr_values_size = 0; ++ size_t n_pcr_values = 0; + TSS2_RC rc; + + assert(c); +@@ -1058,12 +1058,12 @@ static int tpm2_pcr_read( + tpm2_tpml_pcr_selection_sub(&remaining, current_read); + tpm2_tpml_pcr_selection_add(&total_read, current_read); + +- if (!GREEDY_REALLOC(pcr_values, pcr_values_size + current_values->count)) ++ if (!GREEDY_REALLOC(pcr_values, n_pcr_values + current_values->count)) + return log_oom(); + +- memcpy_safe(&pcr_values[pcr_values_size], current_values->digests, ++ memcpy_safe(&pcr_values[n_pcr_values], current_values->digests, + current_values->count * sizeof(TPM2B_DIGEST)); +- pcr_values_size += current_values->count; ++ n_pcr_values += current_values->count; + + if (DEBUG_LOGGING) { + unsigned i = 0; +@@ -1086,8 +1086,8 @@ static int tpm2_pcr_read( + *ret_pcr_selection = total_read; + if (ret_pcr_values) + *ret_pcr_values = TAKE_PTR(pcr_values); +- if (ret_pcr_values_size) +- *ret_pcr_values_size = pcr_values_size; ++ if (ret_n_pcr_values) ++ *ret_n_pcr_values = n_pcr_values; + + return 0; + } +@@ -1099,7 +1099,7 @@ static int tpm2_pcr_mask_good( + + _cleanup_free_ TPM2B_DIGEST *pcr_values = NULL; + TPML_PCR_SELECTION selection; +- size_t pcr_values_size = 0; ++ size_t n_pcr_values = 0; + int r; + + assert(c); +@@ -1110,14 +1110,14 @@ static int tpm2_pcr_mask_good( + + tpm2_tpml_pcr_selection_from_mask(mask, bank, &selection); + +- r = tpm2_pcr_read(c, &selection, &selection, &pcr_values, &pcr_values_size); ++ r = tpm2_pcr_read(c, &selection, &selection, &pcr_values, &n_pcr_values); + if (r < 0) + return r; + + /* If at least one of the selected PCR values is something other than all 0x00 or all 0xFF we are happy. */ + unsigned i = 0; + FOREACH_PCR_IN_TPML_PCR_SELECTION(pcr, s, &selection) { +- assert(i < pcr_values_size); ++ assert(i < n_pcr_values); + + if (!memeqbyte(0x00, pcr_values[i].buffer, pcr_values[i].size) && + !memeqbyte(0xFF, pcr_values[i].buffer, pcr_values[i].size)) diff --git a/SOURCES/0524-tpm2-add-tpm2_policy_pcr.patch b/SOURCES/0524-tpm2-add-tpm2_policy_pcr.patch new file mode 100644 index 0000000..d937f5a --- /dev/null +++ b/SOURCES/0524-tpm2-add-tpm2_policy_pcr.patch @@ -0,0 +1,272 @@ +From 9ffb6893d40f23d0a1fd59176270892c21b7bda3 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 8 Dec 2022 17:56:11 -0500 +Subject: [PATCH] tpm2: add tpm2_policy_pcr() + +This adds functions to get the digest for a PolicyPCR operation. For building +a policy hash, this provides a function to calculate the hash; and for building +a policy hash to satisfy the authPolicy for an existing object, this provides a +function to perform PolicyPCR with an existing session. + +(cherry picked from commit dcbc4674e3daea2d34d02de5a76d4a19bca7545f) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 119 +++++++++++++++++++++++++++++++---------- + src/shared/tpm2-util.h | 3 ++ + src/test/test-tpm2.c | 48 +++++++++++++++++ + 3 files changed, 143 insertions(+), 27 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index a6fab45898..1aa49a7232 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -61,10 +61,12 @@ TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle + + const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL; + ++TSS2_RC (*sym_Tss2_MU_TPM2_CC_Marshal)(TPM2_CC src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL; + TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL; ++TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + +@@ -113,10 +115,12 @@ int dlopen_tpm2(void) { + + return dlopen_many_sym_or_warn( + &libtss2_mu_dl, "libtss2-mu.so.0", LOG_DEBUG, ++ DLSYM_ARG(Tss2_MU_TPM2_CC_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Unmarshal), + DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal), ++ DLSYM_ARG(Tss2_MU_TPML_PCR_SELECTION_Marshal), + DLSYM_ARG(Tss2_MU_TPMT_HA_Marshal), + DLSYM_ARG(Tss2_MU_TPMT_PUBLIC_Marshal)); + } +@@ -1918,6 +1922,90 @@ static int tpm2_get_name( + return 0; + } + ++/* Extend 'digest' with the PolicyPCR calculated hash. */ ++int tpm2_calculate_policy_pcr( ++ const TPML_PCR_SELECTION *pcr_selection, ++ const TPM2B_DIGEST pcr_values[], ++ size_t n_pcr_values, ++ TPM2B_DIGEST *digest) { ++ ++ TPM2_CC command = TPM2_CC_PolicyPCR; ++ TSS2_RC rc; ++ int r; ++ ++ assert(pcr_selection); ++ assert(pcr_values || n_pcr_values == 0); ++ assert(digest); ++ assert(digest->size == SHA256_DIGEST_SIZE); ++ ++ r = dlopen_tpm2(); ++ if (r < 0) ++ return log_error_errno(r, "TPM2 support not installed: %m"); ++ ++ TPM2B_DIGEST hash = {}; ++ r = tpm2_digest_many_digests(TPM2_ALG_SHA256, &hash, pcr_values, n_pcr_values, /* extend= */ false); ++ if (r < 0) ++ return r; ++ ++ _cleanup_free_ uint8_t *buf = NULL; ++ size_t size = 0, maxsize = sizeof(command) + sizeof(*pcr_selection); ++ ++ buf = malloc(maxsize); ++ if (!buf) ++ return log_oom(); ++ ++ rc = sym_Tss2_MU_TPM2_CC_Marshal(command, buf, maxsize, &size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal PolicyPCR command: %s", sym_Tss2_RC_Decode(rc)); ++ ++ rc = sym_Tss2_MU_TPML_PCR_SELECTION_Marshal(pcr_selection, buf, maxsize, &size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal PCR selection: %s", sym_Tss2_RC_Decode(rc)); ++ ++ struct iovec data[] = { ++ IOVEC_MAKE(buf, size), ++ IOVEC_MAKE(hash.buffer, hash.size), ++ }; ++ r = tpm2_digest_many(TPM2_ALG_SHA256, digest, data, ELEMENTSOF(data), /* extend= */ true); ++ if (r < 0) ++ return r; ++ ++ tpm2_log_debug_digest(digest, "PolicyPCR calculated digest"); ++ ++ return 0; ++} ++ ++static int tpm2_policy_pcr( ++ Tpm2Context *c, ++ const Tpm2Handle *session, ++ const TPML_PCR_SELECTION *pcr_selection, ++ TPM2B_DIGEST **ret_policy_digest) { ++ ++ TSS2_RC rc; ++ ++ assert(c); ++ assert(session); ++ assert(pcr_selection); ++ ++ log_debug("Adding PCR hash policy."); ++ ++ rc = sym_Esys_PolicyPCR( ++ c->esys_context, ++ session->esys_handle, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ NULL, ++ pcr_selection); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc)); ++ ++ return tpm2_get_policy_digest(c, session, ret_policy_digest); ++} ++ + static int tpm2_build_sealing_policy( + Tpm2Context *c, + const Tpm2Handle *session, +@@ -1993,21 +2081,8 @@ static int tpm2_build_sealing_policy( + /* Put together the PCR policy we want to use */ + TPML_PCR_SELECTION pcr_selection; + tpm2_tpml_pcr_selection_from_mask(pubkey_pcr_mask, (TPMI_ALG_HASH)pcr_bank, &pcr_selection); +- rc = sym_Esys_PolicyPCR( +- c->esys_context, +- session->esys_handle, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- NULL, +- &pcr_selection); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc)); +- +- /* Get the policy hash of the PCR policy */ + _cleanup_(Esys_Freep) TPM2B_DIGEST *approved_policy = NULL; +- r = tpm2_get_policy_digest(c, session, &approved_policy); ++ r = tpm2_policy_pcr(c, session, &pcr_selection, &approved_policy); + if (r < 0) + return r; + +@@ -2087,21 +2162,11 @@ static int tpm2_build_sealing_policy( + } + + if (hash_pcr_mask != 0) { +- log_debug("Configuring hash-based PCR policy."); +- + TPML_PCR_SELECTION pcr_selection; + tpm2_tpml_pcr_selection_from_mask(hash_pcr_mask, (TPMI_ALG_HASH)pcr_bank, &pcr_selection); +- rc = sym_Esys_PolicyPCR( +- c->esys_context, +- session->esys_handle, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- NULL, +- &pcr_selection); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc)); ++ r = tpm2_policy_pcr(c, session, &pcr_selection, NULL); ++ if (r < 0) ++ return r; + } + + if (use_pin) { +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index a23f383e5a..80c00af141 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -66,10 +66,12 @@ extern TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR ke + + extern const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc); + ++extern TSS2_RC (*sym_Tss2_MU_TPM2_CC_Marshal)(TPM2_CC src, uint8_t buffer[], size_t buffer_size, size_t *offset); + extern TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); + extern TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest); + extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); + extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest); ++extern TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); + extern TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); + extern TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); + +@@ -88,6 +90,7 @@ static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { + } + + int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); ++int tpm2_calculate_policy_pcr(const TPML_PCR_SELECTION *pcr_selection, const TPM2B_DIGEST pcr_values[], size_t pcr_values_count, TPM2B_DIGEST *digest); + + int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); + int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 2515f79e57..c2e074b5f9 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -643,6 +643,54 @@ TEST(calculate_name) { + assert_se(streq(expect, h)); + } + ++TEST(calculate_policy_pcr) { ++ TPML_PCR_SELECTION pcr_selection; ++ TPM2B_DIGEST pcr_values[16]; ++ TPM2B_DIGEST d; ++ uint32_t pcr_mask; ++ ++ digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); ++ pcr_mask = (1<<4) | (1<<7) | (1<<8); ++ tpm2_tpml_pcr_selection_from_mask(pcr_mask, TPM2_ALG_SHA256, &pcr_selection); ++ digest_init_sha256(&pcr_values[0], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); ++ digest_init_sha256(&pcr_values[1], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); ++ digest_init_sha256(&pcr_values[2], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); ++ assert_se(tpm2_calculate_policy_pcr(&pcr_selection, pcr_values, 3, &d) == 0); ++ assert_se(digest_check(&d, "76532a0e16f7e6bf6b02918c11f75d99d729fab0cc81d0df2c4284a2c4fe6e05")); ++ ++ pcr_mask = (1<<4) | (1<<7) | (1<<8); ++ tpm2_tpml_pcr_selection_from_mask(pcr_mask, TPM2_ALG_SHA256, &pcr_selection); ++ digest_init_sha256(&pcr_values[0], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); ++ digest_init_sha256(&pcr_values[1], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); ++ digest_init_sha256(&pcr_values[2], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); ++ assert_se(tpm2_calculate_policy_pcr(&pcr_selection, pcr_values, 3, &d) == 0); ++ assert_se(digest_check(&d, "97e64bcabb64c1fa4b726528644926c8029f5b4458b0575c98c04fe225629a0b")); ++ ++ digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); ++ pcr_mask = 0xffff; ++ tpm2_tpml_pcr_selection_from_mask(pcr_mask, TPM2_ALG_SHA256, &pcr_selection); ++ digest_init_sha256(&pcr_values[ 0], "2124793cbbe60c3a8637d3b84a5d054e87c351e1469a285acc04755e8b204dec"); ++ digest_init_sha256(&pcr_values[ 1], "bf7592f18adcfdc549fc0b94939f5069a24697f9cff4a0dca29014767b97559d"); ++ digest_init_sha256(&pcr_values[ 2], "4b00cff9dee3a364979b2dc241b34568a8ad49fcf2713df259e47dff8875feed"); ++ digest_init_sha256(&pcr_values[ 3], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); ++ digest_init_sha256(&pcr_values[ 4], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); ++ digest_init_sha256(&pcr_values[ 5], "c97c40369691c8e4aa78fb3a52655cd193b780a838b8e23f5f476576919db5e5"); ++ digest_init_sha256(&pcr_values[ 6], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); ++ digest_init_sha256(&pcr_values[ 7], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); ++ digest_init_sha256(&pcr_values[ 8], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); ++ digest_init_sha256(&pcr_values[ 9], "9c2bac22ef5ec84fcdb71c3ebf776cba1247e5da980e5ee08e45666a2edf0b8b"); ++ digest_init_sha256(&pcr_values[10], "9885873f4d7348199ad286f8f2476d4f866940950f6f9fb9f945ed352dbdcbd2"); ++ digest_init_sha256(&pcr_values[11], "42400ab950d21aa79d12cc4fdef67d1087a39ad64900619831c0974dbae54e44"); ++ digest_init_sha256(&pcr_values[12], "767d064382e56ca1ad3bdcc6bc596112e6c2008b593d3570d24c2bfa64c4628c"); ++ digest_init_sha256(&pcr_values[13], "30c16133175959408c9745d8dafadef5daf4b39cb2be04df0d60089bd46d3cc4"); ++ digest_init_sha256(&pcr_values[14], "e3991b7ddd47be7e92726a832d6874c5349b52b789fa0db8b558c69fea29574e"); ++ digest_init_sha256(&pcr_values[15], "852dae3ecb992bdeb13d6002fefeeffdd90feca8b378d56681ef2c885d0e5137"); ++ assert_se(tpm2_calculate_policy_pcr(&pcr_selection, pcr_values, 16, &d) == 0); ++ assert_se(digest_check(&d, "22be4f1674f792d6345cea9427701068f0e8d9f42755dcc0e927e545a68f9c13")); ++ assert_se(tpm2_calculate_policy_pcr(&pcr_selection, pcr_values, 16, &d) == 0); ++ assert_se(digest_check(&d, "7481fd1b116078eb3ac2456e4ad542c9b46b9b8eb891335771ca8e7c8f8e4415")); ++} ++ + #endif /* HAVE_TPM2 */ + + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0525-tpm2-add-tpm2_policy_auth_value.patch b/SOURCES/0525-tpm2-add-tpm2_policy_auth_value.patch new file mode 100644 index 0000000..c8164b2 --- /dev/null +++ b/SOURCES/0525-tpm2-add-tpm2_policy_auth_value.patch @@ -0,0 +1,145 @@ +From 4bdaf980b4c5eb519b0c762179015c7b96d51037 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 9 Dec 2022 14:59:05 -0500 +Subject: [PATCH] tpm2: add tpm2_policy_auth_value() + +This adds functions to get the digest for a PolicyAuthValue operation. For +building a policy hash, this provides a function to calculate the hash; and for +building a policy hash to satisfy the authPolicy for an existing object, this +provides a function to perform PolicyAuthValue with an existing session. + +(cherry picked from commit 8a716354bb97c9a220cf95aef0e78f66abd33584) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 75 +++++++++++++++++++++++++++++++++++------- + src/shared/tpm2-util.h | 1 + + src/test/test-tpm2.c | 10 ++++++ + 3 files changed, 74 insertions(+), 12 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 1aa49a7232..35dfa3f371 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1922,6 +1922,66 @@ static int tpm2_get_name( + return 0; + } + ++/* Extend 'digest' with the PolicyAuthValue calculated hash. */ ++int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest) { ++ TPM2_CC command = TPM2_CC_PolicyAuthValue; ++ TSS2_RC rc; ++ int r; ++ ++ assert(digest); ++ assert(digest->size == SHA256_DIGEST_SIZE); ++ ++ r = dlopen_tpm2(); ++ if (r < 0) ++ return log_error_errno(r, "TPM2 support not installed: %m"); ++ ++ uint8_t buf[sizeof(command)]; ++ size_t offset = 0; ++ ++ rc = sym_Tss2_MU_TPM2_CC_Marshal(command, buf, sizeof(buf), &offset); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal PolicyAuthValue command: %s", sym_Tss2_RC_Decode(rc)); ++ ++ if (offset != sizeof(command)) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Offset 0x%zx wrong after marshalling PolicyAuthValue command", offset); ++ ++ r = tpm2_digest_buffer(TPM2_ALG_SHA256, digest, buf, offset, /* extend= */ true); ++ if (r < 0) ++ return r; ++ ++ tpm2_log_debug_digest(digest, "PolicyAuthValue calculated digest"); ++ ++ return 0; ++} ++ ++static int tpm2_policy_auth_value( ++ Tpm2Context *c, ++ const Tpm2Handle *session, ++ TPM2B_DIGEST **ret_policy_digest) { ++ ++ TSS2_RC rc; ++ ++ assert(c); ++ assert(session); ++ ++ log_debug("Adding authValue policy."); ++ ++ rc = sym_Esys_PolicyAuthValue( ++ c->esys_context, ++ session->esys_handle, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to add authValue policy to TPM: %s", ++ sym_Tss2_RC_Decode(rc)); ++ ++ return tpm2_get_policy_digest(c, session, ret_policy_digest); ++} ++ + /* Extend 'digest' with the PolicyPCR calculated hash. */ + int tpm2_calculate_policy_pcr( + const TPML_PCR_SELECTION *pcr_selection, +@@ -2170,18 +2230,9 @@ static int tpm2_build_sealing_policy( + } + + if (use_pin) { +- log_debug("Configuring PIN policy."); +- +- rc = sym_Esys_PolicyAuthValue( +- c->esys_context, +- session->esys_handle, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to add authValue policy to TPM: %s", +- sym_Tss2_RC_Decode(rc)); ++ r = tpm2_policy_auth_value(c, session, NULL); ++ if (r < 0) ++ return r; + } + + r = tpm2_get_policy_digest(c, session, ret_policy_digest); +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 80c00af141..706d228073 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -90,6 +90,7 @@ static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { + } + + int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); ++int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest); + int tpm2_calculate_policy_pcr(const TPML_PCR_SELECTION *pcr_selection, const TPM2B_DIGEST pcr_values[], size_t pcr_values_count, TPM2B_DIGEST *digest); + + int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index c2e074b5f9..3fbb31bae0 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -643,6 +643,16 @@ TEST(calculate_name) { + assert_se(streq(expect, h)); + } + ++TEST(calculate_policy_auth_value) { ++ TPM2B_DIGEST d; ++ ++ digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); ++ assert_se(tpm2_calculate_policy_auth_value(&d) == 0); ++ assert_se(digest_check(&d, "8fcd2169ab92694e0c633f1ab772842b8241bbc20288981fc7ac1eddc1fddb0e")); ++ assert_se(tpm2_calculate_policy_auth_value(&d) == 0); ++ assert_se(digest_check(&d, "759ebd5ed65100e0b4aa2d04b4b789c2672d92ecc9cdda4b5fa16a303132e008")); ++} ++ + TEST(calculate_policy_pcr) { + TPML_PCR_SELECTION pcr_selection; + TPM2B_DIGEST pcr_values[16]; diff --git a/SOURCES/0526-tpm2-add-tpm2_policy_authorize.patch b/SOURCES/0526-tpm2-add-tpm2_policy_authorize.patch new file mode 100644 index 0000000..bcb1bb7 --- /dev/null +++ b/SOURCES/0526-tpm2-add-tpm2_policy_authorize.patch @@ -0,0 +1,262 @@ +From c25de62babe4e0734d769de0250544fe8de83d4a Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 9 Dec 2022 14:49:52 -0500 +Subject: [PATCH] tpm2: add tpm2_policy_authorize() + +This adds functions to get the digest for a PolicyAuthorize operation. For +building a policy hash, this provides a function to calculate the hash; and for +building a policy hash to satisfy the authPolicy for an existing object, this +provides a function to perform PolicyAuthorize with an existing session. + +(cherry picked from commit 5c7852f78c0c2b44be60651430876165a37eea95) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 197 +++++++++++++++++++++++++++++++++++++++++ + src/shared/tpm2-util.h | 1 + + src/test/test-tpm2.c | 12 +++ + 3 files changed, 210 insertions(+) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 35dfa3f371..4be07d8944 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -2066,6 +2066,203 @@ static int tpm2_policy_pcr( + return tpm2_get_policy_digest(c, session, ret_policy_digest); + } + ++/* Extend 'digest' with the PolicyAuthorize calculated hash. */ ++int tpm2_calculate_policy_authorize( ++ const TPM2B_PUBLIC *public, ++ const TPM2B_DIGEST *policy_ref, ++ TPM2B_DIGEST *digest) { ++ ++ TPM2_CC command = TPM2_CC_PolicyAuthorize; ++ TSS2_RC rc; ++ int r; ++ ++ assert(public); ++ assert(digest); ++ assert(digest->size == SHA256_DIGEST_SIZE); ++ ++ r = dlopen_tpm2(); ++ if (r < 0) ++ return log_error_errno(r, "TPM2 support not installed: %m"); ++ ++ uint8_t buf[sizeof(command)]; ++ size_t offset = 0; ++ ++ rc = sym_Tss2_MU_TPM2_CC_Marshal(command, buf, sizeof(buf), &offset); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal PolicyAuthorize command: %s", sym_Tss2_RC_Decode(rc)); ++ ++ if (offset != sizeof(command)) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Offset 0x%zx wrong after marshalling PolicyAuthorize command", offset); ++ ++ TPM2B_NAME name = {}; ++ r = tpm2_calculate_name(&public->publicArea, &name); ++ if (r < 0) ++ return r; ++ ++ /* PolicyAuthorize does not use the previous hash value; we must zero and then extend it. */ ++ zero(digest->buffer); ++ ++ struct iovec data[] = { ++ IOVEC_MAKE(buf, offset), ++ IOVEC_MAKE(name.name, name.size), ++ }; ++ r = tpm2_digest_many(TPM2_ALG_SHA256, digest, data, ELEMENTSOF(data), /* extend= */ true); ++ if (r < 0) ++ return r; ++ ++ /* PolicyAuthorize requires hashing twice; this is either an extension or rehashing. */ ++ if (policy_ref) ++ r = tpm2_digest_many_digests(TPM2_ALG_SHA256, digest, policy_ref, 1, /* extend= */ true); ++ else ++ r = tpm2_digest_rehash(TPM2_ALG_SHA256, digest); ++ if (r < 0) ++ return r; ++ ++ tpm2_log_debug_digest(digest, "PolicyAuthorize calculated digest"); ++ ++ return 0; ++} ++ ++static int tpm2_policy_authorize( ++ Tpm2Context *c, ++ const Tpm2Handle *session, ++ TPML_PCR_SELECTION *pcr_selection, ++ const TPM2B_PUBLIC *public, ++ const void *fp, ++ size_t fp_size, ++ JsonVariant *signature_json, ++ TPM2B_DIGEST **ret_policy_digest) { ++ ++ TSS2_RC rc; ++ int r; ++ ++ assert(c); ++ assert(session); ++ assert(pcr_selection); ++ assert(public); ++ assert(fp && fp_size > 0); ++ ++ log_debug("Adding PCR signature policy."); ++ ++ _cleanup_tpm2_handle_ Tpm2Handle *pubkey_handle = NULL; ++ r = tpm2_handle_new(c, &pubkey_handle); ++ if (r < 0) ++ return r; ++ ++ /* Load the key into the TPM */ ++ rc = sym_Esys_LoadExternal( ++ c->esys_context, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ NULL, ++ public, ++#if HAVE_TSS2_ESYS3 ++ /* tpm2-tss >= 3.0.0 requires a ESYS_TR_RH_* constant specifying the requested ++ * hierarchy, older versions need TPM2_RH_* instead. */ ++ ESYS_TR_RH_OWNER, ++#else ++ TPM2_RH_OWNER, ++#endif ++ &pubkey_handle->esys_handle); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to load public key into TPM: %s", sym_Tss2_RC_Decode(rc)); ++ ++ /* Acquire the "name" of what we just loaded */ ++ _cleanup_(Esys_Freep) TPM2B_NAME *pubkey_name = NULL; ++ r = tpm2_get_name(c, pubkey_handle, &pubkey_name); ++ if (r < 0) ++ return r; ++ ++ /* If we have a signature, proceed with verifying the PCR digest */ ++ const TPMT_TK_VERIFIED *check_ticket; ++ _cleanup_(Esys_Freep) TPMT_TK_VERIFIED *check_ticket_buffer = NULL; ++ _cleanup_(Esys_Freep) TPM2B_DIGEST *approved_policy = NULL; ++ if (signature_json) { ++ r = tpm2_policy_pcr( ++ c, ++ session, ++ pcr_selection, ++ &approved_policy); ++ if (r < 0) ++ return r; ++ ++ _cleanup_free_ void *signature_raw = NULL; ++ size_t signature_size; ++ ++ r = find_signature( ++ signature_json, ++ pcr_selection, ++ fp, fp_size, ++ approved_policy->buffer, ++ approved_policy->size, ++ &signature_raw, ++ &signature_size); ++ if (r < 0) ++ return r; ++ ++ /* TPM2_VerifySignature() will only verify the RSA part of the RSA+SHA256 signature, ++ * hence we need to do the SHA256 part ourselves, first */ ++ TPM2B_DIGEST signature_hash = *approved_policy; ++ r = tpm2_digest_rehash(TPM2_ALG_SHA256, &signature_hash); ++ if (r < 0) ++ return r; ++ ++ TPMT_SIGNATURE policy_signature = { ++ .sigAlg = TPM2_ALG_RSASSA, ++ .signature.rsassa = { ++ .hash = TPM2_ALG_SHA256, ++ .sig.size = signature_size, ++ }, ++ }; ++ if (signature_size > sizeof(policy_signature.signature.rsassa.sig.buffer)) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Signature larger than buffer."); ++ memcpy(policy_signature.signature.rsassa.sig.buffer, signature_raw, signature_size); ++ ++ rc = sym_Esys_VerifySignature( ++ c->esys_context, ++ pubkey_handle->esys_handle, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ &signature_hash, ++ &policy_signature, ++ &check_ticket_buffer); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to validate signature in TPM: %s", sym_Tss2_RC_Decode(rc)); ++ ++ check_ticket = check_ticket_buffer; ++ } else { ++ /* When enrolling, we pass a NULL ticket */ ++ static const TPMT_TK_VERIFIED check_ticket_null = { ++ .tag = TPM2_ST_VERIFIED, ++ .hierarchy = TPM2_RH_OWNER, ++ }; ++ ++ check_ticket = &check_ticket_null; ++ } ++ ++ rc = sym_Esys_PolicyAuthorize( ++ c->esys_context, ++ session->esys_handle, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ approved_policy, ++ /* policyRef= */ &(const TPM2B_NONCE) {}, ++ pubkey_name, ++ check_ticket); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to push Authorize policy into TPM: %s", sym_Tss2_RC_Decode(rc)); ++ ++ return tpm2_get_policy_digest(c, session, ret_policy_digest); ++} ++ + static int tpm2_build_sealing_policy( + Tpm2Context *c, + const Tpm2Handle *session, +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 706d228073..526e2fdfb2 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -91,6 +91,7 @@ static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { + + int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); + int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest); ++int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGEST *policy_ref, TPM2B_DIGEST *digest); + int tpm2_calculate_policy_pcr(const TPML_PCR_SELECTION *pcr_selection, const TPM2B_DIGEST pcr_values[], size_t pcr_values_count, TPM2B_DIGEST *digest); + + int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 3fbb31bae0..0b123c25a7 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -653,6 +653,18 @@ TEST(calculate_policy_auth_value) { + assert_se(digest_check(&d, "759ebd5ed65100e0b4aa2d04b4b789c2672d92ecc9cdda4b5fa16a303132e008")); + } + ++TEST(calculate_policy_authorize) { ++ TPM2B_PUBLIC public; ++ TPM2B_DIGEST d; ++ ++ tpm2b_public_init(&public); ++ digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); ++ assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); ++ assert_se(digest_check(&d, "95213a3784eaab04f427bc7e8851c2f1df0903be8e42428ec25dcefd907baff1")); ++ assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); ++ assert_se(digest_check(&d, "95213a3784eaab04f427bc7e8851c2f1df0903be8e42428ec25dcefd907baff1")); ++} ++ + TEST(calculate_policy_pcr) { + TPML_PCR_SELECTION pcr_selection; + TPM2B_DIGEST pcr_values[16]; diff --git a/SOURCES/0527-tpm2-use-tpm2_policy_authorize.patch b/SOURCES/0527-tpm2-use-tpm2_policy_authorize.patch new file mode 100644 index 0000000..7594ce6 --- /dev/null +++ b/SOURCES/0527-tpm2-use-tpm2_policy_authorize.patch @@ -0,0 +1,230 @@ +From dc66857abd6ffdf6414d54b8e95f1da594b7317e Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 9 Feb 2023 10:04:58 -0500 +Subject: [PATCH] tpm2: use tpm2_policy_authorize() + +This updates the function to build the sealing policy to use the dedicated +function to perform PolicyAuthorize. + +This is separate from the previous commit to make each commit easier to read. + +(cherry picked from commit 524cef3ff5e52ab8683a5c95c519d598dd3d0726) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 154 ++++++++--------------------------------- + 1 file changed, 29 insertions(+), 125 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 4be07d8944..2747cf0b53 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -2268,20 +2268,19 @@ static int tpm2_build_sealing_policy( + const Tpm2Handle *session, + uint32_t hash_pcr_mask, + uint16_t pcr_bank, +- const void *pubkey, +- size_t pubkey_size, ++ const TPM2B_PUBLIC *public, ++ const void *fp, ++ size_t fp_size, + uint32_t pubkey_pcr_mask, + JsonVariant *signature_json, + bool use_pin, + TPM2B_DIGEST **ret_policy_digest) { + +- TSS2_RC rc; + int r; + + assert(c); + assert(session); +- assert(pubkey || pubkey_size == 0); +- assert(pubkey_pcr_mask == 0 || pubkey_size > 0); ++ assert(pubkey_pcr_mask == 0 || public); + + log_debug("Building sealing policy."); + +@@ -2294,128 +2293,11 @@ static int tpm2_build_sealing_policy( + } + + if (pubkey_pcr_mask != 0) { +- _cleanup_free_ void *fp = NULL; +- size_t fp_size = 0; +- TPM2B_PUBLIC pubkey_tpm2; +- +- log_debug("Configuring public key based PCR policy."); +- +- /* Convert the PEM key to TPM2 format */ +- r = openssl_pubkey_to_tpm2_pubkey(pubkey, pubkey_size, &pubkey_tpm2, &fp, &fp_size); +- if (r < 0) +- return r; +- +- _cleanup_tpm2_handle_ Tpm2Handle *pubkey_handle = NULL; +- r = tpm2_handle_new(c, &pubkey_handle); +- if (r < 0) +- return r; +- +- rc = sym_Esys_LoadExternal( +- c->esys_context, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- NULL, +- &pubkey_tpm2, +-#if HAVE_TSS2_ESYS3 +- /* tpm2-tss >= 3.0.0 requires a ESYS_TR_RH_* constant specifying the requested +- * hierarchy, older versions need TPM2_RH_* instead. */ +- ESYS_TR_RH_OWNER, +-#else +- TPM2_RH_OWNER, +-#endif +- &pubkey_handle->esys_handle); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to load public key into TPM: %s", sym_Tss2_RC_Decode(rc)); +- +- /* Acquire the "name" of what we just loaded */ +- _cleanup_(Esys_Freep) TPM2B_NAME *pubkey_name = NULL; +- r = tpm2_get_name(c, pubkey_handle, &pubkey_name); +- if (r < 0) +- return r; +- +- /* Put together the PCR policy we want to use */ + TPML_PCR_SELECTION pcr_selection; + tpm2_tpml_pcr_selection_from_mask(pubkey_pcr_mask, (TPMI_ALG_HASH)pcr_bank, &pcr_selection); +- _cleanup_(Esys_Freep) TPM2B_DIGEST *approved_policy = NULL; +- r = tpm2_policy_pcr(c, session, &pcr_selection, &approved_policy); ++ r = tpm2_policy_authorize(c, session, &pcr_selection, public, fp, fp_size, signature_json, NULL); + if (r < 0) + return r; +- +- /* When we are unlocking and have a signature, let's pass it to the TPM */ +- _cleanup_(Esys_Freep) TPMT_TK_VERIFIED *check_ticket_buffer = NULL; +- const TPMT_TK_VERIFIED *check_ticket; +- if (signature_json) { +- _cleanup_free_ void *signature_raw = NULL; +- size_t signature_size; +- +- r = find_signature( +- signature_json, +- &pcr_selection, +- fp, fp_size, +- approved_policy->buffer, +- approved_policy->size, +- &signature_raw, +- &signature_size); +- if (r < 0) +- return r; +- +- /* TPM2_VerifySignature() will only verify the RSA part of the RSA+SHA256 signature, +- * hence we need to do the SHA256 part ourselves, first */ +- TPM2B_DIGEST signature_hash = *approved_policy; +- r = tpm2_digest_rehash(TPM2_ALG_SHA256, &signature_hash); +- if (r < 0) +- return r; +- +- TPMT_SIGNATURE policy_signature = { +- .sigAlg = TPM2_ALG_RSASSA, +- .signature.rsassa = { +- .hash = TPM2_ALG_SHA256, +- .sig.size = signature_size, +- }, +- }; +- if (signature_size > sizeof(policy_signature.signature.rsassa.sig.buffer)) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Signature larger than buffer."); +- memcpy(policy_signature.signature.rsassa.sig.buffer, signature_raw, signature_size); +- +- rc = sym_Esys_VerifySignature( +- c->esys_context, +- pubkey_handle->esys_handle, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &signature_hash, +- &policy_signature, +- &check_ticket_buffer); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to validate signature in TPM: %s", sym_Tss2_RC_Decode(rc)); +- +- check_ticket = check_ticket_buffer; +- } else { +- /* When enrolling, we pass a NULL ticket */ +- static const TPMT_TK_VERIFIED check_ticket_null = { +- .tag = TPM2_ST_VERIFIED, +- .hierarchy = TPM2_RH_OWNER, +- }; +- +- check_ticket = &check_ticket_null; +- } +- +- rc = sym_Esys_PolicyAuthorize( +- c->esys_context, +- session->esys_handle, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- approved_policy, +- /* policyRef= */ &(const TPM2B_NONCE) {}, +- pubkey_name, +- check_ticket); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to push Authorize policy into TPM: %s", sym_Tss2_RC_Decode(rc)); + } + + if (hash_pcr_mask != 0) { +@@ -2516,6 +2398,16 @@ int tpm2_seal(const char *device, + return r; + } + ++ TPM2B_PUBLIC pubkey_tpm2, *authorize_key = NULL; ++ _cleanup_free_ void *fp = NULL; ++ size_t fp_size = 0; ++ if (pubkey) { ++ r = openssl_pubkey_to_tpm2_pubkey(pubkey, pubkey_size, &pubkey_tpm2, &fp, &fp_size); ++ if (r < 0) ++ return r; ++ authorize_key = &pubkey_tpm2; ++ } ++ + _cleanup_tpm2_handle_ Tpm2Handle *primary = NULL; + r = tpm2_make_primary(c, /* alg = */0, !!ret_srk_buf, &primary_alg, &primary); + if (r < 0) +@@ -2554,7 +2446,8 @@ int tpm2_seal(const char *device, + policy_session, + hash_pcr_mask, + pcr_bank, +- pubkey, pubkey_size, ++ authorize_key, ++ fp, fp_size, + pubkey_pcr_mask, + /* signature_json= */ NULL, + !!pin, +@@ -2820,6 +2713,16 @@ int tpm2_unseal(const char *device, + sym_Tss2_RC_Decode(rc)); + } + ++ TPM2B_PUBLIC pubkey_tpm2, *authorize_key = NULL; ++ _cleanup_free_ void *fp = NULL; ++ size_t fp_size = 0; ++ if (pubkey) { ++ r = openssl_pubkey_to_tpm2_pubkey(pubkey, pubkey_size, &pubkey_tpm2, &fp, &fp_size); ++ if (r < 0) ++ return r; ++ authorize_key = &pubkey_tpm2; ++ } ++ + /* + * if a pin is set for the seal object, use it to bind the session + * key to that object. This prevents active bus interposers from +@@ -2853,7 +2756,8 @@ int tpm2_unseal(const char *device, + policy_session, + hash_pcr_mask, + pcr_bank, +- pubkey, pubkey_size, ++ authorize_key, ++ fp, fp_size, + pubkey_pcr_mask, + signature, + !!pin, diff --git a/SOURCES/0528-tpm2-add-tpm2_calculate_sealing_policy.patch b/SOURCES/0528-tpm2-add-tpm2_calculate_sealing_policy.patch new file mode 100644 index 0000000..eeac65a --- /dev/null +++ b/SOURCES/0528-tpm2-add-tpm2_calculate_sealing_policy.patch @@ -0,0 +1,208 @@ +From 3047d3fed460b6df83cb0713d5ef25169fdebb4e Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 15 Dec 2022 12:56:35 -0500 +Subject: [PATCH] tpm2: add tpm2_calculate_sealing_policy() + +This adds a function to fully calculate the authPolicy needed to seal a secret, +and updates tpm2_seal() to use the new function instead of a trial policy. + +(cherry picked from commit d9a1f1a724a08defb70dbc6f44aa578983a66ac8) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 118 ++++++++++++++++++++++++----------------- + 1 file changed, 70 insertions(+), 48 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 2747cf0b53..f638c18223 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -2263,6 +2263,40 @@ static int tpm2_policy_authorize( + return tpm2_get_policy_digest(c, session, ret_policy_digest); + } + ++/* Extend 'digest' with the calculated policy hash. */ ++static int tpm2_calculate_sealing_policy( ++ const TPML_PCR_SELECTION *hash_pcr_selection, ++ const TPM2B_DIGEST *hash_pcr_values, ++ size_t n_hash_pcr_values, ++ const TPM2B_PUBLIC *public, ++ const char *pin, ++ TPM2B_DIGEST *digest) { ++ ++ int r; ++ ++ assert(digest); ++ ++ if (public) { ++ r = tpm2_calculate_policy_authorize(public, NULL, digest); ++ if (r < 0) ++ return r; ++ } ++ ++ if (hash_pcr_selection && !tpm2_tpml_pcr_selection_is_empty(hash_pcr_selection)) { ++ r = tpm2_calculate_policy_pcr(hash_pcr_selection, hash_pcr_values, n_hash_pcr_values, digest); ++ if (r < 0) ++ return r; ++ } ++ ++ if (pin) { ++ r = tpm2_calculate_policy_auth_value(digest); ++ if (r < 0) ++ return r; ++ } ++ ++ return 0; ++} ++ + static int tpm2_build_sealing_policy( + Tpm2Context *c, + const Tpm2Handle *session, +@@ -2345,7 +2379,6 @@ int tpm2_seal(const char *device, + _cleanup_(erase_and_freep) void *secret = NULL; + _cleanup_free_ void *hash = NULL; + TPM2B_SENSITIVE_CREATE hmac_sensitive; +- TPMI_ALG_PUBLIC primary_alg; + TPM2B_PUBLIC hmac_template; + usec_t start; + TSS2_RC rc; +@@ -2398,59 +2431,37 @@ int tpm2_seal(const char *device, + return r; + } + ++ TPML_PCR_SELECTION hash_pcr_selection = {}; ++ _cleanup_free_ TPM2B_DIGEST *hash_pcr_values = NULL; ++ size_t n_hash_pcr_values = 0; ++ if (hash_pcr_mask) { ++ /* For now, we just read the current values from the system; we need to be able to specify ++ * expected values, eventually. */ ++ tpm2_tpml_pcr_selection_from_mask(hash_pcr_mask, pcr_bank, &hash_pcr_selection); ++ r = tpm2_pcr_read(c, &hash_pcr_selection, &hash_pcr_selection, &hash_pcr_values, &n_hash_pcr_values); ++ if (r < 0) ++ return r; ++ } ++ + TPM2B_PUBLIC pubkey_tpm2, *authorize_key = NULL; +- _cleanup_free_ void *fp = NULL; +- size_t fp_size = 0; + if (pubkey) { +- r = openssl_pubkey_to_tpm2_pubkey(pubkey, pubkey_size, &pubkey_tpm2, &fp, &fp_size); ++ r = openssl_pubkey_to_tpm2_pubkey(pubkey, pubkey_size, &pubkey_tpm2, NULL, NULL); + if (r < 0) + return r; + authorize_key = &pubkey_tpm2; + } + +- _cleanup_tpm2_handle_ Tpm2Handle *primary = NULL; +- r = tpm2_make_primary(c, /* alg = */0, !!ret_srk_buf, &primary_alg, &primary); ++ TPM2B_DIGEST policy_digest; ++ r = tpm2_digest_init(TPM2_ALG_SHA256, &policy_digest); + if (r < 0) + return r; + +- /* we cannot use the bind key before its created */ +- _cleanup_tpm2_handle_ Tpm2Handle *encryption_session = NULL; +- r = tpm2_make_encryption_session(c, primary, &TPM2_HANDLE_NONE, &encryption_session); +- if (r < 0) +- return r; +- +- /* So apparently some TPM implementations don't implement trial mode correctly. To avoid issues let's +- * avoid it when it is easy to. At the moment we only really need trial mode for the signed PCR +- * policies (since only then we need to shove PCR values into the policy that don't match current +- * state anyway), hence if we have none of those we don't need to bother. Hence, let's patch in +- * TPM2_SE_POLICY even if trial mode is requested unless a pubkey PCR mask is specified that is +- * non-zero, i.e. signed PCR policy is requested. +- * +- * One day we should switch to calculating policy hashes client side when trial mode is requested, to +- * avoid this mess. */ +- bool trial = (pubkey_pcr_mask != 0); +- +- _cleanup_tpm2_handle_ Tpm2Handle *policy_session = NULL; +- r = tpm2_make_policy_session( +- c, +- primary, +- encryption_session, +- trial, +- &policy_session); +- if (r < 0) +- return r; +- +- _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; +- r = tpm2_build_sealing_policy( +- c, +- policy_session, +- hash_pcr_mask, +- pcr_bank, ++ r = tpm2_calculate_sealing_policy( ++ &hash_pcr_selection, ++ hash_pcr_values, ++ n_hash_pcr_values, + authorize_key, +- fp, fp_size, +- pubkey_pcr_mask, +- /* signature_json= */ NULL, +- !!pin, ++ pin, + &policy_digest); + if (r < 0) + return r; +@@ -2466,7 +2477,7 @@ int tpm2_seal(const char *device, + .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT, + .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL, + .unique.keyedHash.size = SHA256_DIGEST_SIZE, +- .authPolicy = *policy_digest, ++ .authPolicy = policy_digest, + }, + }; + +@@ -2490,11 +2501,22 @@ int tpm2_seal(const char *device, + if (r < 0) + return log_error_errno(r, "Failed to generate secret key: %m"); + ++ _cleanup_tpm2_handle_ Tpm2Handle *primary_handle = NULL; ++ TPMI_ALG_PUBLIC primary_alg; ++ r = tpm2_make_primary(c, /* alg = */0, !!ret_srk_buf, &primary_alg, &primary_handle); ++ if (r < 0) ++ return r; ++ ++ _cleanup_tpm2_handle_ Tpm2Handle *encryption_session = NULL; ++ r = tpm2_make_encryption_session(c, primary_handle, &TPM2_HANDLE_NONE, &encryption_session); ++ if (r < 0) ++ return r; ++ + log_debug("Creating HMAC key."); + + rc = sym_Esys_Create( + c->esys_context, +- primary->esys_handle, ++ primary_handle->esys_handle, + encryption_session->esys_handle, /* use HMAC session to enable parameter encryption */ + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -2534,7 +2556,7 @@ int tpm2_seal(const char *device, + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal public key: %s", sym_Tss2_RC_Decode(rc)); + +- hash = memdup(policy_digest->buffer, policy_digest->size); ++ hash = memdup(policy_digest.buffer, policy_digest.size); + if (!hash) + return log_oom(); + +@@ -2544,7 +2566,7 @@ int tpm2_seal(const char *device, + */ + if (ret_srk_buf) { + log_debug("Serializing SRK ESYS_TR reference"); +- rc = sym_Esys_TR_Serialize(c->esys_context, primary->esys_handle, &srk_buf, &srk_buf_size); ++ rc = sym_Esys_TR_Serialize(c->esys_context, primary_handle->esys_handle, &srk_buf, &srk_buf_size); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to serialize primary key: %s", sym_Tss2_RC_Decode(rc)); +@@ -2572,7 +2594,7 @@ int tpm2_seal(const char *device, + *ret_blob = TAKE_PTR(blob); + *ret_blob_size = blob_size; + *ret_pcr_hash = TAKE_PTR(hash); +- *ret_pcr_hash_size = policy_digest->size; ++ *ret_pcr_hash_size = policy_digest.size; + *ret_pcr_bank = pcr_bank; + *ret_primary_alg = primary_alg; + diff --git a/SOURCES/0529-tpm-remove-external-calls-to-dlopen_tpm2.patch b/SOURCES/0529-tpm-remove-external-calls-to-dlopen_tpm2.patch new file mode 100644 index 0000000..ea3ffc1 --- /dev/null +++ b/SOURCES/0529-tpm-remove-external-calls-to-dlopen_tpm2.patch @@ -0,0 +1,46 @@ +From 31bb91d7c4cc04586865e041a227730b459554c0 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 5 May 2023 19:53:32 -0400 +Subject: [PATCH] tpm: remove external calls to dlopen_tpm2() + +The calls outside tpm2-util.c are redundant, as tpm2_context_new() +is always called immediately after, which then calls dlopen_tpm2(). + +(cherry picked from commit 9944909e68e06d903d828aeca4a48abc6285f74e) + +Related: RHEL-16182 +--- + src/boot/pcrphase.c | 4 ---- + src/cryptsetup/cryptsetup.c | 4 ---- + 2 files changed, 8 deletions(-) + +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index 6c37d34fd6..57e31e6cad 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -340,10 +340,6 @@ static int run(int argc, char *argv[]) { + return EXIT_SUCCESS; + } + +- r = dlopen_tpm2(); +- if (r < 0) +- return log_error_errno(r, "Failed to load TPM2 libraries: %m"); +- + _cleanup_tpm2_context_ Tpm2Context *c = NULL; + r = tpm2_context_new(arg_tpm2_device, &c); + if (r < 0) +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index caef45637c..b384897e4f 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -837,10 +837,6 @@ static int measure_volume_key( + } + + #if HAVE_TPM2 +- r = dlopen_tpm2(); +- if (r < 0) +- return log_error_errno(r, "Failed to load TPM2 libraries: %m"); +- + _cleanup_tpm2_context_ Tpm2Context *c = NULL; + r = tpm2_context_new(arg_tpm2_device, &c); + if (r < 0) diff --git a/SOURCES/0530-tpm2-remove-all-extern-tpm2-tss-symbols.patch b/SOURCES/0530-tpm2-remove-all-extern-tpm2-tss-symbols.patch new file mode 100644 index 0000000..3816420 --- /dev/null +++ b/SOURCES/0530-tpm2-remove-all-extern-tpm2-tss-symbols.patch @@ -0,0 +1,198 @@ +From 663405d25eb1bcec9694c7c8c43b29801c9d2005 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 5 May 2023 19:49:49 -0400 +Subject: [PATCH] tpm2: remove all extern tpm2-tss symbols + +These library syms should be restricted to use only in tpm2-util.c, +and all other code should use simpler functions exported in tpm2-util.h. + +Also move the Esys_Freep() cleanup function into tpm-util.c, and make +tpm2-tss symbols static. + +(cherry picked from commit b57a7b3d9b7e1d4081cd1a4abb0772c3b5d3ccb2) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 91 ++++++++++++++++++++++-------------------- + src/shared/tpm2-util.h | 41 ------------------- + 2 files changed, 48 insertions(+), 84 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index f638c18223..6f62dd609a 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -29,46 +29,46 @@ static void *libtss2_esys_dl = NULL; + static void *libtss2_rc_dl = NULL; + static void *libtss2_mu_dl = NULL; + +-TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL; +-TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL; +-TSS2_RC (*sym_Esys_EvictControl)(ESYS_CONTEXT *esysContext, ESYS_TR auth, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPMI_DH_PERSISTENT persistentHandle, ESYS_TR *newObjectHandle); +-void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL; +-TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL; +-void (*sym_Esys_Free)(void *ptr) = NULL; +-TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData); +-TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL; +-TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL; +-TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL; +-TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle); +-TSS2_RC (*sym_Esys_PCR_Extend)(ESYS_CONTEXT *esysContext, ESYS_TR pcrHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST_VALUES *digests); +-TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues); +-TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *approvedPolicy, const TPM2B_NONCE *policyRef, const TPM2B_NAME *keySign, const TPMT_TK_VERIFIED *checkTicket); +-TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL; +-TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL; +-TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL; +-TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_PUBLIC **outPublic, TPM2B_NAME **name, TPM2B_NAME **qualifiedName); +-TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL; +-TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL; +-TSS2_RC (*sym_Esys_TRSess_GetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION *flags); +-TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask); +-TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name); +-TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle); +-TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object); +-TSS2_RC (*sym_Esys_TR_Serialize)(ESYS_CONTEXT *esys_context, ESYS_TR object, uint8_t **buffer, size_t *buffer_size); +-TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL; +-TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL; +-TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation); +- +-const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL; +- +-TSS2_RC (*sym_Tss2_MU_TPM2_CC_Marshal)(TPM2_CC src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; +-TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; +-TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL; +-TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; +-TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL; +-TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; +-TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; +-TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL; ++static TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL; ++static TSS2_RC (*sym_Esys_EvictControl)(ESYS_CONTEXT *esysContext, ESYS_TR auth, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPMI_DH_PERSISTENT persistentHandle, ESYS_TR *newObjectHandle) = NULL; ++static void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL; ++static TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle) = NULL; ++static void (*sym_Esys_Free)(void *ptr) = NULL; ++static TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData) = NULL; ++static TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL; ++static TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL; ++static TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL; ++static TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle) = NULL; ++static TSS2_RC (*sym_Esys_PCR_Extend)(ESYS_CONTEXT *esysContext, ESYS_TR pcrHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST_VALUES *digests) = NULL; ++static TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues) = NULL; ++static TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *approvedPolicy, const TPM2B_NONCE *policyRef, const TPM2B_NAME *keySign, const TPMT_TK_VERIFIED *checkTicket) = NULL; ++static TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL; ++static TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL; ++static TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL; ++static TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_PUBLIC **outPublic, TPM2B_NAME **name, TPM2B_NAME **qualifiedName) = NULL; ++static TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL; ++static TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL; ++static TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle) = NULL; ++static TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object) = NULL; ++static TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name) = NULL; ++static TSS2_RC (*sym_Esys_TR_Serialize)(ESYS_CONTEXT *esys_context, ESYS_TR object, uint8_t **buffer, size_t *buffer_size) = NULL; ++static TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL; ++static TSS2_RC (*sym_Esys_TRSess_GetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION *flags) = NULL; ++static TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask) = NULL; ++static TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData) = NULL; ++static TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation) = NULL; ++ ++static TSS2_RC (*sym_Tss2_MU_TPM2_CC_Marshal)(TPM2_CC src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++ ++static const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL; + + int dlopen_tpm2(void) { + int r; +@@ -95,13 +95,13 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Esys_ReadPublic), + DLSYM_ARG(Esys_StartAuthSession), + DLSYM_ARG(Esys_Startup), +- DLSYM_ARG(Esys_TRSess_GetAttributes), +- DLSYM_ARG(Esys_TRSess_SetAttributes), ++ DLSYM_ARG(Esys_TR_Deserialize), + DLSYM_ARG(Esys_TR_FromTPMPublic), + DLSYM_ARG(Esys_TR_GetName), +- DLSYM_ARG(Esys_TR_Deserialize), + DLSYM_ARG(Esys_TR_Serialize), + DLSYM_ARG(Esys_TR_SetAuth), ++ DLSYM_ARG(Esys_TRSess_GetAttributes), ++ DLSYM_ARG(Esys_TRSess_SetAttributes), + DLSYM_ARG(Esys_Unseal), + DLSYM_ARG(Esys_VerifySignature)); + if (r < 0) +@@ -125,6 +125,11 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Tss2_MU_TPMT_PUBLIC_Marshal)); + } + ++static inline void Esys_Freep(void *p) { ++ if (*(void**) p) ++ sym_Esys_Free(*(void**) p); ++} ++ + static Tpm2Context *tpm2_context_free(Tpm2Context *c) { + if (!c) + return NULL; +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 526e2fdfb2..9bf9e44878 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -39,42 +39,6 @@ static inline bool TPM2_PCR_MASK_VALID(uint32_t pcr_mask) { + #include + #include + +-extern TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket); +-extern TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket); +-extern void (*sym_Esys_Finalize)(ESYS_CONTEXT **context); +-extern TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flushHandle); +-extern void (*sym_Esys_Free)(void *ptr); +-extern TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData); +-extern TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes); +-extern TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion); +-extern TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle); +-extern TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle); +-extern TSS2_RC (*sym_Esys_PCR_Extend)(ESYS_CONTEXT *esysContext, ESYS_TR pcrHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST_VALUES *digests); +-extern TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues); +-extern TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *approvedPolicy, const TPM2B_NONCE *policyRef, const TPM2B_NAME *keySign, const TPMT_TK_VERIFIED *checkTicket); +-extern TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3); +-extern TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest); +-extern TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs); +-extern TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle); +-extern TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType); +-extern TSS2_RC (*sym_Esys_TRSess_GetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION *flags); +-extern TSS2_RC (*sym_Esys_TRSess_SetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION flags, TPMA_SESSION mask); +-extern TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name); +-extern TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue); +-extern TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_SENSITIVE_DATA **outData); +-extern TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation); +- +-extern const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc); +- +-extern TSS2_RC (*sym_Tss2_MU_TPM2_CC_Marshal)(TPM2_CC src, uint8_t buffer[], size_t buffer_size, size_t *offset); +-extern TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); +-extern TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest); +-extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); +-extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest); +-extern TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); +-extern TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); +-extern TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset); +- + int dlopen_tpm2(void); + + int tpm2_digest_many(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const struct iovec data[], size_t count, bool extend); +@@ -125,11 +89,6 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle); + DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free); + #define _cleanup_tpm2_handle_ _cleanup_(tpm2_handle_freep) + +-static inline void Esys_Freep(void *p) { +- if (*(void**) p) +- sym_Esys_Free(*(void**) p); +-} +- + int tpm2_get_good_pcr_banks(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks); + int tpm2_get_good_pcr_banks_strv(Tpm2Context *c, uint32_t pcr_mask, char ***ret); + diff --git a/SOURCES/0531-tpm2-add-tpm2_get_capability-tpm2_cache_capabilities.patch b/SOURCES/0531-tpm2-add-tpm2_get_capability-tpm2_cache_capabilities.patch new file mode 100644 index 0000000..a1d0d8f --- /dev/null +++ b/SOURCES/0531-tpm2-add-tpm2_get_capability-tpm2_cache_capabilities.patch @@ -0,0 +1,274 @@ +From ca70ae72c223e6f0bc4b41efee13a847b2968734 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Sun, 1 Jan 2023 20:19:12 -0500 +Subject: [PATCH] tpm2: add tpm2_get_capability(), tpm2_cache_capabilities(), + tpm2_capability_pcrs() + +This adds a function to query specific capabilities from the TPM. That is then +used in a function to query the allocation of PCRs in the TPM, i.e. which PCR +banks and indexes are available, and caches the PCR allocation when the TPM +context is created. + +(cherry picked from commit 3a35d6cdd29f0303b9fffff2f34461b2be0cb1c7) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 159 ++++++++++++++++++++++++++++------------- + src/shared/tpm2-util.h | 3 + + 2 files changed, 113 insertions(+), 49 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 6f62dd609a..460ebe62c7 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -130,6 +130,94 @@ static inline void Esys_Freep(void *p) { + sym_Esys_Free(*(void**) p); + } + ++/* Get a specific TPM capability (or capabilities). ++ * ++ * Returns 0 if there are no more capability properties of the requested type, or 1 if there are more, or < 0 ++ * on any error. Both 0 and 1 indicate this completed successfully, but do not indicate how many capability ++ * properties were provided in 'ret_capability_data'. To find the number of provided properties, check the ++ * specific type's 'count' field (e.g. for TPM2_CAP_ALGS, check ret_capability_data->algorithms.count). ++ * ++ * This calls TPM2_GetCapability() and does not alter the provided data, so it is important to understand how ++ * that TPM function works. It is recommended to check the TCG TPM specification Part 3 ("Commands") section ++ * on TPM2_GetCapability() for full details, but a short summary is: if this returns 0, all available ++ * properties have been provided in ret_capability_data, or no properties were available. If this returns 1, ++ * there are between 1 and "count" properties provided in ret_capability_data, and there are more available. ++ * Note that this may provide less than "count" properties even if the TPM has more available. Also, each ++ * capability category may have more specific requirements than described here; see the spec for exact ++ * details. */ ++static int tpm2_get_capability( ++ Tpm2Context *c, ++ TPM2_CAP capability, ++ uint32_t property, ++ uint32_t count, ++ TPMU_CAPABILITIES *ret_capability_data) { ++ ++ _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *capabilities = NULL; ++ TPMI_YES_NO more; ++ TSS2_RC rc; ++ ++ assert(c); ++ ++ log_debug("Getting TPM2 capability 0x%04" PRIx32 " property 0x%04" PRIx32 " count %" PRIu32 ".", ++ capability, property, count); ++ ++ rc = sym_Esys_GetCapability( ++ c->esys_context, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ capability, ++ property, ++ count, ++ &more, ++ &capabilities); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to get TPM2 capability 0x%04" PRIx32 " property 0x%04" PRIx32 ": %s", ++ capability, property, sym_Tss2_RC_Decode(rc)); ++ ++ if (capabilities->capability != capability) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "TPM provided wrong capability: 0x%04" PRIx32 " instead of 0x%04" PRIx32 ".", ++ capabilities->capability, capability); ++ ++ if (ret_capability_data) ++ *ret_capability_data = capabilities->data; ++ ++ return more == TPM2_YES; ++} ++ ++static int tpm2_cache_capabilities(Tpm2Context *c) { ++ TPMU_CAPABILITIES capability; ++ int r; ++ ++ assert(c); ++ ++ /* Cache the PCR capabilities, which are safe to cache, as the only way they can change is ++ * TPM2_PCR_Allocate(), which changes the allocation after the next _TPM_Init(). If the TPM is ++ * reinitialized while we are using it, all our context and sessions will be invalid, so we can ++ * safely assume the TPM PCR allocation will not change while we are using it. */ ++ r = tpm2_get_capability( ++ c, ++ TPM2_CAP_PCRS, ++ /* property= */ 0, ++ /* count= */ 1, ++ &capability); ++ if (r < 0) ++ return r; ++ if (r == 1) ++ /* This should never happen. Part 3 ("Commands") of the TCG TPM2 spec in the section for ++ * TPM2_GetCapability states: "TPM_CAP_PCRS – Returns the current allocation of PCR in a ++ * TPML_PCR_SELECTION. The property parameter shall be zero. The TPM will always respond to ++ * this command with the full PCR allocation and moreData will be NO." */ ++ log_warning("TPM bug: reported multiple PCR sets; using only first set."); ++ c->capability_pcrs = capability.assignedPCR; ++ ++ return 0; ++} ++ ++#define tpm2_capability_pcrs(c) ((c)->capability_pcrs) ++ + static Tpm2Context *tpm2_context_free(Tpm2Context *c) { + if (!c) + return NULL; +@@ -250,6 +338,10 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to start up TPM: %s", sym_Tss2_RC_Decode(rc)); + ++ r = tpm2_cache_capabilities(context); ++ if (r < 0) ++ return r; ++ + *ret_context = TAKE_PTR(context); + + return 0; +@@ -1173,48 +1265,33 @@ static int tpm2_get_best_pcr_bank( + uint32_t pcr_mask, + TPMI_ALG_HASH *ret) { + +- _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *pcap = NULL; ++ TPML_PCR_SELECTION pcrs; + TPMI_ALG_HASH supported_hash = 0, hash_with_valid_pcr = 0; +- TPMI_YES_NO more; +- TSS2_RC rc; + int r; + + assert(c); ++ assert(ret); + +- rc = sym_Esys_GetCapability( +- c->esys_context, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- TPM2_CAP_PCRS, +- 0, +- 1, +- &more, +- &pcap); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to determine TPM2 PCR bank capabilities: %s", sym_Tss2_RC_Decode(rc)); +- +- assert(pcap->capability == TPM2_CAP_PCRS); +- +- for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) { ++ pcrs = tpm2_capability_pcrs(c); ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection, &pcrs) { ++ TPMI_ALG_HASH hash = selection->hash; + int good; + + /* For now we are only interested in the SHA1 and SHA256 banks */ +- if (!IN_SET(pcap->data.assignedPCR.pcrSelections[i].hash, TPM2_ALG_SHA256, TPM2_ALG_SHA1)) ++ if (!IN_SET(hash, TPM2_ALG_SHA256, TPM2_ALG_SHA1)) + continue; + +- r = tpm2_bank_has24(pcap->data.assignedPCR.pcrSelections + i); ++ r = tpm2_bank_has24(selection); + if (r < 0) + return r; + if (!r) + continue; + +- good = tpm2_pcr_mask_good(c, pcap->data.assignedPCR.pcrSelections[i].hash, pcr_mask); ++ good = tpm2_pcr_mask_good(c, hash, pcr_mask); + if (good < 0) + return good; + +- if (pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA256) { ++ if (hash == TPM2_ALG_SHA256) { + supported_hash = TPM2_ALG_SHA256; + if (good) { + /* Great, SHA256 is supported and has initialized PCR values, we are done. */ +@@ -1222,7 +1299,7 @@ static int tpm2_get_best_pcr_bank( + break; + } + } else { +- assert(pcap->data.assignedPCR.pcrSelections[i].hash == TPM2_ALG_SHA1); ++ assert(hash == TPM2_ALG_SHA1); + + if (supported_hash == 0) + supported_hash = TPM2_ALG_SHA1; +@@ -1271,42 +1348,26 @@ int tpm2_get_good_pcr_banks( + TPMI_ALG_HASH **ret) { + + _cleanup_free_ TPMI_ALG_HASH *good_banks = NULL, *fallback_banks = NULL; +- _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *pcap = NULL; ++ TPML_PCR_SELECTION pcrs; + size_t n_good_banks = 0, n_fallback_banks = 0; +- TPMI_YES_NO more; +- TSS2_RC rc; + int r; + + assert(c); + assert(ret); + +- rc = sym_Esys_GetCapability( +- c->esys_context, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- TPM2_CAP_PCRS, +- 0, +- 1, +- &more, +- &pcap); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to determine TPM2 PCR bank capabilities: %s", sym_Tss2_RC_Decode(rc)); +- +- assert(pcap->capability == TPM2_CAP_PCRS); +- +- for (size_t i = 0; i < pcap->data.assignedPCR.count; i++) { ++ pcrs = tpm2_capability_pcrs(c); ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection, &pcrs) { ++ TPMI_ALG_HASH hash = selection->hash; + + /* Let's see if this bank is superficially OK, i.e. has at least 24 enabled registers */ +- r = tpm2_bank_has24(pcap->data.assignedPCR.pcrSelections + i); ++ r = tpm2_bank_has24(selection); + if (r < 0) + return r; + if (!r) + continue; + + /* Let's now see if this bank has any of the selected PCRs actually initialized */ +- r = tpm2_pcr_mask_good(c, pcap->data.assignedPCR.pcrSelections[i].hash, pcr_mask); ++ r = tpm2_pcr_mask_good(c, hash, pcr_mask); + if (r < 0) + return r; + +@@ -1317,12 +1378,12 @@ int tpm2_get_good_pcr_banks( + if (!GREEDY_REALLOC(good_banks, n_good_banks+1)) + return log_oom(); + +- good_banks[n_good_banks++] = pcap->data.assignedPCR.pcrSelections[i].hash; ++ good_banks[n_good_banks++] = hash; + } else { + if (!GREEDY_REALLOC(fallback_banks, n_fallback_banks+1)) + return log_oom(); + +- fallback_banks[n_fallback_banks++] = pcap->data.assignedPCR.pcrSelections[i].hash; ++ fallback_banks[n_fallback_banks++] = hash; + } + } + +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 9bf9e44878..5e5d9e2604 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -67,6 +67,9 @@ typedef struct { + void *tcti_dl; + TSS2_TCTI_CONTEXT *tcti_context; + ESYS_CONTEXT *esys_context; ++ ++ /* Some selected cached capabilities of the TPM */ ++ TPML_PCR_SELECTION capability_pcrs; + } Tpm2Context; + + int tpm2_context_new(const char *device, Tpm2Context **ret_context); diff --git a/SOURCES/0532-tpm2-verify-symmetric-parms-in-tpm2_context_new.patch b/SOURCES/0532-tpm2-verify-symmetric-parms-in-tpm2_context_new.patch new file mode 100644 index 0000000..bcafb68 --- /dev/null +++ b/SOURCES/0532-tpm2-verify-symmetric-parms-in-tpm2_context_new.patch @@ -0,0 +1,272 @@ +From 2c78f1e768c75bbea7076fa9242ba484c8f472b5 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 17 Feb 2023 12:59:18 -0500 +Subject: [PATCH] tpm2: verify symmetric parms in tpm2_context_new() + +This adds tpm2_get_capability_algs(), tpm2_supports_alg(), and +tpm2_test_parms(). These functions allow verifying that the TPM supports +specific algs and parameters. + +When creating a new context, this checks if the TPM supports the symmetric algs +we use. If the TPM does not support the symmetric algs and parameters we +require, we log and return error. + +(cherry picked from commit a47060bb34c912ea9909fcf617f7b553488b5daf) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 119 ++++++++++++++++++++++++++++++++++++----- + src/shared/tpm2-util.h | 4 ++ + src/test/test-tpm2.c | 38 +++++++++++++ + 3 files changed, 149 insertions(+), 12 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 460ebe62c7..91f66aaaf4 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -49,6 +49,7 @@ static TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySe + static TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_PUBLIC **outPublic, TPM2B_NAME **name, TPM2B_NAME **qualifiedName) = NULL; + static TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL; + static TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL; ++static TSS2_RC (*sym_Esys_TestParms)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPMT_PUBLIC_PARMS *parameters) = NULL; + static TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle) = NULL; + static TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object) = NULL; + static TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name) = NULL; +@@ -95,6 +96,7 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Esys_ReadPublic), + DLSYM_ARG(Esys_StartAuthSession), + DLSYM_ARG(Esys_Startup), ++ DLSYM_ARG(Esys_TestParms), + DLSYM_ARG(Esys_TR_Deserialize), + DLSYM_ARG(Esys_TR_FromTPMPublic), + DLSYM_ARG(Esys_TR_GetName), +@@ -218,6 +220,87 @@ static int tpm2_cache_capabilities(Tpm2Context *c) { + + #define tpm2_capability_pcrs(c) ((c)->capability_pcrs) + ++/* Get the TPMA_ALGORITHM for a TPM2_ALG_ID. ++ * ++ * Returns 1 if the TPM supports the algorithm and the TPMA_ALGORITHM is provided, or 0 if the TPM does not ++ * support the algorithm, or < 0 for any errors. */ ++static int tpm2_get_capability_alg(Tpm2Context *c, TPM2_ALG_ID alg, TPMA_ALGORITHM *ret) { ++ TPMU_CAPABILITIES capability; ++ int r; ++ ++ assert(c); ++ ++ /* The spec explicitly states the TPM2_ALG_ID should be cast to uint32_t. */ ++ r = tpm2_get_capability(c, TPM2_CAP_ALGS, (uint32_t) alg, 1, &capability); ++ if (r < 0) ++ return r; ++ ++ TPML_ALG_PROPERTY algorithms = capability.algorithms; ++ if (algorithms.count == 0 || algorithms.algProperties[0].alg != alg) { ++ log_debug("TPM does not support alg 0x%02" PRIx16 ".", alg); ++ return 0; ++ } ++ ++ if (ret) ++ *ret = algorithms.algProperties[0].algProperties; ++ ++ return 1; ++} ++ ++/* Returns 1 if the TPM supports the alg, 0 if the TPM does not support the alg, or < 0 for any error. */ ++int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg) { ++ return tpm2_get_capability_alg(c, alg, NULL); ++} ++ ++/* Returns 1 if the TPM supports the parms, or 0 if the TPM does not support the parms. */ ++bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARMS *parms) { ++ TSS2_RC rc; ++ ++ assert(c); ++ assert(parms); ++ ++ TPMT_PUBLIC_PARMS parameters = { ++ .type = alg, ++ .parameters = *parms, ++ }; ++ ++ rc = sym_Esys_TestParms(c->esys_context, ESYS_TR_NONE, ESYS_TR_NONE, ESYS_TR_NONE, ¶meters); ++ if (rc != TSS2_RC_SUCCESS) ++ /* The spec says if the parms are not supported the TPM returns "...the appropriate ++ * unmarshaling error if a parameter is not valid". Since the spec (currently) defines 15 ++ * unmarshaling errors, instead of checking for them all here, let's just assume any error ++ * indicates unsupported parms, and log the specific error text. */ ++ log_debug("TPM does not support tested parms: %s", sym_Tss2_RC_Decode(rc)); ++ ++ return rc == TSS2_RC_SUCCESS; ++} ++ ++static inline bool tpm2_supports_tpmt_sym_def_object(Tpm2Context *c, const TPMT_SYM_DEF_OBJECT *parameters) { ++ assert(c); ++ assert(parameters); ++ ++ TPMU_PUBLIC_PARMS parms = { ++ .symDetail.sym = *parameters, ++ }; ++ ++ return tpm2_test_parms(c, TPM2_ALG_SYMCIPHER, &parms); ++} ++ ++static inline bool tpm2_supports_tpmt_sym_def(Tpm2Context *c, const TPMT_SYM_DEF *parameters) { ++ assert(c); ++ assert(parameters); ++ ++ /* Unfortunately, TPMT_SYM_DEF and TPMT_SYM_DEF_OBEJECT are separately defined, even though they are ++ * functionally identical. */ ++ TPMT_SYM_DEF_OBJECT object = { ++ .algorithm = parameters->algorithm, ++ .keyBits = parameters->keyBits, ++ .mode = parameters->mode, ++ }; ++ ++ return tpm2_supports_tpmt_sym_def_object(c, &object); ++} ++ + static Tpm2Context *tpm2_context_free(Tpm2Context *c) { + if (!c) + return NULL; +@@ -233,6 +316,12 @@ static Tpm2Context *tpm2_context_free(Tpm2Context *c) { + + DEFINE_TRIVIAL_REF_UNREF_FUNC(Tpm2Context, tpm2_context, tpm2_context_free); + ++static const TPMT_SYM_DEF SESSION_TEMPLATE_SYM_AES_128_CFB = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, /* The spec requires sessions to use CFB. */ ++}; ++ + int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + _cleanup_tpm2_context_ Tpm2Context *context = NULL; + TSS2_RC rc; +@@ -342,6 +431,22 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + if (r < 0) + return r; + ++ /* We require AES and CFB support for session encryption. */ ++ r = tpm2_supports_alg(context, TPM2_ALG_AES); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES."); ++ ++ r = tpm2_supports_alg(context, TPM2_ALG_CFB); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support CFB."); ++ ++ if (!tpm2_supports_tpmt_sym_def(context, &SESSION_TEMPLATE_SYM_AES_128_CFB)) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES-128-CFB."); ++ + *ret_context = TAKE_PTR(context); + + return 0; +@@ -1570,11 +1675,6 @@ static int tpm2_make_encryption_session( + const Tpm2Handle *bind_key, + Tpm2Handle **ret_session) { + +- static const TPMT_SYM_DEF symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }; + const TPMA_SESSION sessionAttributes = TPMA_SESSION_DECRYPT | TPMA_SESSION_ENCRYPT | + TPMA_SESSION_CONTINUESESSION; + TSS2_RC rc; +@@ -1602,7 +1702,7 @@ static int tpm2_make_encryption_session( + ESYS_TR_NONE, + NULL, + TPM2_SE_HMAC, +- &symmetric, ++ &SESSION_TEMPLATE_SYM_AES_128_CFB, + TPM2_ALG_SHA256, + &session->esys_handle); + if (rc != TSS2_RC_SUCCESS) +@@ -1631,11 +1731,6 @@ static int tpm2_make_policy_session( + bool trial, + Tpm2Handle **ret_session) { + +- static const TPMT_SYM_DEF symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }; + TPM2_SE session_type = trial ? TPM2_SE_TRIAL : TPM2_SE_POLICY; + TSS2_RC rc; + int r; +@@ -1665,7 +1760,7 @@ static int tpm2_make_policy_session( + ESYS_TR_NONE, + NULL, + session_type, +- &symmetric, ++ &SESSION_TEMPLATE_SYM_AES_128_CFB, + TPM2_ALG_SHA256, + &session->esys_handle); + if (rc != TSS2_RC_SUCCESS) +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 5e5d9e2604..764104ed58 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -92,6 +92,10 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle); + DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free); + #define _cleanup_tpm2_handle_ _cleanup_(tpm2_handle_freep) + ++int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg); ++ ++bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARMS *parms); ++ + int tpm2_get_good_pcr_banks(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks); + int tpm2_get_good_pcr_banks_strv(Tpm2Context *c, uint32_t pcr_mask, char ***ret); + +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 0b123c25a7..130a968273 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -713,6 +713,44 @@ TEST(calculate_policy_pcr) { + assert_se(digest_check(&d, "7481fd1b116078eb3ac2456e4ad542c9b46b9b8eb891335771ca8e7c8f8e4415")); + } + ++TEST(tpm_required_tests) { ++ int r; ++ ++ _cleanup_tpm2_context_ Tpm2Context *c = NULL; ++ r = tpm2_context_new(NULL, &c); ++ if (r < 0) { ++ log_tests_skipped("Could not find TPM"); ++ return; ++ } ++ ++ TPMU_PUBLIC_PARMS parms = { ++ .symDetail.sym = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }, ++ }; ++ ++ /* Test with invalid parms */ ++ assert_se(!tpm2_test_parms(c, TPM2_ALG_CFB, &parms)); ++ ++ TPMU_PUBLIC_PARMS invalid_parms = parms; ++ invalid_parms.symDetail.sym.keyBits.aes = 1; ++ assert_se(!tpm2_test_parms(c, TPM2_ALG_SYMCIPHER, &invalid_parms)); ++ ++ /* Test with valid parms */ ++ assert_se(tpm2_test_parms(c, TPM2_ALG_SYMCIPHER, &parms)); ++ ++ /* Test invalid algs */ ++ assert_se(tpm2_supports_alg(c, TPM2_ALG_ERROR) == 0); ++ assert_se(tpm2_supports_alg(c, TPM2_ALG_LAST + 1) == 0); ++ ++ /* Test valid algs */ ++ assert_se(tpm2_supports_alg(c, TPM2_ALG_RSA) == 1); ++ assert_se(tpm2_supports_alg(c, TPM2_ALG_AES) == 1); ++ assert_se(tpm2_supports_alg(c, TPM2_ALG_CFB) == 1); ++} ++ + #endif /* HAVE_TPM2 */ + + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0533-tpm2-replace-_cleanup_tpm2_-macros-with-_cleanup_.patch b/SOURCES/0533-tpm2-replace-_cleanup_tpm2_-macros-with-_cleanup_.patch new file mode 100644 index 0000000..f92ff11 --- /dev/null +++ b/SOURCES/0533-tpm2-replace-_cleanup_tpm2_-macros-with-_cleanup_.patch @@ -0,0 +1,212 @@ +From 7d2ce56f8ce505b3976b1c8dd435478c163db964 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 17 May 2023 17:16:23 -0400 +Subject: [PATCH] tpm2: replace _cleanup_tpm2_* macros with _cleanup_() + +Remove _cleanup_tpm2_context_ and _cleanup_tpm2_handle_ macros, replacing their +use with _cleanup_(tpm2_context_unrefp) and _cleanup_(tpm2_handle_freep), +respectively. + +(cherry picked from commit 1dc8f51841f2a552da8924c4d5501c7b1c757ba8) + +Related: RHEL-16182 +--- + src/boot/pcrphase.c | 2 +- + src/cryptsetup/cryptsetup.c | 2 +- + src/shared/tpm2-util.c | 30 +++++++++++++++--------------- + src/shared/tpm2-util.h | 2 -- + src/test/test-tpm2.c | 2 +- + 5 files changed, 18 insertions(+), 20 deletions(-) + +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index 57e31e6cad..16d71e6a22 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -340,7 +340,7 @@ static int run(int argc, char *argv[]) { + return EXIT_SUCCESS; + } + +- _cleanup_tpm2_context_ Tpm2Context *c = NULL; ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new(arg_tpm2_device, &c); + if (r < 0) + return r; +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index b384897e4f..674d222db6 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -837,7 +837,7 @@ static int measure_volume_key( + } + + #if HAVE_TPM2 +- _cleanup_tpm2_context_ Tpm2Context *c = NULL; ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new(arg_tpm2_device, &c); + if (r < 0) + return r; +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 91f66aaaf4..bc3ae8340d 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -323,7 +323,7 @@ static const TPMT_SYM_DEF SESSION_TEMPLATE_SYM_AES_128_CFB = { + }; + + int tpm2_context_new(const char *device, Tpm2Context **ret_context) { +- _cleanup_tpm2_context_ Tpm2Context *context = NULL; ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *context = NULL; + TSS2_RC rc; + int r; + +@@ -469,7 +469,7 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) { + if (!handle) + return NULL; + +- _cleanup_tpm2_context_ Tpm2Context *context = (Tpm2Context*)handle->tpm2_context; ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *context = (Tpm2Context*)handle->tpm2_context; + if (context && !handle->keep) + tpm2_handle_flush(context->esys_context, handle->esys_handle); + +@@ -477,7 +477,7 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) { + } + + int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle) { +- _cleanup_tpm2_handle_ Tpm2Handle *handle = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; + + assert(ret_handle); + +@@ -754,7 +754,7 @@ static int tpm2_make_primary( + + ts = now(CLOCK_MONOTONIC); + +- _cleanup_tpm2_handle_ Tpm2Handle *primary = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *primary = NULL; + r = tpm2_handle_new(c, &primary); + if (r < 0) + return r; +@@ -1688,7 +1688,7 @@ static int tpm2_make_encryption_session( + /* Start a salted, unbound HMAC session with a well-known key (e.g. primary key) as tpmKey, which + * means that the random salt will be encrypted with the well-known key. That way, only the TPM can + * recover the salt, which is then used for key derivation. */ +- _cleanup_tpm2_handle_ Tpm2Handle *session = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *session = NULL; + r = tpm2_handle_new(c, &session); + if (r < 0) + return r; +@@ -1746,7 +1746,7 @@ static int tpm2_make_policy_session( + + log_debug("Starting policy session."); + +- _cleanup_tpm2_handle_ Tpm2Handle *session = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *session = NULL; + r = tpm2_handle_new(c, &session); + if (r < 0) + return r; +@@ -2307,7 +2307,7 @@ static int tpm2_policy_authorize( + + log_debug("Adding PCR signature policy."); + +- _cleanup_tpm2_handle_ Tpm2Handle *pubkey_handle = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *pubkey_handle = NULL; + r = tpm2_handle_new(c, &pubkey_handle); + if (r < 0) + return r; +@@ -2579,7 +2579,7 @@ int tpm2_seal(const char *device, + + CLEANUP_ERASE(hmac_sensitive); + +- _cleanup_tpm2_context_ Tpm2Context *c = NULL; ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new(device, &c); + if (r < 0) + return r; +@@ -2662,13 +2662,13 @@ int tpm2_seal(const char *device, + if (r < 0) + return log_error_errno(r, "Failed to generate secret key: %m"); + +- _cleanup_tpm2_handle_ Tpm2Handle *primary_handle = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; + TPMI_ALG_PUBLIC primary_alg; + r = tpm2_make_primary(c, /* alg = */0, !!ret_srk_buf, &primary_alg, &primary_handle); + if (r < 0) + return r; + +- _cleanup_tpm2_handle_ Tpm2Handle *encryption_session = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; + r = tpm2_make_encryption_session(c, primary_handle, &TPM2_HANDLE_NONE, &encryption_session); + if (r < 0) + return r; +@@ -2829,13 +2829,13 @@ int tpm2_unseal(const char *device, + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to unmarshal public key: %s", sym_Tss2_RC_Decode(rc)); + +- _cleanup_tpm2_context_ Tpm2Context *c = NULL; ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new(device, &c); + if (r < 0) + return r; + + /* If their is a primary key we trust, like an SRK, use it */ +- _cleanup_tpm2_handle_ Tpm2Handle *primary = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *primary = NULL; + if (srk_buf) { + + r = tpm2_handle_new(c, &primary); +@@ -2868,7 +2868,7 @@ int tpm2_unseal(const char *device, + * SRK model, the tpmKey is verified. In the non-srk model, with pin, the bindKey + * provides protections. + */ +- _cleanup_tpm2_handle_ Tpm2Handle *hmac_key = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *hmac_key = NULL; + r = tpm2_handle_new(c, &hmac_key); + if (r < 0) + return r; +@@ -2917,13 +2917,13 @@ int tpm2_unseal(const char *device, + if (r < 0) + return r; + +- _cleanup_tpm2_handle_ Tpm2Handle *encryption_session = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; + r = tpm2_make_encryption_session(c, primary, hmac_key, &encryption_session); + if (r < 0) + return r; + + for (unsigned i = RETRY_UNSEAL_MAX;; i--) { +- _cleanup_tpm2_handle_ Tpm2Handle *policy_session = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; + _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; + r = tpm2_make_policy_session( + c, +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 764104ed58..a03bee148b 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -76,7 +76,6 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context); + Tpm2Context *tpm2_context_ref(Tpm2Context *context); + Tpm2Context *tpm2_context_unref(Tpm2Context *context); + DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref); +-#define _cleanup_tpm2_context_ _cleanup_(tpm2_context_unrefp) + + typedef struct { + Tpm2Context *tpm2_context; +@@ -90,7 +89,6 @@ static const Tpm2Handle TPM2_HANDLE_NONE = _tpm2_handle(NULL, ESYS_TR_NONE); + int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle); + Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle); + DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free); +-#define _cleanup_tpm2_handle_ _cleanup_(tpm2_handle_freep) + + int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg); + +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 130a968273..75e207e9d9 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -716,7 +716,7 @@ TEST(calculate_policy_pcr) { + TEST(tpm_required_tests) { + int r; + +- _cleanup_tpm2_context_ Tpm2Context *c = NULL; ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new(NULL, &c); + if (r < 0) { + log_tests_skipped("Could not find TPM"); diff --git a/SOURCES/0534-tpm2-util-use-compound-initialization-when-allocatin.patch b/SOURCES/0534-tpm2-util-use-compound-initialization-when-allocatin.patch new file mode 100644 index 0000000..4dd5afe --- /dev/null +++ b/SOURCES/0534-tpm2-util-use-compound-initialization-when-allocatin.patch @@ -0,0 +1,51 @@ +From 84aed24cce13f1432d050b72a7717df1098c9381 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Fri, 24 Feb 2023 18:20:50 +0100 +Subject: [PATCH] tpm2-util: use compound initialization when allocating tpm2 + objects + +(cherry picked from commit d70e4bc9f1395a5ca48ac4f6b3e71e64029312e1) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 14 +++++++++----- + 1 file changed, 9 insertions(+), 5 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index bc3ae8340d..bf36b4de95 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -329,11 +329,13 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + + assert(ret_context); + +- context = new0(Tpm2Context, 1); ++ context = new(Tpm2Context, 1); + if (!context) + return log_oom(); + +- context->n_ref = 1; ++ *context = (Tpm2Context) { ++ .n_ref = 1, ++ }; + + r = dlopen_tpm2(); + if (r < 0) +@@ -481,12 +483,14 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle) { + + assert(ret_handle); + +- handle = new0(Tpm2Handle, 1); ++ handle = new(Tpm2Handle, 1); + if (!handle) + return log_oom(); + +- handle->tpm2_context = tpm2_context_ref(context); +- handle->esys_handle = ESYS_TR_NONE; ++ *handle = (Tpm2Handle) { ++ .tpm2_context = tpm2_context_ref(context), ++ .esys_handle = ESYS_TR_NONE, ++ }; + + *ret_handle = TAKE_PTR(handle); + diff --git a/SOURCES/0535-tpm2-add-tpm2_get_capability_handle-tpm2_esys_handle.patch b/SOURCES/0535-tpm2-add-tpm2_get_capability_handle-tpm2_esys_handle.patch new file mode 100644 index 0000000..09b4e0c --- /dev/null +++ b/SOURCES/0535-tpm2-add-tpm2_get_capability_handle-tpm2_esys_handle.patch @@ -0,0 +1,408 @@ +From 88bac2c3213ab4a5036732ff9bc4f5dc2b4287b7 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 17 Feb 2023 12:50:31 -0500 +Subject: [PATCH] tpm2: add tpm2_get_capability_handle(), + tpm2_esys_handle_from_tpm_handle() + +Add tpm2_get_capability_handle() to query if a "TPM handle" (meaning, a +location/address in TPM storage) is populated in the TPM, and +tpm2_get_capability_handles() to query for a specific number of handles. + +Add tpm2_esys_handle_from_tpm_handle() to create an "esys handle" (an opaque +reference for use with the TPM EAPI that represents a TPM handle address) for an +existing TPM handle. + +Since the TPM handle already exists in the TPM, this also also requires +updating the cleanup code for Tpm2Handle objects to close the object (free its +resources only from the EAPI code, but leave the handle in the TPM) instead of +flush the object (which frees its EAPI resources and removes it from the TPM). + +(cherry picked from commit c8a85240316898a6de95c9b2565edd08f8450182) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 244 ++++++++++++++++++++++++++++++++--------- + src/shared/tpm2-util.h | 3 +- + 2 files changed, 193 insertions(+), 54 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index bf36b4de95..3278863f4d 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -50,6 +50,7 @@ static TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectH + static TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL; + static TSS2_RC (*sym_Esys_Startup)(ESYS_CONTEXT *esysContext, TPM2_SU startupType) = NULL; + static TSS2_RC (*sym_Esys_TestParms)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPMT_PUBLIC_PARMS *parameters) = NULL; ++static TSS2_RC (*sym_Esys_TR_Close)(ESYS_CONTEXT *esys_context, ESYS_TR *rsrc_handle) = NULL; + static TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle) = NULL; + static TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object) = NULL; + static TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name) = NULL; +@@ -97,6 +98,7 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Esys_StartAuthSession), + DLSYM_ARG(Esys_Startup), + DLSYM_ARG(Esys_TestParms), ++ DLSYM_ARG(Esys_TR_Close), + DLSYM_ARG(Esys_TR_Deserialize), + DLSYM_ARG(Esys_TR_FromTPMPublic), + DLSYM_ARG(Esys_TR_GetName), +@@ -252,6 +254,84 @@ int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg) { + return tpm2_get_capability_alg(c, alg, NULL); + } + ++/* Query the TPM for populated handles. ++ * ++ * This provides an array of handle indexes populated in the TPM, starting at the requested handle. The array will ++ * contain only populated handle addresses (which might not include the requested handle). The number of ++ * handles will be no more than the 'max' number requested. This will not search past the end of the handle ++ * range (i.e. handle & 0xff000000). ++ * ++ * Returns 0 if all populated handles in the range (starting at the requested handle) were provided (or no ++ * handles were in the range), or 1 if there are more populated handles in the range, or < 0 on any error. */ ++static int tpm2_get_capability_handles( ++ Tpm2Context *c, ++ TPM2_HANDLE start, ++ size_t max, ++ TPM2_HANDLE **ret_handles, ++ size_t *ret_n_handles) { ++ ++ _cleanup_free_ TPM2_HANDLE *handles = NULL; ++ size_t n_handles = 0; ++ TPM2_HANDLE current = start; ++ int r = 0; ++ ++ assert(c); ++ assert(ret_handles); ++ assert(ret_n_handles); ++ ++ while (max > 0) { ++ TPMU_CAPABILITIES capability; ++ r = tpm2_get_capability(c, TPM2_CAP_HANDLES, current, (uint32_t) max, &capability); ++ if (r < 0) ++ return r; ++ ++ TPML_HANDLE handle_list = capability.handles; ++ if (handle_list.count == 0) ++ break; ++ ++ assert(handle_list.count <= max); ++ ++ if (n_handles > SIZE_MAX - handle_list.count) ++ return log_oom(); ++ ++ if (!GREEDY_REALLOC(handles, n_handles + handle_list.count)) ++ return log_oom(); ++ ++ memcpy_safe(&handles[n_handles], handle_list.handle, sizeof(handles[0]) * handle_list.count); ++ ++ max -= handle_list.count; ++ n_handles += handle_list.count; ++ ++ /* Update current to the handle index after the last handle in the list. */ ++ current = handles[n_handles - 1] + 1; ++ ++ if (r == 0) ++ /* No more handles in this range. */ ++ break; ++ } ++ ++ *ret_handles = TAKE_PTR(handles); ++ *ret_n_handles = n_handles; ++ ++ return r; ++} ++ ++#define TPM2_HANDLE_RANGE(h) ((TPM2_HANDLE)((h) & TPM2_HR_RANGE_MASK)) ++#define TPM2_HANDLE_TYPE(h) ((TPM2_HT)(TPM2_HANDLE_RANGE(h) >> TPM2_HR_SHIFT)) ++ ++/* Returns 1 if the handle is populated in the TPM, 0 if not, and < 0 on any error. */ ++static int tpm2_get_capability_handle(Tpm2Context *c, TPM2_HANDLE handle) { ++ _cleanup_free_ TPM2_HANDLE *handles = NULL; ++ size_t n_handles = 0; ++ int r; ++ ++ r = tpm2_get_capability_handles(c, handle, 1, &handles, &n_handles); ++ if (r < 0) ++ return r; ++ ++ return n_handles == 0 ? false : handles[0] == handle; ++} ++ + /* Returns 1 if the TPM supports the parms, or 0 if the TPM does not support the parms. */ + bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARMS *parms) { + TSS2_RC rc; +@@ -454,17 +534,25 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + return 0; + } + +-static void tpm2_handle_flush(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle) { ++static void tpm2_handle_cleanup(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle, bool flush) { ++ TSS2_RC rc; ++ + if (!esys_context || esys_handle == ESYS_TR_NONE) + return; + +- TSS2_RC rc = sym_Esys_FlushContext(esys_context, esys_handle); ++ /* Closing the handle removes its reference from the esys_context, but leaves the corresponding ++ * handle in the actual TPM. Flushing the handle removes its reference from the esys_context as well ++ * as removing its corresponding handle from the actual TPM. */ ++ if (flush) ++ rc = sym_Esys_FlushContext(esys_context, esys_handle); ++ else ++ rc = sym_Esys_TR_Close(esys_context, &esys_handle); + if (rc != TSS2_RC_SUCCESS) /* We ignore failures here (besides debug logging), since this is called + * in error paths, where we cannot do anything about failures anymore. And + * when it is called in successful codepaths by this time we already did + * what we wanted to do, and got the results we wanted so there's no + * reason to make this fail more loudly than necessary. */ +- log_debug("Failed to flush TPM handle, ignoring: %s", sym_Tss2_RC_Decode(rc)); ++ log_debug("Failed to %s TPM handle, ignoring: %s", flush ? "flush" : "close", sym_Tss2_RC_Decode(rc)); + } + + Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) { +@@ -472,8 +560,8 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle) { + return NULL; + + _cleanup_(tpm2_context_unrefp) Tpm2Context *context = (Tpm2Context*)handle->tpm2_context; +- if (context && !handle->keep) +- tpm2_handle_flush(context->esys_context, handle->esys_handle); ++ if (context) ++ tpm2_handle_cleanup(context->esys_context, handle->esys_handle, handle->flush); + + return mfree(handle); + } +@@ -490,6 +578,7 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle) { + *handle = (Tpm2Handle) { + .tpm2_context = tpm2_context_ref(context), + .esys_handle = ESYS_TR_NONE, ++ .flush = true, + }; + + *ret_handle = TAKE_PTR(handle); +@@ -497,6 +586,81 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle) { + return 0; + } + ++/* Create a Tpm2Handle object that references a pre-existing handle in the TPM, at the TPM2_HANDLE address ++ * provided. This should be used only for persistent, transient, or NV handles. Returns 1 on success, 0 if ++ * the requested handle is not present in the TPM, or < 0 on error. */ ++static int tpm2_esys_handle_from_tpm_handle( ++ Tpm2Context *c, ++ const Tpm2Handle *session, ++ TPM2_HANDLE tpm_handle, ++ Tpm2Handle **ret_handle) { ++ ++ TSS2_RC rc; ++ int r; ++ ++ assert(c); ++ assert(tpm_handle > 0); ++ assert(ret_handle); ++ ++ /* Let's restrict this, at least for now, to allow only some handle types. */ ++ switch (TPM2_HANDLE_TYPE(tpm_handle)) { ++ case TPM2_HT_PERSISTENT: ++ case TPM2_HT_NV_INDEX: ++ case TPM2_HT_TRANSIENT: ++ break; ++ case TPM2_HT_PCR: ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Refusing to create ESYS handle for PCR handle 0x%08" PRIx32 ".", ++ tpm_handle); ++ case TPM2_HT_HMAC_SESSION: ++ case TPM2_HT_POLICY_SESSION: ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Refusing to create ESYS handle for session handle 0x%08" PRIx32 ".", ++ tpm_handle); ++ case TPM2_HT_PERMANENT: /* Permanent handles are defined, e.g. ESYS_TR_RH_OWNER. */ ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Refusing to create ESYS handle for permanent handle 0x%08" PRIx32 ".", ++ tpm_handle); ++ default: ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Refusing to create ESYS handle for unknown handle 0x%08" PRIx32 ".", ++ tpm_handle); ++ } ++ ++ r = tpm2_get_capability_handle(c, tpm_handle); ++ if (r < 0) ++ return r; ++ if (r == 0) { ++ log_debug("TPM handle 0x%08" PRIx32 " not populated.", tpm_handle); ++ *ret_handle = NULL; ++ return 0; ++ } ++ ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; ++ r = tpm2_handle_new(c, &handle); ++ if (r < 0) ++ return r; ++ ++ /* Since we didn't create this handle in the TPM (this is only creating an ESYS_TR handle for the ++ * pre-existing TPM handle), we shouldn't flush (or evict) it on cleanup. */ ++ handle->flush = false; ++ ++ rc = sym_Esys_TR_FromTPMPublic( ++ c->esys_context, ++ tpm_handle, ++ session ? session->esys_handle : ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ &handle->esys_handle); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to read public info: %s", sym_Tss2_RC_Decode(rc)); ++ ++ *ret_handle = TAKE_PTR(handle); ++ ++ return 1; ++} ++ + #define TPM2_CREDIT_RANDOM_FLAG_PATH "/run/systemd/tpm-rng-credited" + + static int tpm2_credit_random(Tpm2Context *c) { +@@ -660,60 +824,32 @@ const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) { + */ + static int tpm2_get_srk( + Tpm2Context *c, ++ const Tpm2Handle *session, + TPMI_ALG_PUBLIC *ret_alg, +- Tpm2Handle *ret_primary) { ++ Tpm2Handle **ret_handle) { + +- TPMI_YES_NO more_data; +- ESYS_TR primary_tr = ESYS_TR_NONE; +- _cleanup_(Esys_Freep) TPMS_CAPABILITY_DATA *cap_data = NULL; ++ int r; + + assert(c); +- assert(ret_primary); +- +- TSS2_RC rc = sym_Esys_GetCapability(c->esys_context, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- TPM2_CAP_HANDLES, +- SRK_HANDLE, +- 1, +- &more_data, +- &cap_data); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to enumerate handles searching for SRK: %s", +- sym_Tss2_RC_Decode(rc)); +- +- /* Did Not find SRK, indicate this by returning 0 */ +- if (cap_data->data.handles.count == 0 || cap_data->data.handles.handle[0] != SRK_HANDLE) { +- ret_primary->esys_handle = ESYS_TR_NONE; + ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; ++ r = tpm2_esys_handle_from_tpm_handle(c, session, SRK_HANDLE, &handle); ++ if (r < 0) ++ return r; ++ if (r == 0) { /* SRK not found */ + if (ret_alg) +- *ret_alg = 0; ++ *ret_alg = TPM2_ALG_ERROR; ++ if (ret_handle) ++ *ret_handle = NULL; + return 0; + } + +- log_debug("Found SRK on TPM."); +- +- /* convert the raw handle to an ESYS_TR */ +- TPM2_HANDLE handle = cap_data->data.handles.handle[0]; +- rc = sym_Esys_TR_FromTPMPublic(c->esys_context, +- handle, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &primary_tr); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to convert ray handle to ESYS_TR for SRK: %s", +- sym_Tss2_RC_Decode(rc)); +- + /* Get the algorithm if the caller wants it */ + _cleanup_(Esys_Freep) TPM2B_PUBLIC *out_public = NULL; + if (ret_alg) { +- rc = sym_Esys_ReadPublic( ++ TSS2_RC rc = sym_Esys_ReadPublic( + c->esys_context, +- primary_tr, ++ handle->esys_handle, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -726,7 +862,8 @@ static int tpm2_get_srk( + sym_Tss2_RC_Decode(rc)); + } + +- ret_primary->esys_handle = primary_tr; ++ if (ret_handle) ++ *ret_handle = TAKE_PTR(handle); + + if (ret_alg) + *ret_alg = out_public->publicArea.type; +@@ -759,9 +896,6 @@ static int tpm2_make_primary( + ts = now(CLOCK_MONOTONIC); + + _cleanup_(tpm2_handle_freep) Tpm2Handle *primary = NULL; +- r = tpm2_handle_new(c, &primary); +- if (r < 0) +- return r; + + /* we only need the SRK lock when making the SRK since its not atomic, transient + * primary creations don't even matter if they stomp on each other, the TPM will +@@ -776,7 +910,7 @@ static int tpm2_make_primary( + /* Find existing SRK and use it if present */ + if (use_srk_model) { + TPMI_ALG_PUBLIC got_alg = TPM2_ALG_NULL; +- r = tpm2_get_srk(c, &got_alg, primary); ++ r = tpm2_get_srk(c, NULL, &got_alg, &primary); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to establish if SRK is present"); +@@ -796,6 +930,10 @@ static int tpm2_make_primary( + log_debug("Did not find SRK, generating..."); + } + ++ r = tpm2_handle_new(c, &primary); ++ if (r < 0) ++ return r; ++ + if (IN_SET(alg, 0, TPM2_ALG_ECC)) { + primary_template = tpm2_get_primary_template(base_flags | TPM2_SRK_TEMPLATE_ECC); + +@@ -866,7 +1004,7 @@ static int tpm2_make_primary( + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to persist SRK within TPM: %s", sym_Tss2_RC_Decode(rc)); +- primary->keep = true; ++ primary->flush = false; + } + + if (ret_primary) +@@ -2846,7 +2984,7 @@ int tpm2_unseal(const char *device, + if (r < 0) + return r; + +- primary->keep = true; ++ primary->flush = false; + + log_debug("Found existing SRK key to use, deserializing ESYS_TR"); + rc = sym_Esys_TR_Deserialize( +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index a03bee148b..26d25f7ee7 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -80,7 +80,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Context*, tpm2_context_unref); + typedef struct { + Tpm2Context *tpm2_context; + ESYS_TR esys_handle; +- bool keep; ++ ++ bool flush; + } Tpm2Handle; + + #define _tpm2_handle(c, h) { .tpm2_context = (c), .esys_handle = (h), } diff --git a/SOURCES/0536-tpm2-add-tpm2_read_public.patch b/SOURCES/0536-tpm2-add-tpm2_read_public.patch new file mode 100644 index 0000000..16cd4db --- /dev/null +++ b/SOURCES/0536-tpm2-add-tpm2_read_public.patch @@ -0,0 +1,129 @@ +From 516a4e71a764f4f5e19dd8f2e19fb1a86aa0534b Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 17 May 2023 20:03:00 -0400 +Subject: [PATCH] tpm2: add tpm2_read_public() + +(cherry picked from commit 98d6a80942337f07183bc4039ce32dc188f4d4cd) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 69 +++++++++++++++++++++++++++--------------- + 1 file changed, 45 insertions(+), 24 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 3278863f4d..edd871c632 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -721,6 +721,35 @@ static int tpm2_credit_random(Tpm2Context *c) { + return 0; + } + ++static int tpm2_read_public( ++ Tpm2Context *c, ++ const Tpm2Handle *session, ++ const Tpm2Handle *handle, ++ TPM2B_PUBLIC **ret_public, ++ TPM2B_NAME **ret_name, ++ TPM2B_NAME **ret_qname) { ++ ++ TSS2_RC rc; ++ ++ assert(c); ++ assert(handle); ++ ++ rc = sym_Esys_ReadPublic( ++ c->esys_context, ++ handle->esys_handle, ++ session ? session->esys_handle : ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ret_public, ++ ret_name, ++ ret_qname); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to read public info: %s", sym_Tss2_RC_Decode(rc)); ++ ++ return 0; ++} ++ + const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) { + + /* +@@ -825,7 +854,9 @@ const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) { + static int tpm2_get_srk( + Tpm2Context *c, + const Tpm2Handle *session, +- TPMI_ALG_PUBLIC *ret_alg, ++ TPM2B_PUBLIC **ret_public, ++ TPM2B_NAME **ret_name, ++ TPM2B_NAME **ret_qname, + Tpm2Handle **ret_handle) { + + int r; +@@ -837,37 +868,26 @@ static int tpm2_get_srk( + if (r < 0) + return r; + if (r == 0) { /* SRK not found */ +- if (ret_alg) +- *ret_alg = TPM2_ALG_ERROR; ++ if (ret_public) ++ *ret_public = NULL; ++ if (ret_name) ++ *ret_name = NULL; ++ if (ret_qname) ++ *ret_qname = NULL; + if (ret_handle) + *ret_handle = NULL; + return 0; + } + +- /* Get the algorithm if the caller wants it */ +- _cleanup_(Esys_Freep) TPM2B_PUBLIC *out_public = NULL; +- if (ret_alg) { +- TSS2_RC rc = sym_Esys_ReadPublic( +- c->esys_context, +- handle->esys_handle, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &out_public, +- NULL, +- NULL); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to convert ray handle to ESYS_TR for SRK: %s", +- sym_Tss2_RC_Decode(rc)); ++ if (ret_public || ret_name || ret_qname) { ++ r = tpm2_read_public(c, session, handle, ret_public, ret_name, ret_qname); ++ if (r < 0) ++ return r; + } + + if (ret_handle) + *ret_handle = TAKE_PTR(handle); + +- if (ret_alg) +- *ret_alg = out_public->publicArea.type; +- + return 1; + } + +@@ -909,14 +929,15 @@ static int tpm2_make_primary( + + /* Find existing SRK and use it if present */ + if (use_srk_model) { +- TPMI_ALG_PUBLIC got_alg = TPM2_ALG_NULL; +- r = tpm2_get_srk(c, NULL, &got_alg, &primary); ++ _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; ++ r = tpm2_get_srk(c, NULL, &primary_public, NULL, NULL, &primary); + if (r < 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to establish if SRK is present"); + if (r == 1) { + log_debug("Discovered existing SRK"); + ++ TPMI_ALG_PUBLIC got_alg = primary_public->publicArea.type; + if (alg != 0 && alg != got_alg) + log_warning("Caller asked for specific algorithm %u, but existing SRK is %u, ignoring", + alg, got_alg); diff --git a/SOURCES/0537-tpm2-add-tpm2_get_legacy_template-and-tpm2_get_srk_t.patch b/SOURCES/0537-tpm2-add-tpm2_get_legacy_template-and-tpm2_get_srk_t.patch new file mode 100644 index 0000000..4b3dca3 --- /dev/null +++ b/SOURCES/0537-tpm2-add-tpm2_get_legacy_template-and-tpm2_get_srk_t.patch @@ -0,0 +1,525 @@ +From 66338a6cbd88d00ec0d1588de61aa0fd9bcaa658 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 17 Feb 2023 12:59:18 -0500 +Subject: [PATCH] tpm2: add tpm2_get_legacy_template() and + tpm2_get_srk_template() + +Add functions to get either the 'legacy' or standard SRK template, for RSA or +ECC. The 'legacy' templates are those used with earlier code, where a transient +key was created to use for tpm sealing; the standard SRK is the persistent +shared key as defined in TCG guidance docs. + +This also replaces tpm2_get_primary_template() with the new functions; that +function's use of flags is confusing and unnecessary. + +(cherry picked from commit f4f5b3a9de29874bcb2345196eb47ec90d02b67d) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 289 +++++++++++++++++++++++++++-------------- + src/shared/tpm2-util.h | 9 -- + src/test/test-tpm2.c | 91 ------------- + 3 files changed, 195 insertions(+), 194 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index edd871c632..fb75f105e5 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -254,6 +254,25 @@ int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg) { + return tpm2_get_capability_alg(c, alg, NULL); + } + ++/* Returns 1 if the TPM supports the ECC curve, 0 if not, or < 0 for any error. */ ++static int tpm2_supports_ecc_curve(Tpm2Context *c, TPM2_ECC_CURVE curve) { ++ TPMU_CAPABILITIES capability; ++ int r; ++ ++ /* The spec explicitly states the TPM2_ECC_CURVE should be cast to uint32_t. */ ++ r = tpm2_get_capability(c, TPM2_CAP_ECC_CURVES, (uint32_t) curve, 1, &capability); ++ if (r < 0) ++ return r; ++ ++ TPML_ECC_CURVE eccCurves = capability.eccCurves; ++ if (eccCurves.count == 0 || eccCurves.eccCurves[0] != curve) { ++ log_debug("TPM does not support ECC curve 0x%02" PRIx16 ".", curve); ++ return 0; ++ } ++ ++ return 1; ++} ++ + /* Query the TPM for populated handles. + * + * This provides an array of handle indexes populated in the TPM, starting at the requested handle. The array will +@@ -355,6 +374,13 @@ bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARM + return rc == TSS2_RC_SUCCESS; + } + ++static inline bool tpm2_supports_tpmt_public(Tpm2Context *c, const TPMT_PUBLIC *public) { ++ assert(c); ++ assert(public); ++ ++ return tpm2_test_parms(c, public->type, &public->parameters); ++} ++ + static inline bool tpm2_supports_tpmt_sym_def_object(Tpm2Context *c, const TPMT_SYM_DEF_OBJECT *parameters) { + assert(c); + assert(parameters); +@@ -750,102 +776,168 @@ static int tpm2_read_public( + return 0; + } + +-const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags) { +- +- /* +- * Set up array so flags can be used directly as an input. +- * +- * Templates for SRK come from the spec: +- * - https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf +- * +- * However, note their is some lore here. On Linux, the SRK has it's unique field set to size 0 and +- * on Windows the SRK has their unique data set to keyLen in bytes of zeros. +- */ +- assert(flags >= 0); +- assert(flags <= _TPM2_SRK_TEMPLATE_MAX); +- +- static const TPM2B_PUBLIC templ[_TPM2_SRK_TEMPLATE_MAX + 1] = { +- /* index 0 RSA old */ +- [0] = { +- .publicArea = { +- .type = TPM2_ALG_RSA, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, +- .parameters.rsaDetail = { +- .symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }, +- .scheme.scheme = TPM2_ALG_NULL, +- .keyBits = 2048, +- }, ++/* Get one of the legacy primary key templates. ++ * ++ * The legacy templates should only be used for older sealed data that did not use the SRK. Instead of a ++ * persistent SRK, a transient key was created to seal the data and then flushed; and the exact same template ++ * must be used to recreate the same transient key to unseal the data. The alg parameter must be TPM2_ALG_RSA ++ * or TPM2_ALG_ECC. This does not check if the alg is actually supported on this TPM. */ ++static int tpm2_get_legacy_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template) { ++ /* Do not modify. */ ++ static const TPMT_PUBLIC legacy_ecc = { ++ .type = TPM2_ALG_ECC, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, ++ .parameters.eccDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, + }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .curveID = TPM2_ECC_NIST_P256, ++ .kdf.scheme = TPM2_ALG_NULL, + }, +- [TPM2_SRK_TEMPLATE_ECC] = { +- .publicArea = { +- .type = TPM2_ALG_ECC, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, +- .parameters.eccDetail = { +- .symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }, +- .scheme.scheme = TPM2_ALG_NULL, +- .curveID = TPM2_ECC_NIST_P256, +- .kdf.scheme = TPM2_ALG_NULL, +- }, ++ }; ++ ++ /* Do not modify. */ ++ static const TPMT_PUBLIC legacy_rsa = { ++ .type = TPM2_ALG_RSA, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, ++ .parameters.rsaDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, + }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .keyBits = 2048, + }, +- [TPM2_SRK_TEMPLATE_NEW_STYLE] = { +- .publicArea = { +- .type = TPM2_ALG_RSA, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA, +- .parameters.rsaDetail = { +- .symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }, +- .scheme.scheme = TPM2_ALG_NULL, +- .keyBits = 2048, +- }, +- }, ++ }; ++ ++ assert(ret_template); ++ ++ if (alg == TPM2_ALG_ECC) ++ *ret_template = legacy_ecc; ++ else if (alg == TPM2_ALG_RSA) ++ *ret_template = legacy_rsa; ++ else ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "Unsupported legacy SRK alg: 0x%x", alg); ++ ++ return 0; ++} ++ ++/* Get a Storage Root Key (SRK) template. ++ * ++ * The SRK template values are recommended by the "TCG TPM v2.0 Provisioning Guidance" document in section ++ * 7.5.1 "Storage Primary Key (SRK) Templates", referencing "TCG EK Credential Profile for TPM Family 2.0". ++ * The EK Credential Profile version 2.0 provides only a single template each for RSA and ECC, while later EK ++ * Credential Profile versions provide more templates, and keep the original templates as "L-1" (for RSA) and ++ * "L-2" (for ECC). ++ * ++ * https://trustedcomputinggroup.org/resource/tcg-tpm-v2-0-provisioning-guidance ++ * https://trustedcomputinggroup.org/resource/http-trustedcomputinggroup-org-wp-content-uploads-tcg-ek-credential-profile ++ * ++ * These templates are only needed to create a new persistent SRK (or a new transient key that is ++ * SRK-compatible). Preferably, the TPM should contain a shared SRK located at the reserved shared SRK handle ++ * (see TPM2_SRK_HANDLE and tpm2_get_srk() below). ++ * ++ * The alg must be TPM2_ALG_RSA or TPM2_ALG_ECC. Returns error if the requested template is not supported on ++ * this TPM. */ ++static int tpm2_get_srk_template(Tpm2Context *c, TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template) { ++ /* The attributes are the same between ECC and RSA templates. This has the changes specified in the ++ * Provisioning Guidance document, specifically: ++ * TPMA_OBJECT_USERWITHAUTH is added. ++ * TPMA_OBJECT_ADMINWITHPOLICY is removed. ++ * TPMA_OBJECT_NODA is added. */ ++ TPMA_OBJECT srk_attributes = ++ TPMA_OBJECT_DECRYPT | ++ TPMA_OBJECT_FIXEDPARENT | ++ TPMA_OBJECT_FIXEDTPM | ++ TPMA_OBJECT_NODA | ++ TPMA_OBJECT_RESTRICTED | ++ TPMA_OBJECT_SENSITIVEDATAORIGIN | ++ TPMA_OBJECT_USERWITHAUTH; ++ ++ /* The symmetric configuration is the same between ECC and RSA templates. */ ++ TPMT_SYM_DEF_OBJECT srk_symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }; ++ ++ /* Both templates have an empty authPolicy as specified by the Provisioning Guidance document. */ ++ ++ /* From the EK Credential Profile template "L-2". */ ++ TPMT_PUBLIC srk_ecc = { ++ .type = TPM2_ALG_ECC, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = srk_attributes, ++ .parameters.eccDetail = { ++ .symmetric = srk_symmetric, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .curveID = TPM2_ECC_NIST_P256, ++ .kdf.scheme = TPM2_ALG_NULL, + }, +- [TPM2_SRK_TEMPLATE_NEW_STYLE|TPM2_SRK_TEMPLATE_ECC] = { +- .publicArea = { +- .type = TPM2_ALG_ECC, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA, +- .parameters.eccDetail = { +- .symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }, +- .scheme.scheme = TPM2_ALG_NULL, +- .curveID = TPM2_ECC_NIST_P256, +- .kdf.scheme = TPM2_ALG_NULL, +- }, +- }, ++ }; ++ ++ /* From the EK Credential Profile template "L-1". */ ++ TPMT_PUBLIC srk_rsa = { ++ .type = TPM2_ALG_RSA, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = srk_attributes, ++ .parameters.rsaDetail = { ++ .symmetric = srk_symmetric, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .keyBits = 2048, + }, + }; + +- return &templ[flags]; ++ assert(c); ++ assert(ret_template); ++ ++ if (alg == TPM2_ALG_ECC) { ++ if (!tpm2_supports_alg(c, TPM2_ALG_ECC)) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "TPM does not support ECC."); ++ ++ if (!tpm2_supports_ecc_curve(c, srk_ecc.parameters.eccDetail.curveID)) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "TPM does not support ECC-NIST-P256 curve."); ++ ++ if (!tpm2_supports_tpmt_public(c, &srk_ecc)) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "TPM does not support SRK ECC template L-2."); ++ ++ *ret_template = srk_ecc; ++ return 0; ++ } ++ ++ if (alg == TPM2_ALG_RSA) { ++ if (!tpm2_supports_alg(c, TPM2_ALG_RSA)) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "TPM does not support RSA."); ++ ++ if (!tpm2_supports_tpmt_public(c, &srk_rsa)) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "TPM does not support SRK RSA template L-1."); ++ ++ *ret_template = srk_rsa; ++ return 0; ++ } ++ ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported SRK alg: 0x%x.", alg); + } + +-/* +- * Why and what is an SRK? +- * TL;DR provides a working space for those without owner auth. The user enrolling +- * the disk may not have access to the TPMs owner hierarchy auth, so they need a +- * working space. This working space is at the defined address of 0x81000001. +- * Details can be found here: +- * - https://trustedcomputinggroup.org/wp-content/uploads/TCG-TPM-v2.0-Provisioning-Guidance-Published-v1r1.pdf +- */ +-#define SRK_HANDLE UINT32_C(0x81000001) ++/* The SRK handle is defined in the Provisioning Guidance document (see above) in the table "Reserved Handles ++ * for TPM Provisioning Fundamental Elements". The SRK is useful because it is "shared", meaning it has no ++ * authValue nor authPolicy set, and thus may be used by anyone on the system to generate derived keys or ++ * seal secrets. This is useful if the TPM has an auth (password) set for the 'owner hierarchy', which would ++ * prevent users from generating primary transient keys, unless they knew the owner hierarchy auth. See ++ * the Provisioning Guidance document for more details. */ ++#define TPM2_SRK_HANDLE UINT32_C(0x81000001) + + /* + * Retrieves the SRK handle if present. Returns 0 if SRK not present, 1 if present +@@ -864,7 +956,7 @@ static int tpm2_get_srk( + assert(c); + + _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; +- r = tpm2_esys_handle_from_tpm_handle(c, session, SRK_HANDLE, &handle); ++ r = tpm2_esys_handle_from_tpm_handle(c, session, TPM2_SRK_HANDLE, &handle); + if (r < 0) + return r; + if (r == 0) { /* SRK not found */ +@@ -900,8 +992,7 @@ static int tpm2_make_primary( + + static const TPM2B_SENSITIVE_CREATE primary_sensitive = {}; + static const TPML_PCR_SELECTION creation_pcr = {}; +- const TPM2B_PUBLIC *primary_template = NULL; +- Tpm2SRKTemplateFlags base_flags = use_srk_model ? TPM2_SRK_TEMPLATE_NEW_STYLE : 0; ++ TPM2B_PUBLIC primary_template = { .size = sizeof(TPMT_PUBLIC), }; + _cleanup_(release_lock_file) LockFile srk_lock = LOCK_FILE_INIT; + TSS2_RC rc; + usec_t ts; +@@ -956,7 +1047,12 @@ static int tpm2_make_primary( + return r; + + if (IN_SET(alg, 0, TPM2_ALG_ECC)) { +- primary_template = tpm2_get_primary_template(base_flags | TPM2_SRK_TEMPLATE_ECC); ++ if (use_srk_model) ++ r = tpm2_get_srk_template(c, TPM2_ALG_ECC, &primary_template.publicArea); ++ else ++ r = tpm2_get_legacy_template(TPM2_ALG_ECC, &primary_template.publicArea); ++ if (r < 0) ++ return r; + + rc = sym_Esys_CreatePrimary( + c->esys_context, +@@ -965,7 +1061,7 @@ static int tpm2_make_primary( + ESYS_TR_NONE, + ESYS_TR_NONE, + &primary_sensitive, +- primary_template, ++ &primary_template, + NULL, + &creation_pcr, + &primary->esys_handle, +@@ -987,7 +1083,12 @@ static int tpm2_make_primary( + } + + if (IN_SET(alg, 0, TPM2_ALG_RSA)) { +- primary_template = tpm2_get_primary_template(base_flags); ++ if (use_srk_model) ++ r = tpm2_get_srk_template(c, TPM2_ALG_RSA, &primary_template.publicArea); ++ else ++ r = tpm2_get_legacy_template(TPM2_ALG_RSA, &primary_template.publicArea); ++ if (r < 0) ++ return r; + + rc = sym_Esys_CreatePrimary( + c->esys_context, +@@ -996,7 +1097,7 @@ static int tpm2_make_primary( + ESYS_TR_NONE, + ESYS_TR_NONE, + &primary_sensitive, +- primary_template, ++ &primary_template, + NULL, + &creation_pcr, + &primary->esys_handle, +@@ -1021,7 +1122,7 @@ static int tpm2_make_primary( + + if (use_srk_model) { + rc = sym_Esys_EvictControl(c->esys_context, ESYS_TR_RH_OWNER, primary->esys_handle, +- ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, SRK_HANDLE, &primary->esys_handle); ++ ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_SRK_HANDLE, &primary->esys_handle); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to persist SRK within TPM: %s", sym_Tss2_RC_Decode(rc)); +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 26d25f7ee7..1f20aadc98 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -13,13 +13,6 @@ typedef enum TPM2Flags { + TPM2_FLAGS_USE_PIN = 1 << 0, + } TPM2Flags; + +- +-typedef enum Tpm2SRKTemplateFlags { +- TPM2_SRK_TEMPLATE_ECC = 1 << 0, +- TPM2_SRK_TEMPLATE_NEW_STYLE = 1 << 1, +- _TPM2_SRK_TEMPLATE_MAX = TPM2_SRK_TEMPLATE_NEW_STYLE|TPM2_SRK_TEMPLATE_ECC, +-} Tpm2SRKTemplateFlags; +- + /* As per https://trustedcomputinggroup.org/wp-content/uploads/TCG_PCClient_PFP_r1p05_v23_pub.pdf a + * TPM2 on a Client PC must have at least 24 PCRs. This hardcodes our expectation of 24. */ + #define TPM2_PCRS_MAX 24U +@@ -119,8 +112,6 @@ char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l); + size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l); + #define tpm2_tpml_pcr_selection_is_empty(l) (tpm2_tpml_pcr_selection_weight(l) == 0) + +-const TPM2B_PUBLIC *tpm2_get_primary_template(Tpm2SRKTemplateFlags flags); +- + #else /* HAVE_TPM2 */ + typedef struct {} Tpm2Context; + typedef struct {} Tpm2Handle; +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 75e207e9d9..af06085af6 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -410,97 +410,6 @@ TEST(tpml_pcr_selection_add_sub) { + expected2, expected2_count); + } + +- +-/* this test includes TPM2 specific data structures */ +-TEST(tpm2_get_primary_template) { +- +- /* +- * Verify that if someone changes the template code, they know they're breaking things. +- * Templates MUST be changed in a backwards compatible way. +- * +- */ +- static const TPM2B_PUBLIC templ[] = { +- /* index 0 RSA old */ +- [0] = { +- .publicArea = { +- .type = TPM2_ALG_RSA, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, +- .parameters.rsaDetail = { +- .symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }, +- .scheme.scheme = TPM2_ALG_NULL, +- .keyBits = 2048, +- }, +- }, +- }, +- /* Index 1 ECC old */ +- [TPM2_SRK_TEMPLATE_ECC] = { +- .publicArea = { +- .type = TPM2_ALG_ECC, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, +- .parameters.eccDetail = { +- .symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }, +- .scheme.scheme = TPM2_ALG_NULL, +- .curveID = TPM2_ECC_NIST_P256, +- .kdf.scheme = TPM2_ALG_NULL, +- }, +- }, +- }, +- /* index 2 RSA SRK */ +- [TPM2_SRK_TEMPLATE_NEW_STYLE] = { +- .publicArea = { +- .type = TPM2_ALG_RSA, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA, +- .parameters.rsaDetail = { +- .symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }, +- .scheme.scheme = TPM2_ALG_NULL, +- .keyBits = 2048, +- }, +- }, +- }, +- /* Index 3 ECC SRK */ +- [TPM2_SRK_TEMPLATE_NEW_STYLE | TPM2_SRK_TEMPLATE_ECC] = { +- .publicArea = { +- .type = TPM2_ALG_ECC, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_USERWITHAUTH|TPMA_OBJECT_NODA, +- .parameters.eccDetail = { +- .symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }, +- .scheme.scheme = TPM2_ALG_NULL, +- .curveID = TPM2_ECC_NIST_P256, +- .kdf.scheme = TPM2_ALG_NULL, +- }, +- }, +- }, +- }; +- +- assert_cc(ELEMENTSOF(templ) == _TPM2_SRK_TEMPLATE_MAX + 1); +- +- for (size_t i = 0; i < ELEMENTSOF(templ); i++) { +- /* the index counter lines up with the flags and the expected template received */ +- const TPM2B_PUBLIC *got = tpm2_get_primary_template((Tpm2SRKTemplateFlags)i); +- assert_se(memcmp(&templ[i], got, sizeof(*got)) == 0); +- } +-} +- + static bool digest_check(const TPM2B_DIGEST *digest, const char *expect) { + _cleanup_free_ char *h = NULL; + diff --git a/SOURCES/0538-tpm2-add-tpm2_load.patch b/SOURCES/0538-tpm2-add-tpm2_load.patch new file mode 100644 index 0000000..7bf4765 --- /dev/null +++ b/SOURCES/0538-tpm2-add-tpm2_load.patch @@ -0,0 +1,105 @@ +From 82561d585aa6d081fa5f9810f97664526726408e Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 19 Dec 2022 08:26:32 -0500 +Subject: [PATCH] tpm2: add tpm2_load() + +This function allows loading an object (e.g. a sealed secret) or key into the +TPM. + +(cherry picked from commit d1d0de735da52a7cf5aa5638b07d5fdf4e8b23f2) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 69 +++++++++++++++++++++++++++--------------- + 1 file changed, 45 insertions(+), 24 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index fb75f105e5..6eb37a87aa 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1484,6 +1484,50 @@ static int tpm2_get_policy_digest( + return 0; + } + ++static int tpm2_load( ++ Tpm2Context *c, ++ const Tpm2Handle *parent, ++ const Tpm2Handle *session, ++ const TPM2B_PUBLIC *public, ++ const TPM2B_PRIVATE *private, ++ Tpm2Handle **ret_handle) { ++ ++ TSS2_RC rc; ++ int r; ++ ++ assert(c); ++ assert(public); ++ assert(private); ++ assert(ret_handle); ++ ++ log_debug("Loading object into TPM."); ++ ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; ++ r = tpm2_handle_new(c, &handle); ++ if (r < 0) ++ return r; ++ ++ rc = sym_Esys_Load( ++ c->esys_context, ++ parent ? parent->esys_handle : ESYS_TR_RH_OWNER, ++ session ? session->esys_handle : ESYS_TR_PASSWORD, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ private, ++ public, ++ &handle->esys_handle); ++ if (rc == TPM2_RC_LOCKOUT) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOLCK), ++ "TPM2 device is in dictionary attack lockout mode."); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to load key into TPM: %s", sym_Tss2_RC_Decode(rc)); ++ ++ *ret_handle = TAKE_PTR(handle); ++ ++ return 0; ++} ++ + static int tpm2_pcr_read( + Tpm2Context *c, + const TPML_PCR_SELECTION *pcr_selection, +@@ -3133,33 +3177,10 @@ int tpm2_unseal(const char *device, + * provides protections. + */ + _cleanup_(tpm2_handle_freep) Tpm2Handle *hmac_key = NULL; +- r = tpm2_handle_new(c, &hmac_key); ++ r = tpm2_load(c, primary, NULL, &public, &private, &hmac_key); + if (r < 0) + return r; + +- rc = sym_Esys_Load( +- c->esys_context, +- primary->esys_handle, +- ESYS_TR_PASSWORD, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &private, +- &public, +- &hmac_key->esys_handle); +- if (rc != TSS2_RC_SUCCESS) { +- /* If we're in dictionary attack lockout mode, we should see a lockout error here, which we +- * need to translate for the caller. */ +- if (rc == TPM2_RC_LOCKOUT) +- return log_error_errno( +- SYNTHETIC_ERRNO(ENOLCK), +- "TPM2 device is in dictionary attack lockout mode."); +- else +- return log_error_errno( +- SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to load HMAC key in TPM: %s", +- sym_Tss2_RC_Decode(rc)); +- } +- + TPM2B_PUBLIC pubkey_tpm2, *authorize_key = NULL; + _cleanup_free_ void *fp = NULL; + size_t fp_size = 0; diff --git a/SOURCES/0539-tpm2-add-tpm2_load_external.patch b/SOURCES/0539-tpm2-add-tpm2_load_external.patch new file mode 100644 index 0000000..a7ed998 --- /dev/null +++ b/SOURCES/0539-tpm2-add-tpm2_load_external.patch @@ -0,0 +1,102 @@ +From 7ef2fc501dfd3a989c986839b928ba967c57dca3 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 19 Dec 2022 08:26:32 -0500 +Subject: [PATCH] tpm2: add tpm2_load_external() + +This allows loading an external object/key (e.g. an openssl public key) into +the TPM. + +(cherry picked from commit efe153bdc2e57c0d0f9bc47a4010fc82743764e7) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 66 ++++++++++++++++++++++++++++-------------- + 1 file changed, 45 insertions(+), 21 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 6eb37a87aa..277cfa1e8e 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1528,6 +1528,50 @@ static int tpm2_load( + return 0; + } + ++static int tpm2_load_external( ++ Tpm2Context *c, ++ const Tpm2Handle *session, ++ const TPM2B_PUBLIC *public, ++ const TPM2B_SENSITIVE *private, ++ Tpm2Handle **ret_handle) { ++ ++ TSS2_RC rc; ++ int r; ++ ++ assert(c); ++ assert(ret_handle); ++ ++ log_debug("Loading external key into TPM."); ++ ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; ++ r = tpm2_handle_new(c, &handle); ++ if (r < 0) ++ return r; ++ ++ rc = sym_Esys_LoadExternal( ++ c->esys_context, ++ session ? session->esys_handle : ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ private, ++ public, ++#if HAVE_TSS2_ESYS3 ++ /* tpm2-tss >= 3.0.0 requires a ESYS_TR_RH_* constant specifying the requested ++ * hierarchy, older versions need TPM2_RH_* instead. */ ++ ESYS_TR_RH_OWNER, ++#else ++ TPM2_RH_OWNER, ++#endif ++ &handle->esys_handle); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to load public key into TPM: %s", sym_Tss2_RC_Decode(rc)); ++ ++ *ret_handle = TAKE_PTR(handle); ++ ++ return 0; ++} ++ + static int tpm2_pcr_read( + Tpm2Context *c, + const TPML_PCR_SELECTION *pcr_selection, +@@ -2616,30 +2660,10 @@ static int tpm2_policy_authorize( + log_debug("Adding PCR signature policy."); + + _cleanup_(tpm2_handle_freep) Tpm2Handle *pubkey_handle = NULL; +- r = tpm2_handle_new(c, &pubkey_handle); ++ r = tpm2_load_external(c, NULL, public, NULL, &pubkey_handle); + if (r < 0) + return r; + +- /* Load the key into the TPM */ +- rc = sym_Esys_LoadExternal( +- c->esys_context, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- NULL, +- public, +-#if HAVE_TSS2_ESYS3 +- /* tpm2-tss >= 3.0.0 requires a ESYS_TR_RH_* constant specifying the requested +- * hierarchy, older versions need TPM2_RH_* instead. */ +- ESYS_TR_RH_OWNER, +-#else +- TPM2_RH_OWNER, +-#endif +- &pubkey_handle->esys_handle); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to load public key into TPM: %s", sym_Tss2_RC_Decode(rc)); +- + /* Acquire the "name" of what we just loaded */ + _cleanup_(Esys_Freep) TPM2B_NAME *pubkey_name = NULL; + r = tpm2_get_name(c, pubkey_handle, &pubkey_name); diff --git a/SOURCES/0540-tpm2-move-local-vars-in-tpm2_seal-to-point-of-use.patch b/SOURCES/0540-tpm2-move-local-vars-in-tpm2_seal-to-point-of-use.patch new file mode 100644 index 0000000..f6d5123 --- /dev/null +++ b/SOURCES/0540-tpm2-move-local-vars-in-tpm2_seal-to-point-of-use.patch @@ -0,0 +1,106 @@ +From 74b814947793983e56fbedcd8b3044924a73a072 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 12 Dec 2022 09:46:04 -0500 +Subject: [PATCH] tpm2: move local vars in tpm2_seal() to point of use + +No functional change; cosmetic only. + +(cherry picked from commit ee6a8713abbe185f7c8aaedbbc06cc27eefe9072) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 28 +++++++++++++--------------- + 1 file changed, 13 insertions(+), 15 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 277cfa1e8e..12a6036b1d 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -2865,17 +2865,7 @@ int tpm2_seal(const char *device, + void **ret_srk_buf, + size_t *ret_srk_buf_size) { + +- _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; +- _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; +- _cleanup_(Esys_Freep) uint8_t *srk_buf = NULL; +- static const TPML_PCR_SELECTION creation_pcr = {}; +- _cleanup_(erase_and_freep) void *secret = NULL; +- _cleanup_free_ void *hash = NULL; +- TPM2B_SENSITIVE_CREATE hmac_sensitive; +- TPM2B_PUBLIC hmac_template; +- usec_t start; + TSS2_RC rc; +- size_t srk_buf_size; + int r; + + assert(pubkey || pubkey_size == 0); +@@ -2907,9 +2897,7 @@ int tpm2_seal(const char *device, + * is stored in the LUKS2 JSON only in encrypted form with the "primary" key of the TPM2 chip, thus + * binding the unlocking to the TPM2 chip. */ + +- start = now(CLOCK_MONOTONIC); +- +- CLEANUP_ERASE(hmac_sensitive); ++ usec_t start = now(CLOCK_MONOTONIC); + + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new(device, &c); +@@ -2962,7 +2950,7 @@ int tpm2_seal(const char *device, + /* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the + * LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it + * because it's a key type that is universally supported and suitable for symmetric binary blobs. */ +- hmac_template = (TPM2B_PUBLIC) { ++ TPM2B_PUBLIC hmac_template = { + .size = sizeof(TPMT_PUBLIC), + .publicArea = { + .type = TPM2_ALG_KEYEDHASH, +@@ -2974,10 +2962,13 @@ int tpm2_seal(const char *device, + }, + }; + +- hmac_sensitive = (TPM2B_SENSITIVE_CREATE) { ++ TPM2B_SENSITIVE_CREATE hmac_sensitive = { + .size = sizeof(hmac_sensitive.sensitive), + .sensitive.data.size = 32, + }; ++ ++ CLEANUP_ERASE(hmac_sensitive); ++ + if (pin) { + r = tpm2_digest_buffer(TPM2_ALG_SHA256, &hmac_sensitive.sensitive.userAuth, pin, strlen(pin), /* extend= */ false); + if (r < 0) +@@ -3007,6 +2998,9 @@ int tpm2_seal(const char *device, + + log_debug("Creating HMAC key."); + ++ static const TPML_PCR_SELECTION creation_pcr = {}; ++ _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; ++ _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; + rc = sym_Esys_Create( + c->esys_context, + primary_handle->esys_handle, +@@ -3026,6 +3020,7 @@ int tpm2_seal(const char *device, + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to generate HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); + ++ _cleanup_(erase_and_freep) void *secret = NULL; + secret = memdup(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size); + if (!secret) + return log_oom(); +@@ -3049,6 +3044,7 @@ int tpm2_seal(const char *device, + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal public key: %s", sym_Tss2_RC_Decode(rc)); + ++ _cleanup_free_ void *hash = NULL; + hash = memdup(policy_digest.buffer, policy_digest.size); + if (!hash) + return log_oom(); +@@ -3057,6 +3053,8 @@ int tpm2_seal(const char *device, + * the raw TPM handle as well as the object name. The object name is used to verify that + * the key we use later is the key we expect to establish the session with. + */ ++ _cleanup_(Esys_Freep) uint8_t *srk_buf = NULL; ++ size_t srk_buf_size = 0; + if (ret_srk_buf) { + log_debug("Serializing SRK ESYS_TR reference"); + rc = sym_Esys_TR_Serialize(c->esys_context, primary_handle->esys_handle, &srk_buf, &srk_buf_size); diff --git a/SOURCES/0541-tpm2-replace-magic-number-in-hmac_sensitive-initiali.patch b/SOURCES/0541-tpm2-replace-magic-number-in-hmac_sensitive-initiali.patch new file mode 100644 index 0000000..e13b64e --- /dev/null +++ b/SOURCES/0541-tpm2-replace-magic-number-in-hmac_sensitive-initiali.patch @@ -0,0 +1,28 @@ +From 66e049a96b49cfa9b2be4d4c1fc39be6bdf68867 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 8 Jun 2023 14:06:46 -0400 +Subject: [PATCH] tpm2: replace magic number in hmac_sensitive initialization + +Instead of setting hmac_sensitive.sensitive.data.size to '32' use the actual +hash size as set in the hmac_template. + +(cherry picked from commit 180444b8851a8654771361b1494b5db286d8724e) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 12a6036b1d..f7940bcf2e 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -2964,7 +2964,7 @@ int tpm2_seal(const char *device, + + TPM2B_SENSITIVE_CREATE hmac_sensitive = { + .size = sizeof(hmac_sensitive.sensitive), +- .sensitive.data.size = 32, ++ .sensitive.data.size = hmac_template.publicArea.unique.keyedHash.size, + }; + + CLEANUP_ERASE(hmac_sensitive); diff --git a/SOURCES/0542-tpm2-add-tpm2_create.patch b/SOURCES/0542-tpm2-add-tpm2_create.patch new file mode 100644 index 0000000..c7b4e72 --- /dev/null +++ b/SOURCES/0542-tpm2-add-tpm2_create.patch @@ -0,0 +1,193 @@ +From 4d7527b3da486e260cb6fa94a03d9d3e58584ab8 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 12 Dec 2022 09:46:04 -0500 +Subject: [PATCH] tpm2: add tpm2_create() + +This allows creating a new object (e.g. sealed secret) or key using the TPM. + +Note that the new object/key is not loaded in the TPM after creation. + +(cherry picked from commit e3f1f210761de31d262cb701335f4da194ca4ec7) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 125 ++++++++++++++++++++++++++++------------- + 1 file changed, 86 insertions(+), 39 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index f7940bcf2e..4759430cb2 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1484,6 +1484,75 @@ static int tpm2_get_policy_digest( + return 0; + } + ++static int tpm2_create( ++ Tpm2Context *c, ++ const Tpm2Handle *parent, ++ const Tpm2Handle *session, ++ const TPMT_PUBLIC *template, ++ const TPMS_SENSITIVE_CREATE *sensitive, ++ TPM2B_PUBLIC **ret_public, ++ TPM2B_PRIVATE **ret_private) { ++ ++ usec_t ts; ++ TSS2_RC rc; ++ ++ assert(c); ++ assert(template); ++ ++ log_debug("Creating object on TPM."); ++ ++ ts = now(CLOCK_MONOTONIC); ++ ++ TPM2B_PUBLIC tpm2b_public = { ++ .size = sizeof(*template) - sizeof(template->unique), ++ .publicArea = *template, ++ }; ++ ++ /* Zero the unique area. */ ++ zero(tpm2b_public.publicArea.unique); ++ ++ TPM2B_SENSITIVE_CREATE tpm2b_sensitive; ++ if (sensitive) ++ tpm2b_sensitive = (TPM2B_SENSITIVE_CREATE) { ++ .size = sizeof(*sensitive), ++ .sensitive = *sensitive, ++ }; ++ else ++ tpm2b_sensitive = (TPM2B_SENSITIVE_CREATE) {}; ++ ++ _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; ++ _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; ++ rc = sym_Esys_Create( ++ c->esys_context, ++ parent ? parent->esys_handle : ESYS_TR_RH_OWNER, ++ session ? session->esys_handle : ESYS_TR_PASSWORD, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ &tpm2b_sensitive, ++ &tpm2b_public, ++ /* outsideInfo= */ NULL, ++ &(TPML_PCR_SELECTION) {}, ++ &private, ++ &public, ++ /* creationData= */ NULL, ++ /* creationHash= */ NULL, ++ /* creationTicket= */ NULL); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to generate object in TPM: %s", ++ sym_Tss2_RC_Decode(rc)); ++ ++ log_debug("Successfully created object on TPM in %s.", ++ FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC)); ++ ++ if (ret_public) ++ *ret_public = TAKE_PTR(public); ++ if (ret_private) ++ *ret_private = TAKE_PTR(private); ++ ++ return 0; ++} ++ + static int tpm2_load( + Tpm2Context *c, + const Tpm2Handle *parent, +@@ -2950,38 +3019,34 @@ int tpm2_seal(const char *device, + /* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the + * LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it + * because it's a key type that is universally supported and suitable for symmetric binary blobs. */ +- TPM2B_PUBLIC hmac_template = { +- .size = sizeof(TPMT_PUBLIC), +- .publicArea = { +- .type = TPM2_ALG_KEYEDHASH, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT, +- .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL, +- .unique.keyedHash.size = SHA256_DIGEST_SIZE, +- .authPolicy = policy_digest, +- }, ++ TPMT_PUBLIC hmac_template = { ++ .type = TPM2_ALG_KEYEDHASH, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT, ++ .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL, ++ .unique.keyedHash.size = SHA256_DIGEST_SIZE, ++ .authPolicy = policy_digest, + }; + +- TPM2B_SENSITIVE_CREATE hmac_sensitive = { +- .size = sizeof(hmac_sensitive.sensitive), +- .sensitive.data.size = hmac_template.publicArea.unique.keyedHash.size, ++ TPMS_SENSITIVE_CREATE hmac_sensitive = { ++ .data.size = hmac_template.unique.keyedHash.size, + }; + + CLEANUP_ERASE(hmac_sensitive); + + if (pin) { +- r = tpm2_digest_buffer(TPM2_ALG_SHA256, &hmac_sensitive.sensitive.userAuth, pin, strlen(pin), /* extend= */ false); ++ r = tpm2_digest_buffer(TPM2_ALG_SHA256, &hmac_sensitive.userAuth, pin, strlen(pin), /* extend= */ false); + if (r < 0) + return r; + } + +- assert(sizeof(hmac_sensitive.sensitive.data.buffer) >= hmac_sensitive.sensitive.data.size); ++ assert(sizeof(hmac_sensitive.data.buffer) >= hmac_sensitive.data.size); + + (void) tpm2_credit_random(c); + + log_debug("Generating secret key data."); + +- r = crypto_random_bytes(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size); ++ r = crypto_random_bytes(hmac_sensitive.data.buffer, hmac_sensitive.data.size); + if (r < 0) + return log_error_errno(r, "Failed to generate secret key: %m"); + +@@ -2996,32 +3061,14 @@ int tpm2_seal(const char *device, + if (r < 0) + return r; + +- log_debug("Creating HMAC key."); +- +- static const TPML_PCR_SELECTION creation_pcr = {}; + _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; + _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; +- rc = sym_Esys_Create( +- c->esys_context, +- primary_handle->esys_handle, +- encryption_session->esys_handle, /* use HMAC session to enable parameter encryption */ +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &hmac_sensitive, +- &hmac_template, +- NULL, +- &creation_pcr, +- &private, +- &public, +- NULL, +- NULL, +- NULL); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to generate HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); ++ r = tpm2_create(c, primary_handle, encryption_session, &hmac_template, &hmac_sensitive, &public, &private); ++ if (r < 0) ++ return r; + + _cleanup_(erase_and_freep) void *secret = NULL; +- secret = memdup(hmac_sensitive.sensitive.data.buffer, hmac_sensitive.sensitive.data.size); ++ secret = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size); + if (!secret) + return log_oom(); + +@@ -3081,7 +3128,7 @@ int tpm2_seal(const char *device, + } + + *ret_secret = TAKE_PTR(secret); +- *ret_secret_size = hmac_sensitive.sensitive.data.size; ++ *ret_secret_size = hmac_sensitive.data.size; + *ret_blob = TAKE_PTR(blob); + *ret_blob_size = blob_size; + *ret_pcr_hash = TAKE_PTR(hash); diff --git a/SOURCES/0543-tpm2-replace-tpm2_capability_pcrs-macro-with-direct-.patch b/SOURCES/0543-tpm2-replace-tpm2_capability_pcrs-macro-with-direct-.patch new file mode 100644 index 0000000..c5918dc --- /dev/null +++ b/SOURCES/0543-tpm2-replace-tpm2_capability_pcrs-macro-with-direct-.patch @@ -0,0 +1,60 @@ +From 0bbab32b4f969a62fcc1fc5495017fc49cd30d33 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 8 Jun 2023 13:41:33 -0400 +Subject: [PATCH] tpm2: replace tpm2_capability_pcrs() macro with direct + c->capaiblity_pcrs use + +(cherry picked from commit 9ea0ffe61264a107b3a1bcb13bef225c85c9239f) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 10 ++-------- + 1 file changed, 2 insertions(+), 8 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 4759430cb2..cbdae73759 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -220,8 +220,6 @@ static int tpm2_cache_capabilities(Tpm2Context *c) { + return 0; + } + +-#define tpm2_capability_pcrs(c) ((c)->capability_pcrs) +- + /* Get the TPMA_ALGORITHM for a TPM2_ALG_ID. + * + * Returns 1 if the TPM supports the algorithm and the TPMA_ALGORITHM is provided, or 0 if the TPM does not +@@ -1791,15 +1789,13 @@ static int tpm2_get_best_pcr_bank( + uint32_t pcr_mask, + TPMI_ALG_HASH *ret) { + +- TPML_PCR_SELECTION pcrs; + TPMI_ALG_HASH supported_hash = 0, hash_with_valid_pcr = 0; + int r; + + assert(c); + assert(ret); + +- pcrs = tpm2_capability_pcrs(c); +- FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection, &pcrs) { ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection, &c->capability_pcrs) { + TPMI_ALG_HASH hash = selection->hash; + int good; + +@@ -1874,15 +1870,13 @@ int tpm2_get_good_pcr_banks( + TPMI_ALG_HASH **ret) { + + _cleanup_free_ TPMI_ALG_HASH *good_banks = NULL, *fallback_banks = NULL; +- TPML_PCR_SELECTION pcrs; + size_t n_good_banks = 0, n_fallback_banks = 0; + int r; + + assert(c); + assert(ret); + +- pcrs = tpm2_capability_pcrs(c); +- FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection, &pcrs) { ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection, &c->capability_pcrs) { + TPMI_ALG_HASH hash = selection->hash; + + /* Let's see if this bank is superficially OK, i.e. has at least 24 enabled registers */ diff --git a/SOURCES/0544-basic-alloc-util-add-greedy_realloc_append.patch b/SOURCES/0544-basic-alloc-util-add-greedy_realloc_append.patch new file mode 100644 index 0000000..23d9e4b --- /dev/null +++ b/SOURCES/0544-basic-alloc-util-add-greedy_realloc_append.patch @@ -0,0 +1,135 @@ +From 774d759c39336b9650be285a88729cbfb791fce9 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 17 Feb 2023 12:59:18 -0500 +Subject: [PATCH] basic/alloc-util: add greedy_realloc_append() + +Add function to perform greedy realloc as well as copying the new data into the +newly allocated space. + +(cherry picked from commit 3f27ba99542385174a1bc40beb737a8622790912) + +Related: RHEL-16182 +--- + src/basic/alloc-util.c | 27 ++++++++++++++++++++++++ + src/basic/alloc-util.h | 4 ++++ + src/test/test-alloc-util.c | 43 ++++++++++++++++++++++++++++++++++++-- + 3 files changed, 72 insertions(+), 2 deletions(-) + +diff --git a/src/basic/alloc-util.c b/src/basic/alloc-util.c +index b030f454b2..e566350ba2 100644 +--- a/src/basic/alloc-util.c ++++ b/src/basic/alloc-util.c +@@ -102,3 +102,30 @@ void* greedy_realloc0( + + return q; + } ++ ++void* greedy_realloc_append( ++ void **p, ++ size_t *n_p, ++ const void *from, ++ size_t n_from, ++ size_t size) { ++ ++ uint8_t *q; ++ ++ assert(p); ++ assert(n_p); ++ assert(from || n_from == 0); ++ ++ if (n_from > SIZE_MAX - *n_p) ++ return NULL; ++ ++ q = greedy_realloc(p, *n_p + n_from, size); ++ if (!q) ++ return NULL; ++ ++ memcpy_safe(q + *n_p * size, from, n_from * size); ++ ++ *n_p += n_from; ++ ++ return q; ++} +diff --git a/src/basic/alloc-util.h b/src/basic/alloc-util.h +index e4c8b71a2b..28d0cf5ea9 100644 +--- a/src/basic/alloc-util.h ++++ b/src/basic/alloc-util.h +@@ -146,6 +146,7 @@ static inline void *memdup_suffix0_multiply(const void *p, size_t size, size_t n + + void* greedy_realloc(void **p, size_t need, size_t size); + void* greedy_realloc0(void **p, size_t need, size_t size); ++void* greedy_realloc_append(void **p, size_t *n_p, const void *from, size_t n_from, size_t size); + + #define GREEDY_REALLOC(array, need) \ + greedy_realloc((void**) &(array), (need), sizeof((array)[0])) +@@ -153,6 +154,9 @@ void* greedy_realloc0(void **p, size_t need, size_t size); + #define GREEDY_REALLOC0(array, need) \ + greedy_realloc0((void**) &(array), (need), sizeof((array)[0])) + ++#define GREEDY_REALLOC_APPEND(array, n_array, from, n_from) \ ++ greedy_realloc_append((void**) &(array), (size_t*) &(n_array), (from), (n_from), sizeof((array)[0])) ++ + #define alloca0(n) \ + ({ \ + char *_new_; \ +diff --git a/src/test/test-alloc-util.c b/src/test/test-alloc-util.c +index df6139005f..57cb886c41 100644 +--- a/src/test/test-alloc-util.c ++++ b/src/test/test-alloc-util.c +@@ -23,8 +23,8 @@ TEST(alloca) { + } + + TEST(GREEDY_REALLOC) { +- _cleanup_free_ int *a = NULL, *b = NULL; +- size_t i, j; ++ _cleanup_free_ int *a = NULL, *b = NULL, *c = NULL; ++ size_t i, j, n_c = 0; + + /* Give valgrind a chance to verify our realloc() operations */ + +@@ -53,6 +53,45 @@ TEST(GREEDY_REALLOC) { + + for (j = 30; j < i / 2; j += 7) + assert_se(b[j] == (int) j); ++ ++ size_t n_from = 10; ++ int from[n_from]; ++ for (i = 0; i < 2048; i++) { ++ for (j = 0; j < n_from; j++) ++ from[j] = n_from * i + j; ++ ++ _cleanup_free_ int *before = NULL; ++ size_t n_before = 0; ++ assert_se(GREEDY_REALLOC_APPEND(before, n_before, c, n_c)); ++ assert_se(before); ++ assert_se(n_before == n_c); ++ assert_se(memcmp_safe(c, before, n_c) == 0); ++ ++ assert_se(GREEDY_REALLOC_APPEND(c, n_c, from, n_from)); ++ assert_se(n_c == n_before + n_from); ++ assert_se(MALLOC_ELEMENTSOF(c) >= n_c); ++ assert_se(MALLOC_SIZEOF_SAFE(c) >= n_c * sizeof(int)); ++ assert_se(memcmp_safe(c, before, n_before) == 0); ++ assert_se(memcmp_safe(&c[n_before], from, n_from) == 0); ++ ++ before = mfree(before); ++ assert_se(!before); ++ n_before = 0; ++ assert_se(GREEDY_REALLOC_APPEND(before, n_before, c, n_c)); ++ assert_se(before); ++ assert_se(n_before == n_c); ++ assert_se(memcmp_safe(c, before, n_c) == 0); ++ ++ assert_se(GREEDY_REALLOC_APPEND(c, n_c, NULL, 0)); ++ assert_se(c); ++ assert_se(n_c == n_before); ++ assert_se(MALLOC_ELEMENTSOF(c) >= n_c); ++ assert_se(MALLOC_SIZEOF_SAFE(c) >= n_c * sizeof(int)); ++ assert_se(memcmp_safe(c, before, n_c) == 0); ++ } ++ ++ for (j = 0; j < i * n_from; j++) ++ assert_se(c[j] == (int) j); + } + + TEST(memdup_multiply_and_greedy_realloc) { diff --git a/SOURCES/0545-tpm2-cache-the-TPM-supported-commands-add-tpm2_suppo.patch b/SOURCES/0545-tpm2-cache-the-TPM-supported-commands-add-tpm2_suppo.patch new file mode 100644 index 0000000..214db59 --- /dev/null +++ b/SOURCES/0545-tpm2-cache-the-TPM-supported-commands-add-tpm2_suppo.patch @@ -0,0 +1,150 @@ +From b5409900792af67d30b80d8088a853c01fdfdc5f Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 17 Feb 2023 12:59:18 -0500 +Subject: [PATCH] tpm2: cache the TPM supported commands, add + tpm2_supports_command() + +Cache the TPM's supported commands and provide a function to check if a command +is supported. + +(cherry picked from commit adbf0c8cfb5d8635133ce9e2be088f9489b54694) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 60 ++++++++++++++++++++++++++++++++++++++++++ + src/shared/tpm2-util.h | 3 +++ + src/test/test-tpm2.c | 9 +++++++ + 3 files changed, 72 insertions(+) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index cbdae73759..d38e260f9a 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -191,12 +191,47 @@ static int tpm2_get_capability( + return more == TPM2_YES; + } + ++#define TPMA_CC_TO_TPM2_CC(cca) (((cca) & TPMA_CC_COMMANDINDEX_MASK) >> TPMA_CC_COMMANDINDEX_SHIFT) ++ + static int tpm2_cache_capabilities(Tpm2Context *c) { + TPMU_CAPABILITIES capability; + int r; + + assert(c); + ++ /* Cache the command capabilities. The spec isn't actually clear if commands can be added/removed ++ * while running, but that would be crazy, so let's hope it is not possbile. */ ++ TPM2_CC current_cc = TPM2_CC_FIRST; ++ for (;;) { ++ r = tpm2_get_capability( ++ c, ++ TPM2_CAP_COMMANDS, ++ current_cc, ++ TPM2_MAX_CAP_CC, ++ &capability); ++ if (r < 0) ++ return r; ++ ++ TPML_CCA commands = capability.command; ++ ++ /* We should never get 0; the TPM must support some commands, and it must not set 'more' if ++ * there are no more. */ ++ assert(commands.count > 0); ++ ++ if (!GREEDY_REALLOC_APPEND( ++ c->capability_commands, ++ c->n_capability_commands, ++ commands.commandAttributes, ++ commands.count)) ++ return log_oom(); ++ ++ if (r == 0) ++ break; ++ ++ /* Set current_cc to index after last cc the TPM provided */ ++ current_cc = TPMA_CC_TO_TPM2_CC(commands.commandAttributes[commands.count - 1]) + 1; ++ } ++ + /* Cache the PCR capabilities, which are safe to cache, as the only way they can change is + * TPM2_PCR_Allocate(), which changes the allocation after the next _TPM_Init(). If the TPM is + * reinitialized while we are using it, all our context and sessions will be invalid, so we can +@@ -252,6 +287,29 @@ int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg) { + return tpm2_get_capability_alg(c, alg, NULL); + } + ++/* Get the TPMA_CC for a TPM2_CC. Returns true if the TPM supports the command and the TPMA_CC is provided, ++ * otherwise false. */ ++static bool tpm2_get_capability_command(Tpm2Context *c, TPM2_CC command, TPMA_CC *ret) { ++ assert(c); ++ ++ FOREACH_ARRAY(cca, c->capability_commands, c->n_capability_commands) ++ if (TPMA_CC_TO_TPM2_CC(*cca) == command) { ++ if (ret) ++ *ret = *cca; ++ return true; ++ } ++ ++ log_debug("TPM does not support command 0x%04" PRIx32 ".", command); ++ if (ret) ++ *ret = 0; ++ ++ return false; ++} ++ ++bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command) { ++ return tpm2_get_capability_command(c, command, NULL); ++} ++ + /* Returns 1 if the TPM supports the ECC curve, 0 if not, or < 0 for any error. */ + static int tpm2_supports_ecc_curve(Tpm2Context *c, TPM2_ECC_CURVE curve) { + TPMU_CAPABILITIES capability; +@@ -415,6 +473,8 @@ static Tpm2Context *tpm2_context_free(Tpm2Context *c) { + c->tcti_context = mfree(c->tcti_context); + c->tcti_dl = safe_dlclose(c->tcti_dl); + ++ c->capability_commands = mfree(c->capability_commands); ++ + return mfree(c); + } + +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 1f20aadc98..1ca1a2e503 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -62,6 +62,8 @@ typedef struct { + ESYS_CONTEXT *esys_context; + + /* Some selected cached capabilities of the TPM */ ++ TPMA_CC *capability_commands; ++ size_t n_capability_commands; + TPML_PCR_SELECTION capability_pcrs; + } Tpm2Context; + +@@ -85,6 +87,7 @@ Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle); + DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free); + + int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg); ++bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command); + + bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARMS *parms); + +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index af06085af6..dfc8b98e08 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -658,6 +658,15 @@ TEST(tpm_required_tests) { + assert_se(tpm2_supports_alg(c, TPM2_ALG_RSA) == 1); + assert_se(tpm2_supports_alg(c, TPM2_ALG_AES) == 1); + assert_se(tpm2_supports_alg(c, TPM2_ALG_CFB) == 1); ++ ++ /* Test invalid commands */ ++ assert_se(!tpm2_supports_command(c, TPM2_CC_FIRST - 1)); ++ assert_se(!tpm2_supports_command(c, TPM2_CC_LAST + 1)); ++ ++ /* Test valid commands */ ++ assert_se(tpm2_supports_command(c, TPM2_CC_Create)); ++ assert_se(tpm2_supports_command(c, TPM2_CC_CreatePrimary)); ++ assert_se(tpm2_supports_command(c, TPM2_CC_Unseal)); + } + + #endif /* HAVE_TPM2 */ diff --git a/SOURCES/0546-tpm2-cache-TPM-algorithms.patch b/SOURCES/0546-tpm2-cache-TPM-algorithms.patch new file mode 100644 index 0000000..b6c10c0 --- /dev/null +++ b/SOURCES/0546-tpm2-cache-TPM-algorithms.patch @@ -0,0 +1,186 @@ +From 3fb5fadcb2c26e9e7da5de8f8bda0fa0e987c443 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 14 Jun 2023 13:17:21 -0400 +Subject: [PATCH] tpm2: cache TPM algorithms + +Cache the supported algorithms when creating a new context. + +(cherry picked from commit cbc92a3172609238db572b86fa7da5e543e6a4dd) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 84 +++++++++++++++++++++++++++--------------- + src/shared/tpm2-util.h | 4 +- + src/test/test-tpm2.c | 10 ++--- + 3 files changed, 62 insertions(+), 36 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index d38e260f9a..c05c636745 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -199,6 +199,44 @@ static int tpm2_cache_capabilities(Tpm2Context *c) { + + assert(c); + ++ /* Cache the algorithms. The spec indicates supported algorithms can only be modified during runtime ++ * by the SetAlgorithmSet() command. Unfortunately, the spec doesn't require a TPM reinitialization ++ * after changing the algorithm set (unless the PCR algorithms are changed). However, the spec also ++ * indicates the TPM behavior after SetAlgorithmSet() is "vendor-dependent", giving the example of ++ * flushing sessions and objects, erasing policies, etc. So, if the algorithm set is programatically ++ * changed while we are performing some operation, it's reasonable to assume it will break us even if ++ * we don't cache the algorithms, thus they should be "safe" to cache. */ ++ TPM2_ALG_ID current_alg = TPM2_ALG_FIRST; ++ for (;;) { ++ r = tpm2_get_capability( ++ c, ++ TPM2_CAP_ALGS, ++ (uint32_t) current_alg, /* The spec states to cast TPM2_ALG_ID to uint32_t. */ ++ TPM2_MAX_CAP_ALGS, ++ &capability); ++ if (r < 0) ++ return r; ++ ++ TPML_ALG_PROPERTY algorithms = capability.algorithms; ++ ++ /* We should never get 0; the TPM must support some algorithms, and it must not set 'more' if ++ * there are no more. */ ++ assert(algorithms.count > 0); ++ ++ if (!GREEDY_REALLOC_APPEND( ++ c->capability_algorithms, ++ c->n_capability_algorithms, ++ algorithms.algProperties, ++ algorithms.count)) ++ return log_oom(); ++ ++ if (r == 0) ++ break; ++ ++ /* Set current_alg to alg id after last alg id the TPM provided */ ++ current_alg = algorithms.algProperties[algorithms.count - 1].alg + 1; ++ } ++ + /* Cache the command capabilities. The spec isn't actually clear if commands can be added/removed + * while running, but that would be crazy, so let's hope it is not possbile. */ + TPM2_CC current_cc = TPM2_CC_FIRST; +@@ -255,35 +293,26 @@ static int tpm2_cache_capabilities(Tpm2Context *c) { + return 0; + } + +-/* Get the TPMA_ALGORITHM for a TPM2_ALG_ID. +- * +- * Returns 1 if the TPM supports the algorithm and the TPMA_ALGORITHM is provided, or 0 if the TPM does not +- * support the algorithm, or < 0 for any errors. */ +-static int tpm2_get_capability_alg(Tpm2Context *c, TPM2_ALG_ID alg, TPMA_ALGORITHM *ret) { +- TPMU_CAPABILITIES capability; +- int r; +- ++/* Get the TPMA_ALGORITHM for a TPM2_ALG_ID. Returns true if the TPM supports the algorithm and the ++ * TPMA_ALGORITHM is provided, otherwise false. */ ++static bool tpm2_get_capability_alg(Tpm2Context *c, TPM2_ALG_ID alg, TPMA_ALGORITHM *ret) { + assert(c); + +- /* The spec explicitly states the TPM2_ALG_ID should be cast to uint32_t. */ +- r = tpm2_get_capability(c, TPM2_CAP_ALGS, (uint32_t) alg, 1, &capability); +- if (r < 0) +- return r; +- +- TPML_ALG_PROPERTY algorithms = capability.algorithms; +- if (algorithms.count == 0 || algorithms.algProperties[0].alg != alg) { +- log_debug("TPM does not support alg 0x%02" PRIx16 ".", alg); +- return 0; +- } ++ FOREACH_ARRAY(alg_prop, c->capability_algorithms, c->n_capability_algorithms) ++ if (alg_prop->alg == alg) { ++ if (ret) ++ *ret = alg_prop->algProperties; ++ return true; ++ } + ++ log_debug("TPM does not support alg 0x%02" PRIx16 ".", alg); + if (ret) +- *ret = algorithms.algProperties[0].algProperties; ++ *ret = 0; + +- return 1; ++ return false; + } + +-/* Returns 1 if the TPM supports the alg, 0 if the TPM does not support the alg, or < 0 for any error. */ +-int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg) { ++bool tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg) { + return tpm2_get_capability_alg(c, alg, NULL); + } + +@@ -473,6 +502,7 @@ static Tpm2Context *tpm2_context_free(Tpm2Context *c) { + c->tcti_context = mfree(c->tcti_context); + c->tcti_dl = safe_dlclose(c->tcti_dl); + ++ c->capability_algorithms = mfree(c->capability_algorithms); + c->capability_commands = mfree(c->capability_commands); + + return mfree(c); +@@ -598,16 +628,10 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + return r; + + /* We require AES and CFB support for session encryption. */ +- r = tpm2_supports_alg(context, TPM2_ALG_AES); +- if (r < 0) +- return r; +- if (r == 0) ++ if (!tpm2_supports_alg(context, TPM2_ALG_AES)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES."); + +- r = tpm2_supports_alg(context, TPM2_ALG_CFB); +- if (r < 0) +- return r; +- if (r == 0) ++ if (!tpm2_supports_alg(context, TPM2_ALG_CFB)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support CFB."); + + if (!tpm2_supports_tpmt_sym_def(context, &SESSION_TEMPLATE_SYM_AES_128_CFB)) +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 1ca1a2e503..64a2fd3677 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -62,6 +62,8 @@ typedef struct { + ESYS_CONTEXT *esys_context; + + /* Some selected cached capabilities of the TPM */ ++ TPMS_ALG_PROPERTY *capability_algorithms; ++ size_t n_capability_algorithms; + TPMA_CC *capability_commands; + size_t n_capability_commands; + TPML_PCR_SELECTION capability_pcrs; +@@ -86,7 +88,7 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle); + Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle); + DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free); + +-int tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg); ++bool tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg); + bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command); + + bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARMS *parms); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index dfc8b98e08..8fd859b83d 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -651,13 +651,13 @@ TEST(tpm_required_tests) { + assert_se(tpm2_test_parms(c, TPM2_ALG_SYMCIPHER, &parms)); + + /* Test invalid algs */ +- assert_se(tpm2_supports_alg(c, TPM2_ALG_ERROR) == 0); +- assert_se(tpm2_supports_alg(c, TPM2_ALG_LAST + 1) == 0); ++ assert_se(!tpm2_supports_alg(c, TPM2_ALG_ERROR)); ++ assert_se(!tpm2_supports_alg(c, TPM2_ALG_LAST + 1)); + + /* Test valid algs */ +- assert_se(tpm2_supports_alg(c, TPM2_ALG_RSA) == 1); +- assert_se(tpm2_supports_alg(c, TPM2_ALG_AES) == 1); +- assert_se(tpm2_supports_alg(c, TPM2_ALG_CFB) == 1); ++ assert_se(tpm2_supports_alg(c, TPM2_ALG_RSA)); ++ assert_se(tpm2_supports_alg(c, TPM2_ALG_AES)); ++ assert_se(tpm2_supports_alg(c, TPM2_ALG_CFB)); + + /* Test invalid commands */ + assert_se(!tpm2_supports_command(c, TPM2_CC_FIRST - 1)); diff --git a/SOURCES/0547-tpm2-add-tpm2_persist_handle.patch b/SOURCES/0547-tpm2-add-tpm2_persist_handle.patch new file mode 100644 index 0000000..77e12d1 --- /dev/null +++ b/SOURCES/0547-tpm2-add-tpm2_persist_handle.patch @@ -0,0 +1,117 @@ +From 5db840e4bc7b86f7870c153d540ef0f6eddf64c2 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Sun, 1 Jan 2023 23:42:09 -0500 +Subject: [PATCH] tpm2: add tpm2_persist_handle() + +Add function to convert a transient handle in the TPM into a persistent handle +in the TPM. + +(cherry picked from commit d2d29c3be2ff9557d74c7bf852c1423ea6cfa25a) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 83 +++++++++++++++++++++++++++++++++++++----- + 1 file changed, 74 insertions(+), 9 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index c05c636745..0c5f3393dd 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -769,6 +769,75 @@ static int tpm2_esys_handle_from_tpm_handle( + return 1; + } + ++/* Copy an object in the TPM at a transient location to a persistent location. ++ * ++ * The provided transient handle must exist in the TPM in the transient range. The persistent location may be ++ * 0 or any location in the persistent range. If 0, this will try each handle in the persistent range, in ++ * ascending order, until an available one is found. If non-zero, only the requested persistent location will ++ * be used. ++ * ++ * Returns 1 if the object was successfully persisted, or 0 if there is already a key at the requested ++ * location(s), or < 0 on error. The persistent handle is only provided when returning 1. */ ++static int tpm2_persist_handle( ++ Tpm2Context *c, ++ const Tpm2Handle *transient_handle, ++ const Tpm2Handle *session, ++ TPMI_DH_PERSISTENT persistent_location, ++ Tpm2Handle **ret_persistent_handle) { ++ ++ /* We don't use TPM2_PERSISTENT_FIRST and TPM2_PERSISTENT_LAST here due to: ++ * https://github.com/systemd/systemd/pull/27713#issuecomment-1591864753 */ ++ TPMI_DH_PERSISTENT first = UINT32_C(0x81000000), last = UINT32_C(0x81ffffff); ++ TSS2_RC rc; ++ int r; ++ ++ assert(c); ++ assert(transient_handle); ++ ++ /* If persistent location specified, only try that. */ ++ if (persistent_location != 0) { ++ if (TPM2_HANDLE_TYPE(persistent_location) != TPM2_HT_PERSISTENT) ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Handle not in persistent range: 0x%x", persistent_location); ++ ++ first = last = persistent_location; ++ } ++ ++ for (TPMI_DH_PERSISTENT requested = first; requested <= last; requested++) { ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *persistent_handle = NULL; ++ r = tpm2_handle_new(c, &persistent_handle); ++ if (r < 0) ++ return r; ++ ++ /* Since this is a persistent handle, don't flush it. */ ++ persistent_handle->flush = false; ++ ++ rc = sym_Esys_EvictControl( ++ c->esys_context, ++ ESYS_TR_RH_OWNER, ++ transient_handle->esys_handle, ++ session ? session->esys_handle : ESYS_TR_PASSWORD, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ requested, ++ &persistent_handle->esys_handle); ++ if (rc == TSS2_RC_SUCCESS) { ++ if (ret_persistent_handle) ++ *ret_persistent_handle = TAKE_PTR(persistent_handle); ++ ++ return 1; ++ } ++ if (rc != TPM2_RC_NV_DEFINED) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to persist handle: %s", sym_Tss2_RC_Decode(rc)); ++ } ++ ++ if (ret_persistent_handle) ++ *ret_persistent_handle = NULL; ++ ++ return 0; ++} ++ + #define TPM2_CREDIT_RANDOM_FLAG_PATH "/run/systemd/tpm-rng-credited" + + static int tpm2_credit_random(Tpm2Context *c) { +@@ -1203,16 +1272,12 @@ static int tpm2_make_primary( + FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC)); + + if (use_srk_model) { +- rc = sym_Esys_EvictControl(c->esys_context, ESYS_TR_RH_OWNER, primary->esys_handle, +- ESYS_TR_PASSWORD, ESYS_TR_NONE, ESYS_TR_NONE, TPM2_SRK_HANDLE, &primary->esys_handle); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to persist SRK within TPM: %s", sym_Tss2_RC_Decode(rc)); +- primary->flush = false; +- } +- +- if (ret_primary) ++ r = tpm2_persist_handle(c, primary, /* session= */ NULL, TPM2_SRK_HANDLE, ret_primary); ++ if (r < 0) ++ return r; ++ } else if (ret_primary) + *ret_primary = TAKE_PTR(primary); ++ + if (ret_alg) + *ret_alg = alg; + diff --git a/SOURCES/0548-tpm2-add-tpm2_get_or_create_srk.patch b/SOURCES/0548-tpm2-add-tpm2_get_or_create_srk.patch new file mode 100644 index 0000000..df7d58c --- /dev/null +++ b/SOURCES/0548-tpm2-add-tpm2_get_or_create_srk.patch @@ -0,0 +1,327 @@ +From 1228238af33756fa8974840bfa0e65f2ec7bfa71 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 14 Jun 2023 15:49:33 -0400 +Subject: [PATCH] tpm2: add tpm2_get_or_create_srk() + +Add function to simplify getting the TPM SRK; if one exists, it is provided, +otherwise one is created and then the new SRK provided. + +This also add tpm2_create_loaded() and updates tpm2_seal() to use the new +functions instead of tpm2_make_primary(). + +(cherry picked from commit cea525a902246520d063ab53f667a0f33be650f0) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 241 +++++++++++++++++++++++++++++++++++++++-- + 1 file changed, 231 insertions(+), 10 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 0c5f3393dd..2d208479c3 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -30,6 +30,7 @@ static void *libtss2_rc_dl = NULL; + static void *libtss2_mu_dl = NULL; + + static TSS2_RC (*sym_Esys_Create)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL; ++static TSS2_RC (*sym_Esys_CreateLoaded)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_TEMPLATE *inPublic, ESYS_TR *objectHandle, TPM2B_PRIVATE **outPrivate, TPM2B_PUBLIC **outPublic) = NULL; + static TSS2_RC (*sym_Esys_CreatePrimary)(ESYS_CONTEXT *esysContext, ESYS_TR primaryHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE_CREATE *inSensitive, const TPM2B_PUBLIC *inPublic, const TPM2B_DATA *outsideInfo, const TPML_PCR_SELECTION *creationPCR, ESYS_TR *objectHandle, TPM2B_PUBLIC **outPublic, TPM2B_CREATION_DATA **creationData, TPM2B_DIGEST **creationHash, TPMT_TK_CREATION **creationTicket) = NULL; + static TSS2_RC (*sym_Esys_EvictControl)(ESYS_CONTEXT *esysContext, ESYS_TR auth, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPMI_DH_PERSISTENT persistentHandle, ESYS_TR *newObjectHandle) = NULL; + static void (*sym_Esys_Finalize)(ESYS_CONTEXT **context) = NULL; +@@ -78,6 +79,7 @@ int dlopen_tpm2(void) { + r = dlopen_many_sym_or_warn( + &libtss2_esys_dl, "libtss2-esys.so.0", LOG_DEBUG, + DLSYM_ARG(Esys_Create), ++ DLSYM_ARG(Esys_CreateLoaded), + DLSYM_ARG(Esys_CreatePrimary), + DLSYM_ARG(Esys_EvictControl), + DLSYM_ARG(Esys_Finalize), +@@ -996,7 +998,7 @@ static int tpm2_get_legacy_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_templa + * (see TPM2_SRK_HANDLE and tpm2_get_srk() below). + * + * The alg must be TPM2_ALG_RSA or TPM2_ALG_ECC. Returns error if the requested template is not supported on +- * this TPM. */ ++ * this TPM. Also see tpm2_get_best_srk_template() below. */ + static int tpm2_get_srk_template(Tpm2Context *c, TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template) { + /* The attributes are the same between ECC and RSA templates. This has the changes specified in the + * Provisioning Guidance document, specifically: +@@ -1082,6 +1084,16 @@ static int tpm2_get_srk_template(Tpm2Context *c, TPMI_ALG_PUBLIC alg, TPMT_PUBLI + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported SRK alg: 0x%x.", alg); + } + ++/* Get the best supported SRK template. ECC is preferred, then RSA. */ ++static int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template) { ++ if (tpm2_get_srk_template(c, TPM2_ALG_ECC, ret_template) >= 0 || ++ tpm2_get_srk_template(c, TPM2_ALG_RSA, ret_template) >= 0) ++ return 0; ++ ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "TPM does not support either SRK template L-1 (RSA) or L-2 (ECC)."); ++} ++ + /* The SRK handle is defined in the Provisioning Guidance document (see above) in the table "Reserved Handles + * for TPM Provisioning Fundamental Elements". The SRK is useful because it is "shared", meaning it has no + * authValue nor authPolicy set, and thus may be used by anyone on the system to generate derived keys or +@@ -1090,10 +1102,8 @@ static int tpm2_get_srk_template(Tpm2Context *c, TPMI_ALG_PUBLIC alg, TPMT_PUBLI + * the Provisioning Guidance document for more details. */ + #define TPM2_SRK_HANDLE UINT32_C(0x81000001) + +-/* +- * Retrieves the SRK handle if present. Returns 0 if SRK not present, 1 if present +- * and < 0 on error +- */ ++/* Get the SRK. Returns 1 if SRK is found, 0 if there is no SRK, or < 0 on error. Also see ++ * tpm2_get_or_create_srk() below. */ + static int tpm2_get_srk( + Tpm2Context *c, + const Tpm2Handle *session, +@@ -1134,6 +1144,62 @@ static int tpm2_get_srk( + return 1; + } + ++static int tpm2_create_loaded(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPMT_PUBLIC *template, const TPMS_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, TPM2B_PRIVATE **ret_private, Tpm2Handle **ret_handle); ++ ++/* Get the SRK, creating one if needed. Returns 0 on success, or < 0 on error. */ ++static int tpm2_get_or_create_srk( ++ Tpm2Context *c, ++ const Tpm2Handle *session, ++ TPM2B_PUBLIC **ret_public, ++ TPM2B_NAME **ret_name, ++ TPM2B_NAME **ret_qname, ++ Tpm2Handle **ret_handle) { ++ ++ int r; ++ ++ r = tpm2_get_srk(c, session, ret_public, ret_name, ret_qname, ret_handle); ++ if (r < 0) ++ return r; ++ if (r == 1) ++ return 0; ++ ++ /* No SRK, create and persist one */ ++ TPMT_PUBLIC template; ++ r = tpm2_get_best_srk_template(c, &template); ++ if (r < 0) ++ return log_error_errno(r, "Could not get best SRK template: %m"); ++ ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *transient_handle = NULL; ++ r = tpm2_create_loaded( ++ c, ++ /* parent= */ NULL, ++ session, ++ &template, ++ /* sensitive= */ NULL, ++ /* ret_public= */ NULL, ++ /* ret_private= */ NULL, ++ &transient_handle); ++ if (r < 0) ++ return r; ++ ++ /* Try to persist the transient SRK we created. No locking needed; if multiple threads are trying to ++ * persist SRKs concurrently, only one will succeed (r == 1) while the rest will fail (r == 0). In ++ * either case, all threads will get the persistent SRK below. */ ++ r = tpm2_persist_handle(c, transient_handle, session, TPM2_SRK_HANDLE, /* ret_persistent_handle= */ NULL); ++ if (r < 0) ++ return r; ++ ++ /* The SRK should exist now. */ ++ r = tpm2_get_srk(c, session, ret_public, ret_name, ret_qname, ret_handle); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ /* This should never happen. */ ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "SRK we just persisted couldn't be found."); ++ ++ return 0; ++} ++ + static int tpm2_make_primary( + Tpm2Context *c, + TPMI_ALG_PUBLIC alg, +@@ -1788,6 +1854,131 @@ static int tpm2_load_external( + return 0; + } + ++/* This calls TPM2_CreateLoaded() directly, without checking if the TPM supports it. Callers should instead ++ * use tpm2_create_loaded(). */ ++static int _tpm2_create_loaded( ++ Tpm2Context *c, ++ const Tpm2Handle *parent, ++ const Tpm2Handle *session, ++ const TPMT_PUBLIC *template, ++ const TPMS_SENSITIVE_CREATE *sensitive, ++ TPM2B_PUBLIC **ret_public, ++ TPM2B_PRIVATE **ret_private, ++ Tpm2Handle **ret_handle) { ++ ++ usec_t ts; ++ TSS2_RC rc; ++ int r; ++ ++ assert(c); ++ assert(template); ++ ++ log_debug("Creating loaded object on TPM."); ++ ++ ts = now(CLOCK_MONOTONIC); ++ ++ /* Copy the input template and zero the unique area. */ ++ TPMT_PUBLIC template_copy = *template; ++ zero(template_copy.unique); ++ ++ TPM2B_TEMPLATE tpm2b_template; ++ size_t size = 0; ++ rc = sym_Tss2_MU_TPMT_PUBLIC_Marshal( ++ &template_copy, ++ tpm2b_template.buffer, ++ sizeof(tpm2b_template.buffer), ++ &size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal public key template: %s", sym_Tss2_RC_Decode(rc)); ++ assert(size <= UINT16_MAX); ++ tpm2b_template.size = size; ++ ++ TPM2B_SENSITIVE_CREATE tpm2b_sensitive; ++ if (sensitive) ++ tpm2b_sensitive = (TPM2B_SENSITIVE_CREATE) { ++ .size = sizeof(*sensitive), ++ .sensitive = *sensitive, ++ }; ++ else ++ tpm2b_sensitive = (TPM2B_SENSITIVE_CREATE) {}; ++ ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; ++ r = tpm2_handle_new(c, &handle); ++ if (r < 0) ++ return r; ++ ++ _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; ++ _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; ++ rc = sym_Esys_CreateLoaded( ++ c->esys_context, ++ parent ? parent->esys_handle : ESYS_TR_RH_OWNER, ++ session ? session->esys_handle : ESYS_TR_PASSWORD, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ &tpm2b_sensitive, ++ &tpm2b_template, ++ &handle->esys_handle, ++ &private, ++ &public); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to generate loaded object in TPM: %s", ++ sym_Tss2_RC_Decode(rc)); ++ ++ log_debug("Successfully created loaded object on TPM in %s.", ++ FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC)); ++ ++ if (ret_public) ++ *ret_public = TAKE_PTR(public); ++ if (ret_private) ++ *ret_private = TAKE_PTR(private); ++ if (ret_handle) ++ *ret_handle = TAKE_PTR(handle); ++ ++ return 0; ++} ++ ++/* This calls TPM2_CreateLoaded() if the TPM supports it, otherwise it calls TPM2_Create() and TPM2_Load() ++ * separately. */ ++static int tpm2_create_loaded( ++ Tpm2Context *c, ++ const Tpm2Handle *parent, ++ const Tpm2Handle *session, ++ const TPMT_PUBLIC *template, ++ const TPMS_SENSITIVE_CREATE *sensitive, ++ TPM2B_PUBLIC **ret_public, ++ TPM2B_PRIVATE **ret_private, ++ Tpm2Handle **ret_handle) { ++ ++ int r; ++ ++ if (tpm2_supports_command(c, TPM2_CC_CreateLoaded)) ++ return _tpm2_create_loaded(c, parent, session, template, sensitive, ret_public, ret_private, ret_handle); ++ ++ /* Unfortunately, this TPM doesn't support CreateLoaded (added at spec revision 130) so we need to ++ * create and load manually. */ ++ _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; ++ _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; ++ r = tpm2_create(c, parent, session, template, sensitive, &public, &private); ++ if (r < 0) ++ return r; ++ ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; ++ r = tpm2_load(c, parent, session, public, private, &handle); ++ if (r < 0) ++ return r; ++ ++ if (ret_public) ++ *ret_public = TAKE_PTR(public); ++ if (ret_private) ++ *ret_private = TAKE_PTR(private); ++ if (ret_handle) ++ *ret_handle = TAKE_PTR(handle); ++ ++ return 0; ++} ++ + static int tpm2_pcr_read( + Tpm2Context *c, + const TPML_PCR_SELECTION *pcr_selection, +@@ -3193,11 +3384,41 @@ int tpm2_seal(const char *device, + if (r < 0) + return log_error_errno(r, "Failed to generate secret key: %m"); + ++ _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; + _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; +- TPMI_ALG_PUBLIC primary_alg; +- r = tpm2_make_primary(c, /* alg = */0, !!ret_srk_buf, &primary_alg, &primary_handle); +- if (r < 0) +- return r; ++ if (ret_srk_buf) { ++ r = tpm2_get_or_create_srk(c, NULL, &primary_public, NULL, NULL, &primary_handle); ++ if (r < 0) ++ return r; ++ } else { ++ /* TODO: force all callers to provide ret_srk_buf, so we can stop sealing with the legacy templates. */ ++ TPMT_PUBLIC template; ++ r = tpm2_get_legacy_template(TPM2_ALG_ECC, &template); ++ if (r < 0) ++ return log_error_errno(r, "Could not get legacy ECC template: %m"); ++ ++ if (!tpm2_supports_tpmt_public(c, &template)) { ++ r = tpm2_get_legacy_template(TPM2_ALG_RSA, &template); ++ if (r < 0) ++ return log_error_errno(r, "Could not get legacy RSA template: %m"); ++ ++ if (!tpm2_supports_tpmt_public(c, &template)) ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "TPM does not support either ECC or RSA legacy template."); ++ } ++ ++ r = tpm2_create_loaded( ++ c, ++ /* parent= */ NULL, ++ /* session= */ NULL, ++ &template, ++ /* sensitive= */ NULL, ++ &primary_public, ++ /* ret_private= */ NULL, ++ &primary_handle); ++ if (r < 0) ++ return r; ++ } + + _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; + r = tpm2_make_encryption_session(c, primary_handle, &TPM2_HANDLE_NONE, &encryption_session); +@@ -3277,7 +3498,7 @@ int tpm2_seal(const char *device, + *ret_pcr_hash = TAKE_PTR(hash); + *ret_pcr_hash_size = policy_digest.size; + *ret_pcr_bank = pcr_bank; +- *ret_primary_alg = primary_alg; ++ *ret_primary_alg = primary_public->publicArea.type; + + return 0; + } diff --git a/SOURCES/0549-tpm2-move-local-vars-in-tpm2_unseal-to-point-of-use.patch b/SOURCES/0549-tpm2-move-local-vars-in-tpm2_unseal-to-point-of-use.patch new file mode 100644 index 0000000..4926de2 --- /dev/null +++ b/SOURCES/0549-tpm2-move-local-vars-in-tpm2_unseal-to-point-of-use.patch @@ -0,0 +1,70 @@ +From 23ad144b16077c4833f7bcdf3846ebbaa30ae053 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 14 Jun 2023 12:09:35 -0400 +Subject: [PATCH] tpm2: move local vars in tpm2_unseal() to point of use + +No functional change; cosmetic only. + +(cherry picked from commit 98497426d61acc3302505903460abb058142fa0d) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 13 ++++++------- + 1 file changed, 6 insertions(+), 7 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 2d208479c3..92f1fdd962 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -3523,13 +3523,7 @@ int tpm2_unseal(const char *device, + void **ret_secret, + size_t *ret_secret_size) { + +- _cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL; +- _cleanup_(erase_and_freep) char *secret = NULL; +- TPM2B_PRIVATE private = {}; +- TPM2B_PUBLIC public = {}; +- size_t offset = 0; + TSS2_RC rc; +- usec_t start; + int r; + + assert(blob); +@@ -3554,10 +3548,12 @@ int tpm2_unseal(const char *device, + * decrypted if the seed and the PCR policy were right ("unsealing"). We then download the result, + * and use it to unlock the LUKS2 volume. */ + +- start = now(CLOCK_MONOTONIC); ++ usec_t start = now(CLOCK_MONOTONIC); + + log_debug("Unmarshalling private part of HMAC key."); + ++ TPM2B_PRIVATE private = {}; ++ size_t offset = 0; + rc = sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal(blob, blob_size, &offset, &private); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +@@ -3565,6 +3561,7 @@ int tpm2_unseal(const char *device, + + log_debug("Unmarshalling public part of HMAC key."); + ++ TPM2B_PUBLIC public = {}; + rc = sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal(blob, blob_size, &offset, &public); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +@@ -3640,6 +3637,7 @@ int tpm2_unseal(const char *device, + if (r < 0) + return r; + ++ _cleanup_(Esys_Freep) TPM2B_SENSITIVE_DATA* unsealed = NULL; + for (unsigned i = RETRY_UNSEAL_MAX;; i--) { + _cleanup_(tpm2_handle_freep) Tpm2Handle *policy_session = NULL; + _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; +@@ -3691,6 +3689,7 @@ int tpm2_unseal(const char *device, + log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i); + } + ++ _cleanup_(erase_and_freep) char *secret = NULL; + secret = memdup(unsealed->buffer, unsealed->size); + explicit_bzero_safe(unsealed->buffer, unsealed->size); + if (!secret) diff --git a/SOURCES/0550-tpm2-remove-tpm2_make_primary.patch b/SOURCES/0550-tpm2-remove-tpm2_make_primary.patch new file mode 100644 index 0000000..3d0d368 --- /dev/null +++ b/SOURCES/0550-tpm2-remove-tpm2_make_primary.patch @@ -0,0 +1,256 @@ +From 27f6d0b788dd29dfbed8d2b5a9acc8d5bbb04b0e Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 8 Jun 2023 06:55:45 -0400 +Subject: [PATCH] tpm2: remove tpm2_make_primary() + +Replace use of tpm2_make_primary() with tpm2_create_loaded() + +(cherry picked from commit 20988602ff203f6645762ceb8cda70b5f26b0e1d) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 188 ++++++----------------------------------- + 1 file changed, 25 insertions(+), 163 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 92f1fdd962..320261afb6 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1200,156 +1200,6 @@ static int tpm2_get_or_create_srk( + return 0; + } + +-static int tpm2_make_primary( +- Tpm2Context *c, +- TPMI_ALG_PUBLIC alg, +- bool use_srk_model, +- TPMI_ALG_PUBLIC *ret_alg, +- Tpm2Handle **ret_primary) { +- +- static const TPM2B_SENSITIVE_CREATE primary_sensitive = {}; +- static const TPML_PCR_SELECTION creation_pcr = {}; +- TPM2B_PUBLIC primary_template = { .size = sizeof(TPMT_PUBLIC), }; +- _cleanup_(release_lock_file) LockFile srk_lock = LOCK_FILE_INIT; +- TSS2_RC rc; +- usec_t ts; +- int r; +- +- log_debug("Creating %s on TPM.", use_srk_model ? "SRK" : "Transient Primary Key"); +- +- /* So apparently not all TPM2 devices support ECC. ECC is generally preferably, because it's so much +- * faster, noticeably so (~10s vs. ~240ms on my system). Hence, unless explicitly configured let's +- * try to use ECC first, and if that does not work, let's fall back to RSA. */ +- +- ts = now(CLOCK_MONOTONIC); +- +- _cleanup_(tpm2_handle_freep) Tpm2Handle *primary = NULL; +- +- /* we only need the SRK lock when making the SRK since its not atomic, transient +- * primary creations don't even matter if they stomp on each other, the TPM will +- * keep kicking back the same key. +- */ +- if (use_srk_model) { +- r = make_lock_file("/run/systemd/tpm2-srk-init", LOCK_EX, &srk_lock); +- if (r < 0) +- return log_error_errno(r, "Failed to take TPM SRK lock: %m"); +- } +- +- /* Find existing SRK and use it if present */ +- if (use_srk_model) { +- _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; +- r = tpm2_get_srk(c, NULL, &primary_public, NULL, NULL, &primary); +- if (r < 0) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to establish if SRK is present"); +- if (r == 1) { +- log_debug("Discovered existing SRK"); +- +- TPMI_ALG_PUBLIC got_alg = primary_public->publicArea.type; +- if (alg != 0 && alg != got_alg) +- log_warning("Caller asked for specific algorithm %u, but existing SRK is %u, ignoring", +- alg, got_alg); +- +- if (ret_alg) +- *ret_alg = alg; +- if (ret_primary) +- *ret_primary = TAKE_PTR(primary); +- return 0; +- } +- log_debug("Did not find SRK, generating..."); +- } +- +- r = tpm2_handle_new(c, &primary); +- if (r < 0) +- return r; +- +- if (IN_SET(alg, 0, TPM2_ALG_ECC)) { +- if (use_srk_model) +- r = tpm2_get_srk_template(c, TPM2_ALG_ECC, &primary_template.publicArea); +- else +- r = tpm2_get_legacy_template(TPM2_ALG_ECC, &primary_template.publicArea); +- if (r < 0) +- return r; +- +- rc = sym_Esys_CreatePrimary( +- c->esys_context, +- ESYS_TR_RH_OWNER, +- ESYS_TR_PASSWORD, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &primary_sensitive, +- &primary_template, +- NULL, +- &creation_pcr, +- &primary->esys_handle, +- NULL, +- NULL, +- NULL, +- NULL); +- +- if (rc != TSS2_RC_SUCCESS) { +- if (alg != 0) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to generate ECC primary key in TPM: %s", sym_Tss2_RC_Decode(rc)); +- +- log_debug("Failed to generate ECC primary key in TPM, trying RSA: %s", sym_Tss2_RC_Decode(rc)); +- } else { +- log_debug("Successfully created ECC primary key on TPM."); +- alg = TPM2_ALG_ECC; +- } +- } +- +- if (IN_SET(alg, 0, TPM2_ALG_RSA)) { +- if (use_srk_model) +- r = tpm2_get_srk_template(c, TPM2_ALG_RSA, &primary_template.publicArea); +- else +- r = tpm2_get_legacy_template(TPM2_ALG_RSA, &primary_template.publicArea); +- if (r < 0) +- return r; +- +- rc = sym_Esys_CreatePrimary( +- c->esys_context, +- ESYS_TR_RH_OWNER, +- ESYS_TR_PASSWORD, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &primary_sensitive, +- &primary_template, +- NULL, +- &creation_pcr, +- &primary->esys_handle, +- NULL, +- NULL, +- NULL, +- NULL); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to generate RSA primary key in TPM: %s", sym_Tss2_RC_Decode(rc)); +- else if (alg == 0) { +- log_notice("TPM2 chip apparently does not support ECC primary keys, falling back to RSA. " +- "This likely means TPM2 operations will be relatively slow, please be patient."); +- alg = TPM2_ALG_RSA; +- } +- +- log_debug("Successfully created RSA primary key on TPM."); +- } +- +- log_debug("Generating %s on the TPM2 took %s.", use_srk_model ? "SRK" : "Transient Primary Key", +- FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC)); +- +- if (use_srk_model) { +- r = tpm2_persist_handle(c, primary, /* session= */ NULL, TPM2_SRK_HANDLE, ret_primary); +- if (r < 0) +- return r; +- } else if (ret_primary) +- *ret_primary = TAKE_PTR(primary); +- +- if (ret_alg) +- *ret_alg = alg; +- +- return 0; +-} +- + /* Utility functions for TPMS_PCR_SELECTION. */ + + /* Convert a TPMS_PCR_SELECTION object to a mask. */ +@@ -3572,31 +3422,43 @@ int tpm2_unseal(const char *device, + if (r < 0) + return r; + +- /* If their is a primary key we trust, like an SRK, use it */ +- _cleanup_(tpm2_handle_freep) Tpm2Handle *primary = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; + if (srk_buf) { +- +- r = tpm2_handle_new(c, &primary); ++ r = tpm2_handle_new(c, &primary_handle); + if (r < 0) + return r; + +- primary->flush = false; ++ primary_handle->flush = false; + + log_debug("Found existing SRK key to use, deserializing ESYS_TR"); + rc = sym_Esys_TR_Deserialize( + c->esys_context, + srk_buf, + srk_buf_size, +- &primary->esys_handle); ++ &primary_handle->esys_handle); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to deserialize primary key: %s", sym_Tss2_RC_Decode(rc)); +- /* old callers without an SRK still need to create a key */ +- } else { +- r = tpm2_make_primary(c, primary_alg, false, NULL, &primary); ++ } else if (primary_alg != 0) { ++ TPMT_PUBLIC template; ++ r = tpm2_get_legacy_template(primary_alg, &template); ++ if (r < 0) ++ return log_error_errno(r, "Could not get legacy template: %m"); ++ ++ r = tpm2_create_loaded( ++ c, ++ /* parent= */ NULL, ++ /* session= */ NULL, ++ &template, ++ /* sensitive= */ NULL, ++ /* ret_public= */ NULL, ++ /* ret_private= */ NULL, ++ &primary_handle); + if (r < 0) + return r; +- } ++ } else ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "No SRK or primary alg provided."); + + log_debug("Loading HMAC key into TPM."); + +@@ -3607,7 +3469,7 @@ int tpm2_unseal(const char *device, + * provides protections. + */ + _cleanup_(tpm2_handle_freep) Tpm2Handle *hmac_key = NULL; +- r = tpm2_load(c, primary, NULL, &public, &private, &hmac_key); ++ r = tpm2_load(c, primary_handle, NULL, &public, &private, &hmac_key); + if (r < 0) + return r; + +@@ -3633,7 +3495,7 @@ int tpm2_unseal(const char *device, + return r; + + _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; +- r = tpm2_make_encryption_session(c, primary, hmac_key, &encryption_session); ++ r = tpm2_make_encryption_session(c, primary_handle, hmac_key, &encryption_session); + if (r < 0) + return r; + +@@ -3643,7 +3505,7 @@ int tpm2_unseal(const char *device, + _cleanup_(Esys_Freep) TPM2B_DIGEST *policy_digest = NULL; + r = tpm2_make_policy_session( + c, +- primary, ++ primary_handle, + encryption_session, + /* trial= */ false, + &policy_session); diff --git a/SOURCES/0551-tpm2-use-CreatePrimary-to-create-primary-keys-instea.patch b/SOURCES/0551-tpm2-use-CreatePrimary-to-create-primary-keys-instea.patch new file mode 100644 index 0000000..4328c1d --- /dev/null +++ b/SOURCES/0551-tpm2-use-CreatePrimary-to-create-primary-keys-instea.patch @@ -0,0 +1,246 @@ +From 41ed51e6fd011244875680580abb892c4cf1fecf Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 3 Aug 2023 14:44:57 -0400 +Subject: [PATCH] tpm2: use CreatePrimary() to create primary keys instead of + Create() + +Older versions used CreatePrimary() to create a transient primary key to use +when creating a sealed data object. That was changed in v254 to use Create() +instead, which should result in the same transient key, but it seems some +hardware TPMs refuse to allow using Create() to generate primary keys. + +This reverts to using CreatePrimary() to create primary key. + +Fixes: #28654 +(cherry picked from commit aff853f8ea29f22b28e3b584807893c528227769) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 102 +++++++++++++++++++++++++++++++---------- + src/shared/tpm2-util.h | 4 ++ + 2 files changed, 81 insertions(+), 25 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 320261afb6..e889d4c0fe 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1144,8 +1144,6 @@ static int tpm2_get_srk( + return 1; + } + +-static int tpm2_create_loaded(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPMT_PUBLIC *template, const TPMS_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, TPM2B_PRIVATE **ret_private, Tpm2Handle **ret_handle); +- + /* Get the SRK, creating one if needed. Returns 0 on success, or < 0 on error. */ + static int tpm2_get_or_create_srk( + Tpm2Context *c, +@@ -1164,20 +1162,18 @@ static int tpm2_get_or_create_srk( + return 0; + + /* No SRK, create and persist one */ +- TPMT_PUBLIC template; +- r = tpm2_get_best_srk_template(c, &template); ++ TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), }; ++ r = tpm2_get_best_srk_template(c, &template.publicArea); + if (r < 0) + return log_error_errno(r, "Could not get best SRK template: %m"); + + _cleanup_(tpm2_handle_freep) Tpm2Handle *transient_handle = NULL; +- r = tpm2_create_loaded( ++ r = tpm2_create_primary( + c, +- /* parent= */ NULL, + session, + &template, + /* sensitive= */ NULL, + /* ret_public= */ NULL, +- /* ret_private= */ NULL, + &transient_handle); + if (r < 0) + return r; +@@ -1547,8 +1543,65 @@ static int tpm2_get_policy_digest( + return 0; + } + +-static int tpm2_create( ++int tpm2_create_primary( + Tpm2Context *c, ++ const Tpm2Handle *session, ++ const TPM2B_PUBLIC *template, ++ const TPM2B_SENSITIVE_CREATE *sensitive, ++ TPM2B_PUBLIC **ret_public, ++ Tpm2Handle **ret_handle) { ++ ++ usec_t ts; ++ TSS2_RC rc; ++ int r; ++ ++ assert(c); ++ assert(template); ++ ++ log_debug("Creating primary key on TPM."); ++ ++ ts = now(CLOCK_MONOTONIC); ++ ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; ++ r = tpm2_handle_new(c, &handle); ++ if (r < 0) ++ return r; ++ ++ _cleanup_(Esys_Freep) TPM2B_PUBLIC *public = NULL; ++ rc = sym_Esys_CreatePrimary( ++ c->esys_context, ++ ESYS_TR_RH_OWNER, ++ session ? session->esys_handle : ESYS_TR_PASSWORD, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ sensitive ? sensitive : &(TPM2B_SENSITIVE_CREATE) {}, ++ template, ++ /* outsideInfo= */ NULL, ++ &(TPML_PCR_SELECTION) {}, ++ &handle->esys_handle, ++ &public, ++ /* creationData= */ NULL, ++ /* creationHash= */ NULL, ++ /* creationTicket= */ NULL); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to generate primary key in TPM: %s", ++ sym_Tss2_RC_Decode(rc)); ++ ++ log_debug("Successfully created primary key on TPM in %s.", ++ FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - ts, USEC_PER_MSEC)); ++ ++ if (ret_public) ++ *ret_public = TAKE_PTR(public); ++ if (ret_handle) ++ *ret_handle = TAKE_PTR(handle); ++ ++ return 0; ++} ++ ++/* Create a TPM object. Do not use this to create primary keys, because some HW TPMs refuse to allow that; ++ * instead use tpm2_create_primary(). */ ++int tpm2_create(Tpm2Context *c, + const Tpm2Handle *parent, + const Tpm2Handle *session, + const TPMT_PUBLIC *template, +@@ -1560,6 +1613,7 @@ static int tpm2_create( + TSS2_RC rc; + + assert(c); ++ assert(parent); + assert(template); + + log_debug("Creating object on TPM."); +@@ -1587,7 +1641,7 @@ static int tpm2_create( + _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; + rc = sym_Esys_Create( + c->esys_context, +- parent ? parent->esys_handle : ESYS_TR_RH_OWNER, ++ parent->esys_handle, + session ? session->esys_handle : ESYS_TR_PASSWORD, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -1721,6 +1775,7 @@ static int _tpm2_create_loaded( + int r; + + assert(c); ++ assert(parent); + assert(template); + + log_debug("Creating loaded object on TPM."); +@@ -1762,7 +1817,7 @@ static int _tpm2_create_loaded( + _cleanup_(Esys_Freep) TPM2B_PRIVATE *private = NULL; + rc = sym_Esys_CreateLoaded( + c->esys_context, +- parent ? parent->esys_handle : ESYS_TR_RH_OWNER, ++ parent->esys_handle, + session ? session->esys_handle : ESYS_TR_PASSWORD, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -1790,8 +1845,9 @@ static int _tpm2_create_loaded( + } + + /* This calls TPM2_CreateLoaded() if the TPM supports it, otherwise it calls TPM2_Create() and TPM2_Load() +- * separately. */ +-static int tpm2_create_loaded( ++ * separately. Do not use this to create primary keys, because some HW TPMs refuse to allow that; instead use ++ * tpm2_create_primary(). */ ++int tpm2_create_loaded( + Tpm2Context *c, + const Tpm2Handle *parent, + const Tpm2Handle *session, +@@ -3242,29 +3298,27 @@ int tpm2_seal(const char *device, + return r; + } else { + /* TODO: force all callers to provide ret_srk_buf, so we can stop sealing with the legacy templates. */ +- TPMT_PUBLIC template; +- r = tpm2_get_legacy_template(TPM2_ALG_ECC, &template); ++ TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), }; ++ r = tpm2_get_legacy_template(TPM2_ALG_ECC, &template.publicArea); + if (r < 0) + return log_error_errno(r, "Could not get legacy ECC template: %m"); + +- if (!tpm2_supports_tpmt_public(c, &template)) { +- r = tpm2_get_legacy_template(TPM2_ALG_RSA, &template); ++ if (!tpm2_supports_tpmt_public(c, &template.publicArea)) { ++ r = tpm2_get_legacy_template(TPM2_ALG_RSA, &template.publicArea); + if (r < 0) + return log_error_errno(r, "Could not get legacy RSA template: %m"); + +- if (!tpm2_supports_tpmt_public(c, &template)) ++ if (!tpm2_supports_tpmt_public(c, &template.publicArea)) + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM does not support either ECC or RSA legacy template."); + } + +- r = tpm2_create_loaded( ++ r = tpm2_create_primary( + c, +- /* parent= */ NULL, + /* session= */ NULL, + &template, + /* sensitive= */ NULL, + &primary_public, +- /* ret_private= */ NULL, + &primary_handle); + if (r < 0) + return r; +@@ -3440,19 +3494,17 @@ int tpm2_unseal(const char *device, + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to deserialize primary key: %s", sym_Tss2_RC_Decode(rc)); + } else if (primary_alg != 0) { +- TPMT_PUBLIC template; +- r = tpm2_get_legacy_template(primary_alg, &template); ++ TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), }; ++ r = tpm2_get_legacy_template(primary_alg, &template.publicArea); + if (r < 0) + return log_error_errno(r, "Could not get legacy template: %m"); + +- r = tpm2_create_loaded( ++ r = tpm2_create_primary( + c, +- /* parent= */ NULL, + /* session= */ NULL, + &template, + /* sensitive= */ NULL, + /* ret_public= */ NULL, +- /* ret_private= */ NULL, + &primary_handle); + if (r < 0) + return r; +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 64a2fd3677..e059f95790 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -88,6 +88,10 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle); + Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle); + DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free); + ++int tpm2_create_primary(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_PUBLIC *template, const TPM2B_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, Tpm2Handle **ret_handle); ++int tpm2_create(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPMT_PUBLIC *template, const TPMS_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, TPM2B_PRIVATE **ret_private); ++int tpm2_create_loaded(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPMT_PUBLIC *template, const TPMS_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, TPM2B_PRIVATE **ret_private, Tpm2Handle **ret_handle); ++ + bool tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg); + bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command); + diff --git a/SOURCES/0552-cryptsetup-downgrade-a-bunch-of-log-messages-that-to.patch b/SOURCES/0552-cryptsetup-downgrade-a-bunch-of-log-messages-that-to.patch new file mode 100644 index 0000000..2cce69f --- /dev/null +++ b/SOURCES/0552-cryptsetup-downgrade-a-bunch-of-log-messages-that-to.patch @@ -0,0 +1,182 @@ +From 1609ee25acda161923e179182d6adbdac810993e Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Tue, 2 May 2023 11:14:20 +0200 +Subject: [PATCH] cryptsetup: downgrade a bunch of log messages that to + LOG_WARNING + +In all these cases we ignore the failure, hence per our rule the log +level should be below LOG_ERR. Fix that. + +(cherry picked from commit b96cc40a95ccf5bdb61e54f6b361a1c7557ab81a) + +Related: RHEL-16182 +--- + src/cryptsetup/cryptsetup.c | 62 ++++++++++++++----------------------- + 1 file changed, 24 insertions(+), 38 deletions(-) + +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index 674d222db6..96341207b3 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -158,12 +158,12 @@ static int parse_one_option(const char *option) { + + r = safe_atou(val, &arg_key_size); + if (r < 0) { +- log_error_errno(r, "Failed to parse %s, ignoring: %m", option); ++ log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + return 0; + } + + if (arg_key_size % 8) { +- log_error("size= not a multiple of 8, ignoring."); ++ log_warning("size= not a multiple of 8, ignoring."); + return 0; + } + +@@ -173,29 +173,25 @@ static int parse_one_option(const char *option) { + + r = safe_atou(val, &arg_sector_size); + if (r < 0) { +- log_error_errno(r, "Failed to parse %s, ignoring: %m", option); ++ log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + return 0; + } + + if (arg_sector_size % 2) { +- log_error("sector-size= not a multiple of 2, ignoring."); ++ log_warning("sector-size= not a multiple of 2, ignoring."); + return 0; + } + +- if (arg_sector_size < CRYPT_SECTOR_SIZE || arg_sector_size > CRYPT_MAX_SECTOR_SIZE) { +- log_error("sector-size= is outside of %u and %u, ignoring.", CRYPT_SECTOR_SIZE, CRYPT_MAX_SECTOR_SIZE); +- return 0; +- } ++ if (arg_sector_size < CRYPT_SECTOR_SIZE || arg_sector_size > CRYPT_MAX_SECTOR_SIZE) ++ log_warning("sector-size= is outside of %u and %u, ignoring.", CRYPT_SECTOR_SIZE, CRYPT_MAX_SECTOR_SIZE); + + } else if ((val = startswith(option, "key-slot=")) || + (val = startswith(option, "keyslot="))) { + + arg_type = ANY_LUKS; + r = safe_atoi(val, &arg_key_slot); +- if (r < 0) { +- log_error_errno(r, "Failed to parse %s, ignoring: %m", option); +- return 0; +- } ++ if (r < 0) ++ log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + + } else if ((val = startswith(option, "tcrypt-keyfile="))) { + +@@ -204,29 +200,25 @@ static int parse_one_option(const char *option) { + if (strv_extend(&arg_tcrypt_keyfiles, val) < 0) + return log_oom(); + } else +- log_error("Key file path \"%s\" is not absolute. Ignoring.", val); ++ log_warning("Key file path \"%s\" is not absolute, ignoring.", val); + + } else if ((val = startswith(option, "keyfile-size="))) { + + r = safe_atou(val, &arg_keyfile_size); +- if (r < 0) { +- log_error_errno(r, "Failed to parse %s, ignoring: %m", option); +- return 0; +- } ++ if (r < 0) ++ log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + + } else if ((val = startswith(option, "keyfile-offset="))) { + + r = safe_atou64(val, &arg_keyfile_offset); +- if (r < 0) { +- log_error_errno(r, "Failed to parse %s, ignoring: %m", option); +- return 0; +- } ++ if (r < 0) ++ log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + + } else if ((val = startswith(option, "keyfile-erase="))) { + + r = parse_boolean(val); + if (r < 0) { +- log_error_errno(r, "Failed to parse %s, ignoring: %m", option); ++ log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + return 0; + } + +@@ -258,10 +250,8 @@ static int parse_one_option(const char *option) { + } else if ((val = startswith(option, "tries="))) { + + r = safe_atou(val, &arg_tries); +- if (r < 0) { +- log_error_errno(r, "Failed to parse %s, ignoring: %m", option); +- return 0; +- } ++ if (r < 0) ++ log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + + } else if (STR_IN_SET(option, "readonly", "read-only")) + arg_readonly = true; +@@ -314,10 +304,8 @@ static int parse_one_option(const char *option) { + else if ((val = startswith(option, "timeout="))) { + + r = parse_sec_fix_0(val, &arg_timeout); +- if (r < 0) { +- log_error_errno(r, "Failed to parse %s, ignoring: %m", option); +- return 0; +- } ++ if (r < 0) ++ log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + + } else if ((val = startswith(option, "offset="))) { + +@@ -420,7 +408,7 @@ static int parse_one_option(const char *option) { + + r = parse_boolean(val); + if (r < 0) { +- log_error_errno(r, "Failed to parse %s, ignoring: %m", option); ++ log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + return 0; + } + +@@ -439,7 +427,7 @@ static int parse_one_option(const char *option) { + + pcr = r ? TPM_PCR_INDEX_VOLUME_KEY : UINT_MAX; + } else if (!TPM2_PCR_VALID(pcr)) { +- log_error("Selected TPM index for measurement %u outside of allowed range 0…%u, ignoring.", pcr, TPM2_PCRS_MAX-1); ++ log_warning("Selected TPM index for measurement %u outside of allowed range 0…%u, ignoring.", pcr, TPM2_PCRS_MAX-1); + return 0; + } + +@@ -472,7 +460,7 @@ static int parse_one_option(const char *option) { + + r = parse_boolean(val); + if (r < 0) { +- log_error_errno(r, "Failed to parse %s, ignoring: %m", option); ++ log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + return 0; + } + +@@ -484,7 +472,7 @@ static int parse_one_option(const char *option) { + + r = parse_boolean(val); + if (r < 0) { +- log_error_errno(r, "Failed to parse %s, ignoring: %m", option); ++ log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + return 0; + } + +@@ -495,10 +483,8 @@ static int parse_one_option(const char *option) { + else if ((val = startswith(option, "token-timeout="))) { + + r = parse_sec_fix_0(val, &arg_token_timeout_usec); +- if (r < 0) { +- log_error_errno(r, "Failed to parse %s, ignoring: %m", option); +- return 0; +- } ++ if (r < 0) ++ log_warning_errno(r, "Failed to parse %s, ignoring: %m", option); + + } else if (!streq(option, "x-initrd.attach")) + log_warning("Encountered unknown /etc/crypttab option '%s', ignoring.", option); diff --git a/SOURCES/0553-boot-measure-replace-TPM-PolicyPCR-session-with-calc.patch b/SOURCES/0553-boot-measure-replace-TPM-PolicyPCR-session-with-calc.patch new file mode 100644 index 0000000..d1b5e52 --- /dev/null +++ b/SOURCES/0553-boot-measure-replace-TPM-PolicyPCR-session-with-calc.patch @@ -0,0 +1,148 @@ +From 4555de31d96745cabd1362aea7ba3b849429e98b Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 5 May 2023 19:48:14 -0400 +Subject: [PATCH] boot/measure: replace TPM PolicyPCR session with calculation + +Instead of using a trial policy with a TPM to calculate the measurement hash, +this uses a function to calculate the hash with no TPM needed. + +(cherry picked from commit b2efe286587e11e2aa4a6c7e4a2c15da3bb58a2a) + +Related: RHEL-16182 +--- + src/boot/measure.c | 85 +++++++++------------------------------------- + 1 file changed, 16 insertions(+), 69 deletions(-) + +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 86edf77c52..5ce3049147 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -718,7 +718,6 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + _cleanup_(pcr_state_free_all) PcrState *pcr_states = NULL; + _cleanup_(EVP_PKEY_freep) EVP_PKEY *privkey = NULL, *pubkey = NULL; + _cleanup_fclose_ FILE *privkeyf = NULL; +- TSS2_RC rc; + size_t n; + int r; + +@@ -787,15 +786,6 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + if (r < 0) + return r; + +- r = dlopen_tpm2(); +- if (r < 0) +- return r; +- +- _cleanup_tpm2_context_ Tpm2Context *c = NULL; +- r = tpm2_context_new(arg_tpm2_device, &c); +- if (r < 0) +- return r; +- + STRV_FOREACH(phase, arg_phase) { + + r = measure_phase(pcr_states, n, *phase); +@@ -803,42 +793,8 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + return r; + + for (size_t i = 0; i < n; i++) { +- static const TPMT_SYM_DEF symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }; + PcrState *p = pcr_states + i; + +- _cleanup_tpm2_handle_ Tpm2Handle *session = NULL; +- r = tpm2_handle_new(c, &session); +- if (r < 0) +- return r; +- +- rc = sym_Esys_StartAuthSession( +- c->esys_context, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- NULL, +- TPM2_SE_TRIAL, +- &symmetric, +- TPM2_ALG_SHA256, +- &session->esys_handle); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc)); +- +- /* Generate a single hash value from the PCRs included in our policy. Given that that's +- * exactly one, the calculation is trivial. */ +- TPM2B_DIGEST intermediate_digest = { +- .size = SHA256_DIGEST_SIZE, +- }; +- assert(sizeof(intermediate_digest.buffer) >= SHA256_DIGEST_SIZE); +- sha256_direct(p->value, p->value_size, intermediate_digest.buffer); +- + int tpmalg = tpm2_hash_alg_from_string(EVP_MD_name(p->md)); + if (tpmalg < 0) + return log_error_errno(tpmalg, "Unsupported PCR bank"); +@@ -848,29 +804,20 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + tpmalg, + &pcr_selection); + +- rc = sym_Esys_PolicyPCR( +- c->esys_context, +- session->esys_handle, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &intermediate_digest, +- &pcr_selection); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to push PCR policy into TPM: %s", sym_Tss2_RC_Decode(rc)); +- +- _cleanup_(Esys_Freep) TPM2B_DIGEST *pcr_policy_digest = NULL; +- rc = sym_Esys_PolicyGetDigest( +- c->esys_context, +- session->esys_handle, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- &pcr_policy_digest); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc)); ++ TPM2B_DIGEST pcr_values = { ++ .size = p->value_size, ++ }; ++ assert(sizeof(pcr_values.buffer) >= p->value_size); ++ memcpy_safe(pcr_values.buffer, p->value, p->value_size); ++ ++ TPM2B_DIGEST pcr_policy_digest; ++ r = tpm2_digest_init(TPM2_ALG_SHA256, &pcr_policy_digest); ++ if (r < 0) ++ return r; ++ ++ r = tpm2_calculate_policy_pcr(&pcr_selection, &pcr_values, 1, &pcr_policy_digest); ++ if (r < 0) ++ return r; + + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = NULL; + mdctx = EVP_MD_CTX_new(); +@@ -881,7 +828,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to initialize signature context."); + +- if (EVP_DigestSignUpdate(mdctx, pcr_policy_digest->buffer, pcr_policy_digest->size) != 1) ++ if (EVP_DigestSignUpdate(mdctx, pcr_policy_digest.buffer, pcr_policy_digest.size) != 1) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to sign data."); + +@@ -913,7 +860,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + r = json_build(&bv, JSON_BUILD_OBJECT( + JSON_BUILD_PAIR("pcrs", JSON_BUILD_VARIANT(a)), /* PCR mask */ + JSON_BUILD_PAIR("pkfp", JSON_BUILD_HEX(pubkey_fp, pubkey_fp_size)), /* SHA256 fingerprint of public key (DER) used for the signature */ +- JSON_BUILD_PAIR("pol", JSON_BUILD_HEX(pcr_policy_digest->buffer, pcr_policy_digest->size)), /* TPM2 policy hash that is signed */ ++ JSON_BUILD_PAIR("pol", JSON_BUILD_HEX(pcr_policy_digest.buffer, pcr_policy_digest.size)), /* TPM2 policy hash that is signed */ + JSON_BUILD_PAIR("sig", JSON_BUILD_BASE64(sig, ss)))); /* signature data */ + if (r < 0) + return log_error_errno(r, "Failed to build JSON object: %m"); diff --git a/SOURCES/0554-core-imply-DeviceAllow-dev-tpmrm0-with-LoadCredentia.patch b/SOURCES/0554-core-imply-DeviceAllow-dev-tpmrm0-with-LoadCredentia.patch new file mode 100644 index 0000000..df87175 --- /dev/null +++ b/SOURCES/0554-core-imply-DeviceAllow-dev-tpmrm0-with-LoadCredentia.patch @@ -0,0 +1,77 @@ +From 8d11c92de8644e5f090018933bad25be0f2adebb Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Wed, 8 Feb 2023 00:25:00 +0000 +Subject: [PATCH] core: imply DeviceAllow=/dev/tpmrm0 with + LoadCredentialEncrypted + +If the device access policy is restricted, add implicitly access to the TPM +if at least one encrypted credential needs to be loaded. + +Fixes https://github.com/systemd/systemd/issues/26042 + +(cherry picked from commit 398dc7d39b9a877e71529f0e0b139329e4c6992e) + +Related: RHEL-16182 +--- + man/systemd.exec.xml | 8 +++++++- + src/core/unit.c | 10 ++++++++++ + test/units/testsuite-70.sh | 6 ++++++ + 3 files changed, 23 insertions(+), 1 deletion(-) + +diff --git a/man/systemd.exec.xml b/man/systemd.exec.xml +index 29666b102b..4927764b9b 100644 +--- a/man/systemd.exec.xml ++++ b/man/systemd.exec.xml +@@ -3113,7 +3113,13 @@ StandardInputData=V2XigLJyZSBubyBzdHJhbmdlcnMgdG8gbG92ZQpZb3Uga25vdyB0aGUgcnVsZX + authenticated credentials improves security as credentials are not stored in plaintext and only + authenticated and decrypted into plaintext the moment a service requiring them is started. Moreover, + credentials may be bound to the local hardware and installations, so that they cannot easily be +- analyzed offline, or be generated externally. ++ analyzed offline, or be generated externally. When DevicePolicy= is set to ++ closed or strict, or set to auto and ++ DeviceAllow= is set, or PrivateDevices= is set, then this ++ setting adds /dev/tpmrm0 with rw mode to ++ DeviceAllow=. See ++ systemd.resource-control5 ++ for the details about DevicePolicy= or DeviceAllow=. + + The credential files/IPC sockets must be accessible to the service manager, but don't have to + be directly accessible to the unit's processes: the credential data is read and copied into separate, +diff --git a/src/core/unit.c b/src/core/unit.c +index c9a42ee3d7..f109d16eb3 100644 +--- a/src/core/unit.c ++++ b/src/core/unit.c +@@ -4191,6 +4191,16 @@ int unit_patch_contexts(Unit *u) { + if (r < 0) + return r; + } ++ ++ /* If there are encrypted credentials we might need to access the TPM. */ ++ ExecLoadCredential *cred; ++ HASHMAP_FOREACH(cred, ec->load_credentials) ++ if (cred->encrypted) { ++ r = cgroup_add_device_allow(cc, "/dev/tpmrm0", "rw"); ++ if (r < 0) ++ return r; ++ break; ++ } + } + } + +diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh +index 3b4d66b686..2c405bccbb 100755 +--- a/test/units/testsuite-70.sh ++++ b/test/units/testsuite-70.sh +@@ -185,6 +185,12 @@ else + echo "/usr/lib/systemd/systemd-pcrphase or PCR sysfs files not found, skipping PCR extension test case" + fi + ++# Ensure that sandboxing doesn't stop creds from being accessible ++echo "test" > /tmp/testdata ++systemd-creds encrypt /tmp/testdata /tmp/testdata.encrypted --with-key=tpm2 ++systemd-run -p PrivateDevices=yes -p LoadCredentialEncrypted=testdata.encrypted:/tmp/testdata.encrypted --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata ++rm /tmp/testdata ++ + echo OK >/testok + + exit 0 diff --git a/SOURCES/0555-added-more-test-cases.patch b/SOURCES/0555-added-more-test-cases.patch new file mode 100644 index 0000000..d1fdded --- /dev/null +++ b/SOURCES/0555-added-more-test-cases.patch @@ -0,0 +1,101 @@ +From c42a85ba710d4c0e60a97ecb6003825979351dab Mon Sep 17 00:00:00 2001 +From: OMOJOLA JOSHUA DAMILOLA +Date: Mon, 27 Mar 2023 15:24:03 +0000 +Subject: [PATCH] added more test cases + +(cherry picked from commit e2a4411a2b683e3e5b78c1d4931b5e1029d3ba6e) + +Related: RHEL-16182 +--- + test/units/testsuite-70.sh | 78 ++++++++++++++++++++++++++++++++++++++ + 1 file changed, 78 insertions(+) + +diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh +index 2c405bccbb..19768ef7bf 100755 +--- a/test/units/testsuite-70.sh ++++ b/test/units/testsuite-70.sh +@@ -191,6 +191,84 @@ systemd-creds encrypt /tmp/testdata /tmp/testdata.encrypted --with-key=tpm2 + systemd-run -p PrivateDevices=yes -p LoadCredentialEncrypted=testdata.encrypted:/tmp/testdata.encrypted --pipe --wait systemd-creds cat testdata.encrypted | cmp - /tmp/testdata + rm /tmp/testdata + ++# negative tests for cryptenroll ++ ++# Prepare a new disk image ++img_2="/var/tmp/file_enroll.txt" ++truncate -s 20M $img_2 ++echo -n password >/tmp/password ++cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom $img_2 /tmp/password ++ ++#boolean_arguments ++ret="$(! systemd-cryptenroll --fido2-with-client-pin=false 2> >(grep "No block device node specified"))" ++test -n "${ret}" ++ ++ret="$(! systemd-cryptenroll --fido2-with-user-presence=f $img_2 /tmp/foo 2> >(grep "Too many arguments"))" ++test -n "${ret}" ++ ++ret="$(! systemd-cryptenroll --fido2-with-client-pin=1234 $img_2 2> >(grep "Failed to parse boolean argument"))" ++test -n "${ret}" ++ ++systemd-cryptenroll --fido2-with-client-pin=false $img_2 ++ ++ret="$(! systemd-cryptenroll --fido2-with-user-presence=1234 $img_2 2> >(grep "Failed to parse boolean argument"))" ++test -n "${ret}" ++ ++systemd-cryptenroll --fido2-with-user-presence=false $img_2 ++ ++ret="$(! systemd-cryptenroll --fido2-with-user-verification=1234 $img_2 2> >(grep "Failed to parse boolean argument"))" ++test -n "${ret}" ++ ++ret="$(! systemd-cryptenroll --tpm2-with-pin=1234 $img_2 2> >(grep "Failed to parse boolean argument"))" ++test -n "${ret}" ++ ++systemd-cryptenroll --fido2-with-user-verification=false $img_2 ++ ++#arg_enroll_type ++ret="$(! systemd-cryptenroll --recovery-key --password $img_2 2> >(grep "Multiple operations specified at once"))" ++test -n "${ret}" ++ ++ret="$(! systemd-cryptenroll --password --recovery-key $img_2 2> >(grep "Multiple operations specified at once"))" ++test -n "${ret}" ++ ++ret="$(! systemd-cryptenroll --password --fido2-device=auto $img_2 2> >(grep "Multiple operations specified at once"))" ++test -n "${ret}" ++ ++ret="$(! systemd-cryptenroll --password --pkcs11-token-uri=auto $img_2 2> >(grep "Multiple operations specified at once"))" ++test -n "${ret}" ++ ++ret="$(! systemd-cryptenroll --password --tpm2-device=auto $img_2 2> >(grep "Multiple operations specified at once"))" ++test -n "${ret}" ++ ++#arg_unlock_type ++ret="$(! systemd-cryptenroll --unlock-fido2-device=auto --unlock-fido2-device=auto $img_2 2> >(grep "Multiple unlock methods specified at once"))" ++test -n "${ret}" ++ ++ret="$(! systemd-cryptenroll --unlock-fido2-device=auto --unlock-key-file=/tmp/unlock $img_2 2> >(grep "Multiple unlock methods specified at once"))" ++test -n "${ret}" ++ ++#fido2_cred_alg ++ret="$(! systemd-cryptenroll --fido2-credential-algorithm=es512 $img_2 2> >(grep "Failed to parse COSE algorithm"))" ++test -n "${ret}" ++ ++#tpm2_errors ++ret="$(! systemd-cryptenroll --tpm2-public-key-pcrs=key $img_2 2> >(grep "Failed to parse PCR number"))" ++test -n "${ret}" ++ ++ret="$(! systemd-cryptenroll --tpm2-pcrs=key $img_2 2> >(grep "Failed to parse PCR number"))" ++test -n "${ret}" ++ ++#wipe_slots ++ret="$(! systemd-cryptenroll --wipe-slot $img_2 2> >(grep "Failed to parse slot index"))" ++test -n "${ret}" ++ ++ret="$(! systemd-cryptenroll --wipe-slot=10240000 $img_2 2> >(grep "Slot index"))" ++test -n "${ret}" ++ ++#fido2_multiple_auto ++ret="$(! systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto $img_2 2> >(grep "When both enrolling and unlocking with FIDO2 tokens, automatic discovery is unsupported. Please specify device paths for enrolling and unlocking respectively."))" ++test -n "${ret}" ++ + echo OK >/testok + + exit 0 diff --git a/SOURCES/0556-test-fixed-negative-checks-in-TEST-70-TPM2.patch b/SOURCES/0556-test-fixed-negative-checks-in-TEST-70-TPM2.patch new file mode 100644 index 0000000..554b55f --- /dev/null +++ b/SOURCES/0556-test-fixed-negative-checks-in-TEST-70-TPM2.patch @@ -0,0 +1,112 @@ +From 60b259a6c4ddd71a3fe1256271a97c706419b860 Mon Sep 17 00:00:00 2001 +From: OMOJOLA JOSHUA DAMILOLA +Date: Thu, 30 Mar 2023 21:36:50 +0000 +Subject: [PATCH] test: fixed negative checks in TEST-70-TPM2. Use in-line + error handling rather than redirections. Follow up on #27020 + +(cherry picked from commit 27d45db38c29d0eb5e2d707507b066438340a792) + +Related: RHEL-16182 +--- + test/units/testsuite-70.sh | 59 +++++++++++++------------------------- + 1 file changed, 20 insertions(+), 39 deletions(-) + +diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh +index 19768ef7bf..1bfa14e01a 100755 +--- a/test/units/testsuite-70.sh ++++ b/test/units/testsuite-70.sh +@@ -200,74 +200,55 @@ echo -n password >/tmp/password + cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom $img_2 /tmp/password + + #boolean_arguments +-ret="$(! systemd-cryptenroll --fido2-with-client-pin=false 2> >(grep "No block device node specified"))" +-test -n "${ret}" ++systemd-cryptenroll --fido2-with-client-pin=false && { echo 'unexpected success'; exit 1; } + +-ret="$(! systemd-cryptenroll --fido2-with-user-presence=f $img_2 /tmp/foo 2> >(grep "Too many arguments"))" +-test -n "${ret}" ++systemd-cryptenroll --fido2-with-user-presence=f $img_2 /tmp/foo && { echo 'unexpected success'; exit 1; } + +-ret="$(! systemd-cryptenroll --fido2-with-client-pin=1234 $img_2 2> >(grep "Failed to parse boolean argument"))" +-test -n "${ret}" ++systemd-cryptenroll --fido2-with-client-pin=1234 $img_2 && { echo 'unexpected success'; exit 1; } + + systemd-cryptenroll --fido2-with-client-pin=false $img_2 + +-ret="$(! systemd-cryptenroll --fido2-with-user-presence=1234 $img_2 2> >(grep "Failed to parse boolean argument"))" +-test -n "${ret}" ++systemd-cryptenroll --fido2-with-user-presence=1234 $img_2 && { echo 'unexpected success'; exit 1; } + + systemd-cryptenroll --fido2-with-user-presence=false $img_2 + +-ret="$(! systemd-cryptenroll --fido2-with-user-verification=1234 $img_2 2> >(grep "Failed to parse boolean argument"))" +-test -n "${ret}" ++systemd-cryptenroll --fido2-with-user-verification=1234 $img_2 && { echo 'unexpected success'; exit 1; } + +-ret="$(! systemd-cryptenroll --tpm2-with-pin=1234 $img_2 2> >(grep "Failed to parse boolean argument"))" +-test -n "${ret}" ++systemd-cryptenroll --tpm2-with-pin=1234 $img_2 && { echo 'unexpected success'; exit 1; } + + systemd-cryptenroll --fido2-with-user-verification=false $img_2 + + #arg_enroll_type +-ret="$(! systemd-cryptenroll --recovery-key --password $img_2 2> >(grep "Multiple operations specified at once"))" +-test -n "${ret}" ++systemd-cryptenroll --recovery-key --password $img_2 && { echo 'unexpected success'; exit 1; } + +-ret="$(! systemd-cryptenroll --password --recovery-key $img_2 2> >(grep "Multiple operations specified at once"))" +-test -n "${ret}" ++systemd-cryptenroll --password --recovery-key $img_2 && { echo 'unexpected success'; exit 1; } + +-ret="$(! systemd-cryptenroll --password --fido2-device=auto $img_2 2> >(grep "Multiple operations specified at once"))" +-test -n "${ret}" ++systemd-cryptenroll --password --fido2-device=auto $img_2 && { echo 'unexpected success'; exit 1; } + +-ret="$(! systemd-cryptenroll --password --pkcs11-token-uri=auto $img_2 2> >(grep "Multiple operations specified at once"))" +-test -n "${ret}" ++systemd-cryptenroll --password --pkcs11-token-uri=auto $img_2 && { echo 'unexpected success'; exit 1; } + +-ret="$(! systemd-cryptenroll --password --tpm2-device=auto $img_2 2> >(grep "Multiple operations specified at once"))" +-test -n "${ret}" ++systemd-cryptenroll --password --tpm2-device=auto $img_2 && { echo 'unexpected success'; exit 1; } + + #arg_unlock_type +-ret="$(! systemd-cryptenroll --unlock-fido2-device=auto --unlock-fido2-device=auto $img_2 2> >(grep "Multiple unlock methods specified at once"))" +-test -n "${ret}" ++systemd-cryptenroll --unlock-fido2-device=auto --unlock-fido2-device=auto $img_2 && { echo 'unexpected success'; exit 1; } + +-ret="$(! systemd-cryptenroll --unlock-fido2-device=auto --unlock-key-file=/tmp/unlock $img_2 2> >(grep "Multiple unlock methods specified at once"))" +-test -n "${ret}" ++systemd-cryptenroll --unlock-fido2-device=auto --unlock-key-file=/tmp/unlock $img_2 && { echo 'unexpected success'; exit 1; } + +-#fido2_cred_alg +-ret="$(! systemd-cryptenroll --fido2-credential-algorithm=es512 $img_2 2> >(grep "Failed to parse COSE algorithm"))" +-test -n "${ret}" ++#fido2_cred_algorithm ++systemd-cryptenroll --fido2-credential-algorithm=es512 $img_2 && { echo 'unexpected success'; exit 1; } + + #tpm2_errors +-ret="$(! systemd-cryptenroll --tpm2-public-key-pcrs=key $img_2 2> >(grep "Failed to parse PCR number"))" +-test -n "${ret}" ++systemd-cryptenroll --tpm2-public-key-pcrs=key $img_2 && { echo 'unexpected success'; exit 1; } + +-ret="$(! systemd-cryptenroll --tpm2-pcrs=key $img_2 2> >(grep "Failed to parse PCR number"))" +-test -n "${ret}" ++systemd-cryptenroll --tpm2-pcrs=key $img_2 && { echo 'unexpected success'; exit 1; } + + #wipe_slots +-ret="$(! systemd-cryptenroll --wipe-slot $img_2 2> >(grep "Failed to parse slot index"))" +-test -n "${ret}" ++systemd-cryptenroll --wipe-slot $img_2 && { echo 'unexpected success'; exit 1; } + +-ret="$(! systemd-cryptenroll --wipe-slot=10240000 $img_2 2> >(grep "Slot index"))" +-test -n "${ret}" ++systemd-cryptenroll --wipe-slot=10240000 $img_2 && { echo 'unexpected success'; exit 1; } + + #fido2_multiple_auto +-ret="$(! systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto $img_2 2> >(grep "When both enrolling and unlocking with FIDO2 tokens, automatic discovery is unsupported. Please specify device paths for enrolling and unlocking respectively."))" +-test -n "${ret}" ++systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto $img_2 && { echo 'unexpected success'; exit 1; } + + echo OK >/testok + diff --git a/SOURCES/0557-systemd-cryptenroll-add-string-aliases-for-tpm2-PCRs.patch b/SOURCES/0557-systemd-cryptenroll-add-string-aliases-for-tpm2-PCRs.patch new file mode 100644 index 0000000..a081427 --- /dev/null +++ b/SOURCES/0557-systemd-cryptenroll-add-string-aliases-for-tpm2-PCRs.patch @@ -0,0 +1,358 @@ +From d91f027b44b32703fbd6bcf9a28aadde2549b8fd Mon Sep 17 00:00:00 2001 +From: OMOJOLA JOSHUA DAMILOLA +Date: Thu, 30 Mar 2023 07:55:41 +0000 +Subject: [PATCH] systemd-cryptenroll: add string aliases for tpm2 PCRs Fixes + #26697. RFE. + +(cherry picked from commit 96ead603b80339a4cf047ab2d2ab03d4b26271af) + +Related: RHEL-16182 +--- + man/systemd-cryptenroll.xml | 46 +++++++++++++++++++++++++++++------- + src/basic/string-table.h | 1 + + src/shared/tpm2-util.c | 32 +++++++++++++++++++------ + src/shared/tpm2-util.h | 27 +++++++++++++++++++++ + src/test/test-tpm2.c | 47 +++++++++++++++++++++++++++++++++++++ + test/units/testsuite-70.sh | 8 +++++++ + 6 files changed, 146 insertions(+), 15 deletions(-) + +diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml +index f08d95c6fb..af5269aa7a 100644 +--- a/man/systemd-cryptenroll.xml ++++ b/man/systemd-cryptenroll.xml +@@ -225,7 +225,12 @@ + Configures the TPM2 PCRs (Platform Configuration Registers) to bind the enrollment + requested via to. Takes a + separated list of + numeric PCR indexes in the range 0…23. If not used, defaults to PCR 7 only. If an empty string is +- specified, binds the enrollment to no PCRs at all. PCRs allow binding the enrollment to specific ++ specified, binds the enrollment to no PCRs at all. ++ Registers may also be specified using string aliases. ++ For instance to bind to the registers ++ 4, 1, and 5. Check the PCR definitions table below for a full list ++ of available string aliases. ++ PCRs allow binding the enrollment to specific + software versions and system state, so that the enrolled unlocking key is only accessible (may be + "unsealed") if specific trusted software and/or configuration is used. + +@@ -239,13 +244,15 @@ + + + +- ++ + ++ + + + + + PCR ++ alias + Explanation + + +@@ -253,41 +260,43 @@ + + + 0 ++ platform-code + Core system firmware executable code; changes on firmware updates + + + + 1 ++ platform-config + Core system firmware data/host platform configuration; typically contains serial and model numbers, changes on basic hardware/CPU/RAM replacements + + + + 2 ++ external-code + Extended or pluggable executable code; includes option ROMs on pluggable hardware + + + + 3 ++ external-config + Extended or pluggable firmware data; includes information about pluggable hardware + + + + 4 ++ boot-loader-code + Boot loader and additional drivers; changes on boot loader updates. The shim project will measure the PE binary it chain loads into this PCR. If the Linux kernel is invoked as UEFI PE binary, it is measured here, too. sd-stub7 measures system extension images read from the ESP here too (see systemd-sysext8). + + + + 5 ++ boot-loader-config + GPT/Partition table; changes when the partitions are added, modified or removed + + +- +- 6 +- Power state events; changes on system suspend/sleep +- +- + + 7 ++ secure-boot-policy + Secure boot state; changes when UEFI SecureBoot mode is enabled/disabled, or firmware certificates (PK, KEK, db, dbx, …) changes. The shim project will measure most of its (non-MOK) certificates and SBAT data into this PCR. + + +@@ -296,39 +305,58 @@ + + + 9 ++ kernel-initrd + The Linux kernel measures all initrds it receives into this PCR. + + + + + 10 ++ ima + The IMA project measures its runtime state into this PCR. + + + + 11 ++ kernel-boot + systemd-stub7 measures the ELF kernel image, embedded initrd and other payload of the PE image it is placed in into this PCR. Unlike PCR 4 (where the same data should be measured into), this PCR value should be easy to pre-calculate, as this only contains static parts of the PE binary. Use this PCR to bind TPM policies to a specific kernel image, possibly with an embedded initrd. systemd-pcrphase.service8 measures boot phase strings into this PCR at various milestones of the boot process. + + + + 12 ++ kernel-config + systemd-boot7 measures any specified kernel command line into this PCR. systemd-stub7 measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR. (Note that if systemd-boot and systemd-stub are used in combination the command line might be measured twice!) + + + + 13 ++ sysexts + systemd-stub7 measures any systemd-sysext8 images it loads and passed to the booted kernel into this PCR. + + + + 14 ++ shim-policy + The shim project measures its "MOK" certificates and hashes into this PCR. + + + + 15 ++ system-identity + systemd-cryptsetup7 optionally measures the volume key of activated LUKS volumes into this PCR. + ++ ++ ++ 16 ++ debug ++ Debug ++ ++ ++ ++ 23 ++ application-support ++ Application Support ++ + + + +@@ -382,7 +410,9 @@ + : the former binds decryption to the current, specific PCR + values; the latter binds decryption to any set of PCR values for which a signature by the specified + public key can be provided. The latter is hence more useful in scenarios where software updates shell +- be possible without losing access to all previously encrypted LUKS2 volumes. ++ be possible without losing access to all previously encrypted LUKS2 volumes. ++ Like with , string aliases as defined in the table above can also be used ++ to specify the registers, for instance . + + The option takes a path to a TPM2 PCR signature file + as generated by the +diff --git a/src/basic/string-table.h b/src/basic/string-table.h +index e3a26a623c..3be70dfade 100644 +--- a/src/basic/string-table.h ++++ b/src/basic/string-table.h +@@ -95,6 +95,7 @@ ssize_t string_table_lookup(const char * const *table, size_t len, const char *k + #define DEFINE_STRING_TABLE_LOOKUP_WITH_FALLBACK(name,type,max) \ + _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,) \ + _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,) ++#define DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(name,type,max) _DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_FALLBACK(name,type,max,) + + #define DEFINE_PRIVATE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max) \ + _DEFINE_STRING_TABLE_LOOKUP_TO_STRING_FALLBACK(name,type,max,static) +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index e889d4c0fe..dd22f94dc0 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -20,6 +20,7 @@ + #include "random-util.h" + #include "sha256.h" + #include "stat-util.h" ++#include "string-table.h" + #include "time-util.h" + #include "tpm2-util.h" + #include "virt.h" +@@ -3869,14 +3870,10 @@ int tpm2_pcr_mask_from_string(const char *arg, uint32_t *ret_mask) { + if (r < 0) + return log_error_errno(r, "Failed to parse PCR list: %s", arg); + +- r = safe_atou(pcr, &n); ++ r = pcr_index_from_string(pcr); + if (r < 0) +- return log_error_errno(r, "Failed to parse PCR number: %s", pcr); +- if (n >= TPM2_PCRS_MAX) +- return log_error_errno(SYNTHETIC_ERRNO(ERANGE), +- "PCR number out of range (valid range 0…%u): %u", +- TPM2_PCRS_MAX - 1, n); +- ++ return log_error_errno(r, "Failed to parse specified PCR or specified PCR is out of range: %s", pcr); ++ n = r; + SET_BIT(mask, n);; + } + +@@ -4373,3 +4370,24 @@ int tpm2_util_pbkdf2_hmac_sha256(const void *pass, + + return 0; + } ++ ++static const char* const pcr_index_table[_PCR_INDEX_MAX_DEFINED] = { ++ [PCR_PLATFORM_CODE] = "platform-code", ++ [PCR_PLATFORM_CONFIG] = "platform-config", ++ [PCR_EXTERNAL_CODE] = "external-code", ++ [PCR_EXTERNAL_CONFIG] = "external-config", ++ [PCR_BOOT_LOADER_CODE] = "boot-loader-code", ++ [PCR_BOOT_LOADER_CONFIG] = "boot-loader-config", ++ [PCR_SECURE_BOOT_POLICY] = "secure-boot-policy", ++ [PCR_KERNEL_INITRD] = "kernel-initrd", ++ [PCR_IMA] = "ima", ++ [PCR_KERNEL_BOOT] = "kernel-boot", ++ [PCR_KERNEL_CONFIG] = "kernel-config", ++ [PCR_SYSEXTS] = "sysexts", ++ [PCR_SHIM_POLICY] = "shim-policy", ++ [PCR_SYSTEM_IDENTITY] = "system-identity", ++ [PCR_DEBUG] = "debug", ++ [PCR_APPLICATION_SUPPORT] = "application-support", ++}; ++ ++DEFINE_STRING_TABLE_LOOKUP_FROM_STRING_WITH_FALLBACK(pcr_index, int, TPM2_PCRS_MAX); +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index e059f95790..97dae85fcb 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -190,6 +190,31 @@ typedef enum Tpm2Support { + TPM2_SUPPORT_FULL = TPM2_SUPPORT_FIRMWARE|TPM2_SUPPORT_DRIVER|TPM2_SUPPORT_SYSTEM|TPM2_SUPPORT_SUBSYSTEM, + } Tpm2Support; + ++typedef enum PcrIndex { ++/* The following names for PCRs 0…7 are based on the names in the "TCG PC Client Specific Platform Firmware Profile Specification" (https://trustedcomputinggroup.org/resource/pc-client-specific-platform-firmware-profile-specification/) */ ++ PCR_PLATFORM_CODE = 0, ++ PCR_PLATFORM_CONFIG = 1, ++ PCR_EXTERNAL_CODE = 2, ++ PCR_EXTERNAL_CONFIG = 3, ++ PCR_BOOT_LOADER_CODE = 4, ++ PCR_BOOT_LOADER_CONFIG = 5, ++ PCR_SECURE_BOOT_POLICY = 7, ++/* The following names for PCRs 9…15 are based on the "Linux TPM PCR Registry" ++(https://uapi-group.org/specifications/specs/linux_tpm_pcr_registry/) */ ++ PCR_KERNEL_INITRD = 9, ++ PCR_IMA = 10, ++ PCR_KERNEL_BOOT = 11, ++ PCR_KERNEL_CONFIG = 12, ++ PCR_SYSEXTS = 13, ++ PCR_SHIM_POLICY = 14, ++ PCR_SYSTEM_IDENTITY = 15, ++/* As per "TCG PC Client Specific Platform Firmware Profile Specification" again, see above */ ++ PCR_DEBUG = 16, ++ PCR_APPLICATION_SUPPORT = 23, ++ _PCR_INDEX_MAX_DEFINED = TPM2_PCRS_MAX, ++ _PCR_INDEX_INVALID = -EINVAL, ++} PcrIndex; ++ + Tpm2Support tpm2_support(void); + + int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask); +@@ -202,3 +227,5 @@ int tpm2_util_pbkdf2_hmac_sha256(const void *pass, + const void *salt, + size_t saltlen, + uint8_t res[static SHA256_DIGEST_SIZE]); ++ ++int pcr_index_from_string(const char *s); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 8fd859b83d..87c8f6f421 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -27,6 +27,53 @@ TEST(tpm2_mask_from_string) { + test_tpm2_pcr_mask_from_string_one("0,2", 5, 0); + test_tpm2_pcr_mask_from_string_one("0+2", 5, 0); + test_tpm2_pcr_mask_from_string_one("foo", 0, -EINVAL); ++ test_tpm2_pcr_mask_from_string_one("7+application-support", 8388736, 0); ++ test_tpm2_pcr_mask_from_string_one("8+boot-loader-code", 272, 0); ++ test_tpm2_pcr_mask_from_string_one("6+boot-loader-code,44", 0, -EINVAL); ++ test_tpm2_pcr_mask_from_string_one("7,shim-policy,4", 16528, 0); ++ test_tpm2_pcr_mask_from_string_one("sysexts,shim-policy+kernel-boot", 26624, 0); ++ test_tpm2_pcr_mask_from_string_one("sysexts,shim+kernel-boot", 0, -EINVAL); ++ test_tpm2_pcr_mask_from_string_one("sysexts+17+23", 8527872, 0); ++ test_tpm2_pcr_mask_from_string_one("debug+24", 16842752, 0); ++} ++ ++TEST(pcr_index_from_string) { ++ assert_se(pcr_index_from_string("platform-code") == 0); ++ assert_se(pcr_index_from_string("0") == 0); ++ assert_se(pcr_index_from_string("platform-config") == 1); ++ assert_se(pcr_index_from_string("1") == 1); ++ assert_se(pcr_index_from_string("external-code") == 2); ++ assert_se(pcr_index_from_string("2") == 2); ++ assert_se(pcr_index_from_string("external-config") == 3); ++ assert_se(pcr_index_from_string("3") == 3); ++ assert_se(pcr_index_from_string("boot-loader-code") == 4); ++ assert_se(pcr_index_from_string("4") == 4); ++ assert_se(pcr_index_from_string("boot-loader-config") == 5); ++ assert_se(pcr_index_from_string("5") == 5); ++ assert_se(pcr_index_from_string("secure-boot-policy") == 7); ++ assert_se(pcr_index_from_string("7") == 7); ++ assert_se(pcr_index_from_string("kernel-initrd") == 9); ++ assert_se(pcr_index_from_string("9") == 9); ++ assert_se(pcr_index_from_string("ima") == 10); ++ assert_se(pcr_index_from_string("10") == 10); ++ assert_se(pcr_index_from_string("kernel-boot") == 11); ++ assert_se(pcr_index_from_string("11") == 11); ++ assert_se(pcr_index_from_string("kernel-config") == 12); ++ assert_se(pcr_index_from_string("12") == 12); ++ assert_se(pcr_index_from_string("sysexts") == 13); ++ assert_se(pcr_index_from_string("13") == 13); ++ assert_se(pcr_index_from_string("shim-policy") == 14); ++ assert_se(pcr_index_from_string("14") == 14); ++ assert_se(pcr_index_from_string("system-identity") == 15); ++ assert_se(pcr_index_from_string("15") == 15); ++ assert_se(pcr_index_from_string("debug") == 16); ++ assert_se(pcr_index_from_string("16") == 16); ++ assert_se(pcr_index_from_string("application-support") == 23); ++ assert_se(pcr_index_from_string("23") == 23); ++ assert_se(pcr_index_from_string("hello") == -EINVAL); ++ assert_se(pcr_index_from_string("8") == 8); ++ assert_se(pcr_index_from_string("44") == -EINVAL); ++ assert_se(pcr_index_from_string("-5") == -EINVAL); + } + + TEST(tpm2_util_pbkdf2_hmac_sha256) { +diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh +index 1bfa14e01a..5d4b155286 100755 +--- a/test/units/testsuite-70.sh ++++ b/test/units/testsuite-70.sh +@@ -242,6 +242,14 @@ systemd-cryptenroll --tpm2-public-key-pcrs=key $img_2 && { echo 'unexpected succ + + systemd-cryptenroll --tpm2-pcrs=key $img_2 && { echo 'unexpected success'; exit 1; } + ++systemd-cryptenroll --tpm2-pcrs=44+8 $img_2 && { echo 'unexpected success'; exit 1; } ++ ++systemd-cryptenroll --tpm2-pcrs=8 $img_2 ++ ++systemd-cryptenroll --tpm2-pcrs=hello $img_2 && { echo 'unexpected success'; exit 1; } ++ ++systemd-cryptenroll --tpm2-pcrs=boot-loader-code+boot-loader-config $img_2 ++ + #wipe_slots + systemd-cryptenroll --wipe-slot $img_2 && { echo 'unexpected success'; exit 1; } + diff --git a/SOURCES/0558-cryptenroll-fix-an-assertion-with-weak-passwords.patch b/SOURCES/0558-cryptenroll-fix-an-assertion-with-weak-passwords.patch new file mode 100644 index 0000000..894a727 --- /dev/null +++ b/SOURCES/0558-cryptenroll-fix-an-assertion-with-weak-passwords.patch @@ -0,0 +1,32 @@ +From 9b0ead2c1db66555e6ca97934085fd3dac397595 Mon Sep 17 00:00:00 2001 +From: Frantisek Sumsal +Date: Tue, 9 May 2023 22:17:15 +0200 +Subject: [PATCH] cryptenroll: fix an assertion with weak passwords + +Passing 0 to log_xxx_errno() leads to an assertion, so let's not do that: + +$ NEWPASSWORD="" build-san/systemd-cryptenroll --unlock-key-file=/tmp/password --password "$img" +/tmp/password has 0644 mode that is too permissive, please adjust the ownership and access mode. +Assertion '(_error) != 0' failed at src/cryptenroll/cryptenroll-password.c:164, function enroll_password(). Aborting. +Aborted (core dumped) + +(cherry picked from commit 0e43ab6d245a77aab35c7963ec636f37e6103984) + +Related: RHEL-16182 +--- + src/cryptenroll/cryptenroll-password.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/cryptenroll/cryptenroll-password.c b/src/cryptenroll/cryptenroll-password.c +index 9b7c8b5400..2e2efcf6f6 100644 +--- a/src/cryptenroll/cryptenroll-password.c ++++ b/src/cryptenroll/cryptenroll-password.c +@@ -81,7 +81,7 @@ int enroll_password( + if (r < 0) + return log_error_errno(r, "Failed to check password for quality: %m"); + if (r == 0) +- log_warning_errno(r, "Specified password does not pass quality checks (%s), proceeding anyway.", error); ++ log_warning("Specified password does not pass quality checks (%s), proceeding anyway.", error); + + keyslot = crypt_keyslot_add_by_volume_key( + cd, diff --git a/SOURCES/0559-man-systemd-cryptenroll-update-list-of-PCRs-link-to-.patch b/SOURCES/0559-man-systemd-cryptenroll-update-list-of-PCRs-link-to-.patch new file mode 100644 index 0000000..af16409 --- /dev/null +++ b/SOURCES/0559-man-systemd-cryptenroll-update-list-of-PCRs-link-to-.patch @@ -0,0 +1,397 @@ +From 5d389e870101a3906f45f2f1b276a319d23c7440 Mon Sep 17 00:00:00 2001 +From: =?UTF-8?q?Zbigniew=20J=C4=99drzejewski-Szmek?= +Date: Wed, 5 Apr 2023 09:30:52 +0200 +Subject: [PATCH] man/systemd-cryptenroll: update list of PCRs, link to uapi + docs + +Entia non sunt multiplicanda praeter necessitatem. We had a list of PCRs in the +man page which was already half out-of-date. Instead, link to web page with the +"authoritative" list. Here, drop the descriptions of what shim and grub do. Instead, +just give some short descriptions and mention what systemd components do. +systemd-pcrmachine.service and systemd-pcrfs@.service are now mentioned too. + +https://github.com/uapi-group/specifications/commit/d0e590b1e2648e76ece66157ceade3f45b165b14 +extended the table in the specs repo. +https://github.com/uapi-group/specifications/pull/59 adds some more text there +too. + +Also, rework the recommendation: hint that PCR 11 is useful, and recommend +binding to policy signatures instead of direct PCR values. This new text is +intentionally vague: doing this correctly is hard, but let's at least not imply +that just binding to PCR 7 is useful in any way. + +Also, change "string alias" to "name" in discussion of PCR names. + +Inspired by https://discussion.fedoraproject.org/t/future-of-encryption-in-fedora/80397/17 + +(cherry picked from commit 10fa7251c0d8a465c932f2c4cf4496efb1637458) + +Related: RHEL-16182 +--- + man/systemd-cryptenroll.xml | 336 +++++++++++++++++++----------------- + 1 file changed, 177 insertions(+), 159 deletions(-) + +diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml +index af5269aa7a..5ddaab40bc 100644 +--- a/man/systemd-cryptenroll.xml ++++ b/man/systemd-cryptenroll.xml +@@ -58,6 +58,162 @@ + + The tool supports only LUKS2 volumes, as it stores token meta-information in the LUKS2 JSON token + area, which is not available in other encryption formats. ++ ++ ++ TPM2 PCRs and policies ++ ++ PCRs allow binding of the encryption of secrets to specific software versions and system state, ++ so that the enrolled key is only accessible (may be "unsealed") if specific trusted software and/or ++ configuration is used. Such bindings may be created with the option ++ described below. ++ ++ Secrets may also be bound indirectly: a signed policy for a state of some combination of PCR ++ values is provided, and the secret is bound to the public part of the key used to sign this policy. ++ This means that the owner of a key can generate a sequence of signed policies, for specific software ++ versions and system states, and the secret can be decrypted as long as the machine state matches one of ++ those policies. For example, a vendor may provide such a policy for each kernel+initrd update, allowing ++ users to encrypt secrets so that they can be decrypted when running any kernel+initrd signed by the ++ vendor. Such bindings may be created with the options , ++ , described below. ++ ++ ++ See Linux TPM ++ PCR Registry for an authoritative list of PCRs and how they are updated. The table below ++ contains a quick reference, describing in particular the PCRs modified by systemd. ++ ++ ++ Well-known PCR Definitions ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ ++ PCR ++ name ++ Explanation ++ ++ ++ ++ ++ ++ 0 ++ platform-code ++ Core system firmware executable code; changes on firmware updates ++ ++ ++ ++ 1 ++ platform-config ++ Core system firmware data/host platform configuration; typically contains serial and model numbers, changes on basic hardware/CPU/RAM replacements ++ ++ ++ ++ 2 ++ external-code ++ Extended or pluggable executable code; includes option ROMs on pluggable hardware ++ ++ ++ ++ 3 ++ external-config ++ Extended or pluggable firmware data; includes information about pluggable hardware ++ ++ ++ ++ 4 ++ boot-loader-code ++ Boot loader and additional drivers, PE binaries invoked by the boot loader; changes on boot loader updates. sd-stub7 measures system extension images read from the ESP here too (see systemd-sysext8). ++ ++ ++ ++ 5 ++ boot-loader-config ++ GPT/Partition table; changes when the partitions are added, modified, or removed ++ ++ ++ ++ 7 ++ secure-boot-policy ++ Secure Boot state; changes when UEFI SecureBoot mode is enabled/disabled, or firmware certificates (PK, KEK, db, dbx, …) changes. ++ ++ ++ ++ 9 ++ kernel-initrd ++ The Linux kernel measures all initrds it receives into this PCR. ++ ++ ++ ++ ++ 10 ++ ima ++ The IMA project measures its runtime state into this PCR. ++ ++ ++ ++ 11 ++ kernel-boot ++ systemd-stub7 measures the ELF kernel image, embedded initrd and other payload of the PE image it is placed in into this PCR. systemd-pcrphase.service8 measures boot phase strings into this PCR at various milestones of the boot process. ++ ++ ++ ++ 12 ++ kernel-config ++ systemd-boot7 measures the kernel command line into this PCR. systemd-stub7 measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR. ++ ++ ++ ++ 13 ++ sysexts ++ systemd-stub7 measures any systemd-sysext8 images it passes to the booted kernel into this PCR. ++ ++ ++ ++ 14 ++ shim-policy ++ The shim project measures its "MOK" certificates and hashes into this PCR. ++ ++ ++ ++ 15 ++ system-identity ++ systemd-cryptsetup8 optionally measures the volume key of activated LUKS volumes into this PCR. systemd-pcrmachine.service8 measures the machine-id5 into this PCR. systemd-pcrfs@.service8 measures mount points, file system UUIDs, labels, partion UUIDs of the root and /var/ filesystems into this PCR. ++ ++ ++ ++ 16 ++ debug ++ Debug ++ ++ ++ ++ 23 ++ application-support ++ Application Support ++ ++ ++ ++
++ ++ In general, encrypted volumes would be bound to some combination of PCRs 7, 11, and 14 (if ++ shim/MOK is used). In order to allow firmware and OS version updates, it is typically not advisable to ++ use PCRs such as 0 and 2, since the program code they cover should already be covered indirectly ++ through the certificates measured into PCR 7. Validation through certificates hashes is typically ++ preferable over validation through direct measurements as it is less brittle in context of OS/firmware ++ updates: the measurements will change on every update, but signatures should remain unchanged. See the ++ Linux TPM PCR ++ Registry for more discussion. ++
+ + + +@@ -222,154 +378,15 @@ + + PCR + +- Configures the TPM2 PCRs (Platform Configuration Registers) to bind the enrollment +- requested via to. Takes a + separated list of +- numeric PCR indexes in the range 0…23. If not used, defaults to PCR 7 only. If an empty string is +- specified, binds the enrollment to no PCRs at all. +- Registers may also be specified using string aliases. +- For instance to bind to the registers +- 4, 1, and 5. Check the PCR definitions table below for a full list +- of available string aliases. +- PCRs allow binding the enrollment to specific +- software versions and system state, so that the enrolled unlocking key is only accessible (may be +- "unsealed") if specific trusted software and/or configuration is used. +- +- +- Well-known PCR Definitions +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- +- PCR +- alias +- Explanation +- +- +- +- +- +- 0 +- platform-code +- Core system firmware executable code; changes on firmware updates +- +- +- +- 1 +- platform-config +- Core system firmware data/host platform configuration; typically contains serial and model numbers, changes on basic hardware/CPU/RAM replacements +- +- +- +- 2 +- external-code +- Extended or pluggable executable code; includes option ROMs on pluggable hardware +- +- +- +- 3 +- external-config +- Extended or pluggable firmware data; includes information about pluggable hardware +- +- +- +- 4 +- boot-loader-code +- Boot loader and additional drivers; changes on boot loader updates. The shim project will measure the PE binary it chain loads into this PCR. If the Linux kernel is invoked as UEFI PE binary, it is measured here, too. sd-stub7 measures system extension images read from the ESP here too (see systemd-sysext8). +- +- +- +- 5 +- boot-loader-config +- GPT/Partition table; changes when the partitions are added, modified or removed +- +- +- +- 7 +- secure-boot-policy +- Secure boot state; changes when UEFI SecureBoot mode is enabled/disabled, or firmware certificates (PK, KEK, db, dbx, …) changes. The shim project will measure most of its (non-MOK) certificates and SBAT data into this PCR. +- +- +- +- +- +- +- 9 +- kernel-initrd +- The Linux kernel measures all initrds it receives into this PCR. +- +- +- +- +- 10 +- ima +- The IMA project measures its runtime state into this PCR. +- +- +- +- 11 +- kernel-boot +- systemd-stub7 measures the ELF kernel image, embedded initrd and other payload of the PE image it is placed in into this PCR. Unlike PCR 4 (where the same data should be measured into), this PCR value should be easy to pre-calculate, as this only contains static parts of the PE binary. Use this PCR to bind TPM policies to a specific kernel image, possibly with an embedded initrd. systemd-pcrphase.service8 measures boot phase strings into this PCR at various milestones of the boot process. +- +- +- +- 12 +- kernel-config +- systemd-boot7 measures any specified kernel command line into this PCR. systemd-stub7 measures any manually specified kernel command line (i.e. a kernel command line that overrides the one embedded in the unified PE image) and loaded credentials into this PCR. (Note that if systemd-boot and systemd-stub are used in combination the command line might be measured twice!) +- +- +- +- 13 +- sysexts +- systemd-stub7 measures any systemd-sysext8 images it loads and passed to the booted kernel into this PCR. +- +- +- +- 14 +- shim-policy +- The shim project measures its "MOK" certificates and hashes into this PCR. +- +- +- +- 15 +- system-identity +- systemd-cryptsetup7 optionally measures the volume key of activated LUKS volumes into this PCR. +- +- +- +- 16 +- debug +- Debug +- +- +- +- 23 +- application-support +- Application Support +- +- +- +-
+- +- For most applications it should be sufficient to bind against PCR 7 (and possibly PCR 14, if +- shim/MOK is desired), as this includes measurements of the trusted certificates (and possibly hashes) +- that are used to validate all components of the boot process up to and including the OS kernel. In +- order to simplify firmware and OS version updates it's typically not advisable to include PCRs such +- as 0 and 2 in the binding of the enrollment, since the program code they cover should already be +- protected indirectly through the certificates measured into PCR 7. Validation through these +- certificates is typically preferable over validation through direct measurements as it is less +- brittle in context of OS/firmware updates: the measurements will change on every update, but code +- signatures likely will validate against pre-existing certificates.
++ Configures the TPM2 PCRs (Platform Configuration Registers) to bind to when ++ enrollment is requested via . Takes a list of PCR names or numeric ++ indices in the range 0…23. Multiple PCR indexes are separated by +. If not ++ specified, the default is to use PCR 7 only. If an empty string is specified, binds the enrollment to ++ no PCRs at all. See the table above for a list of available PCRs. ++ ++ Example: ++ specifies that PCR registers 4, 1, and 5 should be used. ++ +
+ + +@@ -410,20 +427,21 @@ + : the former binds decryption to the current, specific PCR + values; the latter binds decryption to any set of PCR values for which a signature by the specified + public key can be provided. The latter is hence more useful in scenarios where software updates shell +- be possible without losing access to all previously encrypted LUKS2 volumes. +- Like with , string aliases as defined in the table above can also be used +- to specify the registers, for instance .
++ be possible without losing access to all previously encrypted LUKS2 volumes. Like with ++ , names defined in the table above can also be used to specify the ++ registers, for instance ++ . + +- The option takes a path to a TPM2 PCR signature file +- as generated by the ++ The option takes a path to a TPM2 PCR signature file as ++ generated by the + systemd-measure1 +- tool. If this this is not specified explicitly a suitable signature file ++ tool. If this is not specified explicitly, a suitable signature file + tpm2-pcr-signature.json is searched for in /etc/systemd/, +- /run/systemd/, /usr/lib/systemd/ (in this order) and +- used. If a signature file is specified or found it is used to verify if the volume can be unlocked +- with it given the current PCR state, before the new slot is written to disk. This is intended as +- safety net to ensure that access to a volume is not lost if a public key is enrolled for which no +- valid signature for the current PCR state is available. If the supplied signature does not unlock the ++ /run/systemd/, /usr/lib/systemd/ (in this order) and used. ++ If a signature file is specified or found it is used to verify if the volume can be unlocked with it ++ given the current PCR state, before the new slot is written to disk. This is intended as safety net ++ to ensure that access to a volume is not lost if a public key is enrolled for which no valid ++ signature for the current PCR state is available. If the supplied signature does not unlock the + current PCR state and public key combination, no slot is enrolled and the operation will fail. If no + signature file is specified or found no such safety verification is done. +
diff --git a/SOURCES/0560-tpm2-add-debug-logging-to-functions-converting-hash-.patch b/SOURCES/0560-tpm2-add-debug-logging-to-functions-converting-hash-.patch new file mode 100644 index 0000000..e004656 --- /dev/null +++ b/SOURCES/0560-tpm2-add-debug-logging-to-functions-converting-hash-.patch @@ -0,0 +1,53 @@ +From 73f1ef0adb005695b7feca7e5568a41baf29e6fb Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 1 Aug 2023 14:09:04 -0400 +Subject: [PATCH] tpm2: add debug logging to functions converting hash or asym + algs to/from strings or ids + +Add debug log message if the algorithm name or id is not known. + +(cherry picked from commit 240774f5ce70f0bcbf64999a3db5c25be3f44a9c) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 6 ++++-- + 1 file changed, 4 insertions(+), 2 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index dd22f94dc0..7387dcc48a 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -4183,6 +4183,7 @@ const char *tpm2_hash_alg_to_string(uint16_t alg) { + return "sha384"; + if (alg == TPM2_ALG_SHA512) + return "sha512"; ++ log_debug("Unknown hash algorithm id 0x%" PRIx16, alg); + return NULL; + } + +@@ -4195,7 +4196,7 @@ int tpm2_hash_alg_from_string(const char *alg) { + return TPM2_ALG_SHA384; + if (strcaseeq_ptr(alg, "sha512")) + return TPM2_ALG_SHA512; +- return -EINVAL; ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown hash algorithm name '%s'", alg); + } + + const char *tpm2_asym_alg_to_string(uint16_t alg) { +@@ -4203,6 +4204,7 @@ const char *tpm2_asym_alg_to_string(uint16_t alg) { + return "ecc"; + if (alg == TPM2_ALG_RSA) + return "rsa"; ++ log_debug("Unknown asymmetric algorithm id 0x%" PRIx16, alg); + return NULL; + } + +@@ -4211,7 +4213,7 @@ int tpm2_asym_alg_from_string(const char *alg) { + return TPM2_ALG_ECC; + if (strcaseeq_ptr(alg, "rsa")) + return TPM2_ALG_RSA; +- return -EINVAL; ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown asymmetric algorithm name '%s'", alg); + } + + Tpm2Support tpm2_support(void) { diff --git a/SOURCES/0561-tpm2-add-tpm2_hash_alg_to_size.patch b/SOURCES/0561-tpm2-add-tpm2_hash_alg_to_size.patch new file mode 100644 index 0000000..5a93622 --- /dev/null +++ b/SOURCES/0561-tpm2-add-tpm2_hash_alg_to_size.patch @@ -0,0 +1,51 @@ +From c81e096b3856a3b9906d1b1140db39848db02472 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 11 Jul 2023 11:11:59 -0400 +Subject: [PATCH] tpm2: add tpm2_hash_alg_to_size() + +Add function to get the hash size for a hash algorithm + +(cherry picked from commit c9df1fb119b3e57b0468457cc681920f453ff6e7) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 12 ++++++++++++ + src/shared/tpm2-util.h | 2 ++ + 2 files changed, 14 insertions(+) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 7387dcc48a..509dab60f8 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -4174,6 +4174,18 @@ int tpm2_parse_luks2_json( + return 0; + } + ++int tpm2_hash_alg_to_size(uint16_t alg) { ++ if (alg == TPM2_ALG_SHA1) ++ return 20; ++ if (alg == TPM2_ALG_SHA256) ++ return 32; ++ if (alg == TPM2_ALG_SHA384) ++ return 48; ++ if (alg == TPM2_ALG_SHA512) ++ return 64; ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown hash algorithm id 0x%" PRIx16, alg); ++} ++ + const char *tpm2_hash_alg_to_string(uint16_t alg) { + if (alg == TPM2_ALG_SHA1) + return "sha1"; +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 97dae85fcb..affcbea3a1 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -164,6 +164,8 @@ int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_p + #define TPM2_ALG_RSA 0x1 + #endif + ++int tpm2_hash_alg_to_size(uint16_t alg); ++ + const char *tpm2_hash_alg_to_string(uint16_t alg); + int tpm2_hash_alg_from_string(const char *alg); + diff --git a/SOURCES/0562-tpm2-change-tpm2_tpm-_pcr_selection_to_mask-to-retur.patch b/SOURCES/0562-tpm2-change-tpm2_tpm-_pcr_selection_to_mask-to-retur.patch new file mode 100644 index 0000000..edb6707 --- /dev/null +++ b/SOURCES/0562-tpm2-change-tpm2_tpm-_pcr_selection_to_mask-to-retur.patch @@ -0,0 +1,199 @@ +From be19ca580bf23d3b6e31c7a030cd3e19c2498f16 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 1 Aug 2023 12:55:17 -0400 +Subject: [PATCH] tpm2: change tpm2_tpm*_pcr_selection_to_mask() to return mask + +This simplifies use of the functions, as well as avoiding the use of -ENOENT +from tpm2_tpml_pcr_selection_to_mask(). + +(cherry picked from commit dbaae766c7eaacdfb19ee23600f0f382a16ae33b) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 52 ++++++++++++++++-------------------------- + src/shared/tpm2-util.h | 4 ++-- + src/test/test-tpm2.c | 8 ++----- + 3 files changed, 24 insertions(+), 40 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 509dab60f8..b0a2f715ef 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1200,15 +1200,14 @@ static int tpm2_get_or_create_srk( + /* Utility functions for TPMS_PCR_SELECTION. */ + + /* Convert a TPMS_PCR_SELECTION object to a mask. */ +-void tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s, uint32_t *ret) { ++uint32_t tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s) { + assert(s); + assert(s->sizeofSelect <= sizeof(s->pcrSelect)); +- assert(ret); + + uint32_t mask = 0; + for (unsigned i = 0; i < s->sizeofSelect; i++) + SET_FLAG(mask, (uint32_t)s->pcrSelect[i] << (i * 8), true); +- *ret = mask; ++ return mask; + } + + /* Convert a mask and hash alg to a TPMS_PCR_SELECTION object. */ +@@ -1231,25 +1230,27 @@ void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash_alg, TP + + /* Add all PCR selections in 'b' to 'a'. Both must have the same hash alg. */ + void tpm2_tpms_pcr_selection_add(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b) { ++ uint32_t maska, maskb; ++ + assert(a); + assert(b); + assert(a->hash == b->hash); + +- uint32_t maska, maskb; +- tpm2_tpms_pcr_selection_to_mask(a, &maska); +- tpm2_tpms_pcr_selection_to_mask(b, &maskb); ++ maska = tpm2_tpms_pcr_selection_to_mask(a); ++ maskb = tpm2_tpms_pcr_selection_to_mask(b); + tpm2_tpms_pcr_selection_from_mask(maska | maskb, a->hash, a); + } + + /* Remove all PCR selections in 'b' from 'a'. Both must have the same hash alg. */ + void tpm2_tpms_pcr_selection_sub(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b) { ++ uint32_t maska, maskb; ++ + assert(a); + assert(b); + assert(a->hash == b->hash); + +- uint32_t maska, maskb; +- tpm2_tpms_pcr_selection_to_mask(a, &maska); +- tpm2_tpms_pcr_selection_to_mask(b, &maskb); ++ maska = tpm2_tpms_pcr_selection_to_mask(a); ++ maskb = tpm2_tpms_pcr_selection_to_mask(b); + tpm2_tpms_pcr_selection_from_mask(maska & ~maskb, a->hash, a); + } + +@@ -1265,11 +1266,7 @@ void tpm2_tpms_pcr_selection_move(TPMS_PCR_SELECTION *a, TPMS_PCR_SELECTION *b) + #define FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms) \ + _FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms, UNIQ) + #define _FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms, uniq) \ +- FOREACH_PCR_IN_MASK(pcr, \ +- ({ uint32_t UNIQ_T(_mask, uniq); \ +- tpm2_tpms_pcr_selection_to_mask(tpms, &UNIQ_T(_mask, uniq)); \ +- UNIQ_T(_mask, uniq); \ +- })) ++ FOREACH_PCR_IN_MASK(pcr, tpm2_tpms_pcr_selection_to_mask(tpms)) + + #define FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml) \ + UNIQ_FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml, UNIQ) +@@ -1291,21 +1288,17 @@ char *tpm2_tpms_pcr_selection_to_string(const TPMS_PCR_SELECTION *s) { + + const char *algstr = strna(tpm2_hash_alg_to_string(s->hash)); + +- uint32_t mask; +- tpm2_tpms_pcr_selection_to_mask(s, &mask); +- _cleanup_free_ char *maskstr = tpm2_pcr_mask_to_string(mask); +- if (!maskstr) ++ _cleanup_free_ char *mask = tpm2_pcr_mask_to_string(tpm2_tpms_pcr_selection_to_mask(s)); ++ if (!mask) + return NULL; + +- return strjoin(algstr, "(", maskstr, ")"); ++ return strjoin(algstr, "(", mask, ")"); + } + + size_t tpm2_tpms_pcr_selection_weight(const TPMS_PCR_SELECTION *s) { + assert(s); + +- uint32_t mask; +- tpm2_tpms_pcr_selection_to_mask(s, &mask); +- return (size_t)__builtin_popcount(mask); ++ return (size_t)__builtin_popcount(tpm2_tpms_pcr_selection_to_mask(s)); + } + + /* Utility functions for TPML_PCR_SELECTION. */ +@@ -1356,10 +1349,9 @@ static TPMS_PCR_SELECTION *tpm2_tpml_pcr_selection_get_tpms_pcr_selection( + return selection; + } + +-/* Convert a TPML_PCR_SELECTION object to a mask. Returns -ENOENT if 'hash_alg' is not in the object. */ +-int tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash_alg, uint32_t *ret) { ++/* Convert a TPML_PCR_SELECTION object to a mask. Returns empty mask (i.e. 0) if 'hash_alg' is not in the object. */ ++uint32_t tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash_alg) { + assert(l); +- assert(ret); + + /* Make a copy, as tpm2_tpml_pcr_selection_get_tpms_pcr_selection() will modify the object if there + * are multiple entries with the requested hash alg. */ +@@ -1368,10 +1360,9 @@ int tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH h + TPMS_PCR_SELECTION *s; + s = tpm2_tpml_pcr_selection_get_tpms_pcr_selection(&lcopy, hash_alg); + if (!s) +- return SYNTHETIC_ERRNO(ENOENT); ++ return 0; + +- tpm2_tpms_pcr_selection_to_mask(s, ret); +- return 0; ++ return tpm2_tpms_pcr_selection_to_mask(s); + } + + /* Convert a mask and hash alg to a TPML_PCR_SELECTION object. */ +@@ -2574,10 +2565,7 @@ static int find_signature( + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Signature is not a JSON object."); + + uint16_t pcr_bank = pcr_selection->pcrSelections[0].hash; +- uint32_t pcr_mask; +- r = tpm2_tpml_pcr_selection_to_mask(pcr_selection, pcr_bank, &pcr_mask); +- if (r < 0) +- return r; ++ uint32_t pcr_mask = tpm2_tpml_pcr_selection_to_mask(pcr_selection, pcr_bank); + + k = tpm2_hash_alg_to_string(pcr_bank); + if (!k) +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index affcbea3a1..2f1eb8a012 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -102,7 +102,7 @@ int tpm2_get_good_pcr_banks_strv(Tpm2Context *c, uint32_t pcr_mask, char ***ret) + + int tpm2_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size); + +-void tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s, uint32_t *ret); ++uint32_t tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s); + void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPMS_PCR_SELECTION *ret); + void tpm2_tpms_pcr_selection_add(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b); + void tpm2_tpms_pcr_selection_sub(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b); +@@ -111,7 +111,7 @@ char *tpm2_tpms_pcr_selection_to_string(const TPMS_PCR_SELECTION *s); + size_t tpm2_tpms_pcr_selection_weight(const TPMS_PCR_SELECTION *s); + #define tpm2_tpms_pcr_selection_is_empty(s) (tpm2_tpms_pcr_selection_weight(s) == 0) + +-int tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t *ret); ++uint32_t tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash); + void tpm2_tpml_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPML_PCR_SELECTION *ret); + void tpm2_tpml_pcr_selection_add_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s); + void tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 87c8f6f421..c61bbf6d94 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -157,9 +157,7 @@ static void verify_tpms_pcr_selection(TPMS_PCR_SELECTION *s, uint32_t mask, TPMI + assert_se(s->pcrSelect[2] == ((mask >> 16) & 0xff)); + assert_se(s->pcrSelect[3] == 0); + +- uint32_t m = POISON_U32; +- tpm2_tpms_pcr_selection_to_mask(s, &m); +- assert_se(m == mask); ++ assert_se(tpm2_tpms_pcr_selection_to_mask(s) == mask); + } + + static void verify_tpml_pcr_selection(TPML_PCR_SELECTION *l, TPMS_PCR_SELECTION s[], size_t count) { +@@ -167,10 +165,8 @@ static void verify_tpml_pcr_selection(TPML_PCR_SELECTION *l, TPMS_PCR_SELECTION + for (size_t i = 0; i < count; i++) { + assert_tpms_pcr_selection_eq(&s[i], &l->pcrSelections[i]); + +- uint32_t mask = POISON_U32; + TPMI_ALG_HASH hash = l->pcrSelections[i].hash; +- assert_se(tpm2_tpml_pcr_selection_to_mask(l, hash, &mask) == 0); +- verify_tpms_pcr_selection(&l->pcrSelections[i], mask, hash); ++ verify_tpms_pcr_selection(&l->pcrSelections[i], tpm2_tpml_pcr_selection_to_mask(l, hash), hash); + } + } + diff --git a/SOURCES/0563-tpm2-add-more-helper-functions-for-managing-TPML_PCR.patch b/SOURCES/0563-tpm2-add-more-helper-functions-for-managing-TPML_PCR.patch new file mode 100644 index 0000000..8489e53 --- /dev/null +++ b/SOURCES/0563-tpm2-add-more-helper-functions-for-managing-TPML_PCR.patch @@ -0,0 +1,134 @@ +From 2748f56bf147d5212cada67cba838877f3ce3f5c Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 19 Jul 2023 08:50:06 -0400 +Subject: [PATCH] tpm2: add more helper functions for managing + TPML_PCR_SELECTION and TPMS_PCR_SELECTION + +Add more functions to help manage these objects. + +(cherry picked from commit 13b551744b9df9ea08d7e06dee57a8cea7b48d1b) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 62 +++++++++++++++++++++++++++++++++++------- + src/shared/tpm2-util.h | 6 ++++ + 2 files changed, 58 insertions(+), 10 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index b0a2f715ef..9c0cad47c6 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1228,30 +1228,45 @@ void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash_alg, TP + }; + } + ++/* Test if all bits in the mask are set in the TPMS_PCR_SELECTION. */ ++bool tpm2_tpms_pcr_selection_has_mask(const TPMS_PCR_SELECTION *s, uint32_t mask) { ++ assert(s); ++ ++ return FLAGS_SET(tpm2_tpms_pcr_selection_to_mask(s), mask); ++} ++ ++static void tpm2_tpms_pcr_selection_update_mask(TPMS_PCR_SELECTION *s, uint32_t mask, bool b) { ++ assert(s); ++ ++ tpm2_tpms_pcr_selection_from_mask(UPDATE_FLAG(tpm2_tpms_pcr_selection_to_mask(s), mask, b), s->hash, s); ++} ++ ++/* Add all PCR selections in the mask. */ ++void tpm2_tpms_pcr_selection_add_mask(TPMS_PCR_SELECTION *s, uint32_t mask) { ++ tpm2_tpms_pcr_selection_update_mask(s, mask, 1); ++} ++ ++/* Remove all PCR selections in the mask. */ ++void tpm2_tpms_pcr_selection_sub_mask(TPMS_PCR_SELECTION *s, uint32_t mask) { ++ tpm2_tpms_pcr_selection_update_mask(s, mask, 0); ++} ++ + /* Add all PCR selections in 'b' to 'a'. Both must have the same hash alg. */ + void tpm2_tpms_pcr_selection_add(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b) { +- uint32_t maska, maskb; +- + assert(a); + assert(b); + assert(a->hash == b->hash); + +- maska = tpm2_tpms_pcr_selection_to_mask(a); +- maskb = tpm2_tpms_pcr_selection_to_mask(b); +- tpm2_tpms_pcr_selection_from_mask(maska | maskb, a->hash, a); ++ tpm2_tpms_pcr_selection_add_mask(a, tpm2_tpms_pcr_selection_to_mask(b)); + } + + /* Remove all PCR selections in 'b' from 'a'. Both must have the same hash alg. */ + void tpm2_tpms_pcr_selection_sub(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b) { +- uint32_t maska, maskb; +- + assert(a); + assert(b); + assert(a->hash == b->hash); + +- maska = tpm2_tpms_pcr_selection_to_mask(a); +- maskb = tpm2_tpms_pcr_selection_to_mask(b); +- tpm2_tpms_pcr_selection_from_mask(maska & ~maskb, a->hash, a); ++ tpm2_tpms_pcr_selection_sub_mask(a, tpm2_tpms_pcr_selection_to_mask(b)); + } + + /* Move all PCR selections in 'b' to 'a'. Both must have the same hash alg. */ +@@ -1426,6 +1441,33 @@ void tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(TPML_PCR_SELECTION *l, const + tpm2_tpms_pcr_selection_sub(selection, s); + } + ++/* Test if all bits in the mask for the hash are set in the TPML_PCR_SELECTION. */ ++bool tpm2_tpml_pcr_selection_has_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t mask) { ++ assert(l); ++ ++ return FLAGS_SET(tpm2_tpml_pcr_selection_to_mask(l, hash), mask); ++} ++ ++/* Add the PCR selections in the mask, with the provided hash. */ ++void tpm2_tpml_pcr_selection_add_mask(TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t mask) { ++ TPMS_PCR_SELECTION tpms; ++ ++ assert(l); ++ ++ tpm2_tpms_pcr_selection_from_mask(mask, hash, &tpms); ++ tpm2_tpml_pcr_selection_add_tpms_pcr_selection(l, &tpms); ++} ++ ++/* Remove the PCR selections in the mask, with the provided hash. */ ++void tpm2_tpml_pcr_selection_sub_mask(TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t mask) { ++ TPMS_PCR_SELECTION tpms; ++ ++ assert(l); ++ ++ tpm2_tpms_pcr_selection_from_mask(mask, hash, &tpms); ++ tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(l, &tpms); ++} ++ + /* Add all PCR selections in 'b' to 'a'. */ + void tpm2_tpml_pcr_selection_add(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION *b) { + assert(a); +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 2f1eb8a012..c024245915 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -104,6 +104,9 @@ int tpm2_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const vo + + uint32_t tpm2_tpms_pcr_selection_to_mask(const TPMS_PCR_SELECTION *s); + void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPMS_PCR_SELECTION *ret); ++bool tpm2_tpms_pcr_selection_has_mask(const TPMS_PCR_SELECTION *s, uint32_t mask); ++void tpm2_tpms_pcr_selection_add_mask(TPMS_PCR_SELECTION *s, uint32_t mask); ++void tpm2_tpms_pcr_selection_sub_mask(TPMS_PCR_SELECTION *s, uint32_t mask); + void tpm2_tpms_pcr_selection_add(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b); + void tpm2_tpms_pcr_selection_sub(TPMS_PCR_SELECTION *a, const TPMS_PCR_SELECTION *b); + void tpm2_tpms_pcr_selection_move(TPMS_PCR_SELECTION *a, TPMS_PCR_SELECTION *b); +@@ -113,6 +116,9 @@ size_t tpm2_tpms_pcr_selection_weight(const TPMS_PCR_SELECTION *s); + + uint32_t tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash); + void tpm2_tpml_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash, TPML_PCR_SELECTION *ret); ++bool tpm2_tpml_pcr_selection_has_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t mask); ++void tpm2_tpml_pcr_selection_add_mask(TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t mask); ++void tpm2_tpml_pcr_selection_sub_mask(TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash, uint32_t mask); + void tpm2_tpml_pcr_selection_add_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s); + void tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(TPML_PCR_SELECTION *l, const TPMS_PCR_SELECTION *s); + void tpm2_tpml_pcr_selection_add(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION *b); diff --git a/SOURCES/0564-tpm2-add-Tpm2PCRValue-struct-and-associated-function.patch b/SOURCES/0564-tpm2-add-Tpm2PCRValue-struct-and-associated-function.patch new file mode 100644 index 0000000..f2d8164 --- /dev/null +++ b/SOURCES/0564-tpm2-add-Tpm2PCRValue-struct-and-associated-function.patch @@ -0,0 +1,611 @@ +From 1f5a825144e492e54b68373d035bd2889bffc81b Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 11 Jul 2023 21:23:36 -0400 +Subject: [PATCH] tpm2: add Tpm2PCRValue struct and associated functions + +Add a new struct that can represent a PCR index, hash, and value all +together. This replaces code (e.g. the tpm2_pcr_read() parameters) that +required using both a TPML_PCR_SELECTION as well as array of TPM2B_DIGEST +entries, which was difficult to correlate the selection hash/index to each +digest. + +(cherry picked from commit 323eb4803a29a9cc255aa16ef7cab3a00429b146) + +Related: RHEL-16182 +--- + .../cryptsetup-token-systemd-tpm2.c | 2 +- + src/cryptsetup/cryptsetup.c | 2 +- + src/shared/tpm2-util.c | 419 ++++++++++++++++-- + src/shared/tpm2-util.h | 25 +- + 4 files changed, 399 insertions(+), 49 deletions(-) + +diff --git a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +index aab3a4b4c0..94d568c17f 100644 +--- a/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tokens/cryptsetup-token-systemd-tpm2.c +@@ -278,7 +278,7 @@ _public_ int cryptsetup_token_validate( + } + + u = json_variant_unsigned(e); +- if (!TPM2_PCR_VALID(u)) { ++ if (!TPM2_PCR_INDEX_VALID(u)) { + crypt_log_debug(cd, "TPM2 PCR number out of range."); + return 1; + } +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index 96341207b3..866141ac44 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -426,7 +426,7 @@ static int parse_one_option(const char *option) { + } + + pcr = r ? TPM_PCR_INDEX_VOLUME_KEY : UINT_MAX; +- } else if (!TPM2_PCR_VALID(pcr)) { ++ } else if (!TPM2_PCR_INDEX_VALID(pcr)) { + log_warning("Selected TPM index for measurement %u outside of allowed range 0…%u, ignoring.", pcr, TPM2_PCRS_MAX-1); + return 0; + } +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 9c0cad47c6..cef251e69c 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -19,6 +19,7 @@ + #include "parse-util.h" + #include "random-util.h" + #include "sha256.h" ++#include "sort-util.h" + #include "stat-util.h" + #include "string-table.h" + #include "time-util.h" +@@ -1516,6 +1517,318 @@ size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l) { + return weight; + } + ++bool TPM2_PCR_VALUE_VALID(const Tpm2PCRValue *pcr_value) { ++ int r; ++ ++ assert(pcr_value); ++ ++ if (!TPM2_PCR_INDEX_VALID(pcr_value->index)) { ++ log_debug("PCR index %u invalid.", pcr_value->index); ++ return false; ++ } ++ ++ /* If it contains a value, the value size must match the hash size. */ ++ if (pcr_value->value.size > 0) { ++ r = tpm2_hash_alg_to_size(pcr_value->hash); ++ if (r < 0) ++ return false; ++ ++ if ((int) pcr_value->value.size != r) { ++ log_debug("PCR hash 0x%" PRIx16 " expected size %d does not match actual size %" PRIu16 ".", ++ pcr_value->hash, r, pcr_value->value.size); ++ return false; ++ } ++ } ++ ++ return true; ++} ++ ++/* Verify all entries are valid, and consistent with each other. The requirements for consistency are: ++ * ++ * 1) all entries must be sorted in ascending order (e.g. using tpm2_sort_pcr_values()) ++ * 2) all entries must be unique, i.e. there cannot be 2 entries with the same hash and index ++ */ ++bool TPM2_PCR_VALUES_VALID(const Tpm2PCRValue *pcr_values, size_t n_pcr_values) { ++ assert(pcr_values || n_pcr_values == 0); ++ ++ for (size_t i = 0; i < n_pcr_values; i++) { ++ const Tpm2PCRValue *v = &pcr_values[i]; ++ ++ if (!TPM2_PCR_VALUE_VALID(v)) ++ return false; ++ ++ if (i == 0) ++ continue; ++ ++ const Tpm2PCRValue *l = &pcr_values[i - 1]; ++ ++ /* Hashes must be sorted in ascending order */ ++ if (v->hash < l->hash) { ++ log_debug("PCR values not in ascending order, hash %" PRIu16 " is after %" PRIu16 ".", ++ v->hash, l->hash); ++ return false; ++ } ++ ++ if (v->hash == l->hash) { ++ /* Indexes (for the same hash) must be sorted in ascending order */ ++ if (v->index < l->index) { ++ log_debug("PCR values not in ascending order, hash %" PRIu16 " index %u is after %u.", ++ v->hash, v->index, l->index); ++ return false; ++ } ++ ++ /* Indexes (for the same hash) must not be duplicates */ ++ if (v->index == l->index) { ++ log_debug("PCR values contain duplicates for hash %" PRIu16 " index %u.", ++ v->hash, v->index); ++ return false; ++ } ++ } ++ } ++ ++ return true; ++} ++ ++static int cmp_pcr_values(const Tpm2PCRValue *a, const Tpm2PCRValue *b) { ++ assert(a); ++ assert(b); ++ ++ return CMP(a->hash, b->hash) ?: CMP(a->index, b->index); ++} ++ ++/* Sort the array of Tpm2PCRValue entries in-place. This sorts first in ascending order of hash algorithm ++ * (sorting simply by the TPM2 hash algorithm number), and then sorting by pcr index. */ ++void tpm2_sort_pcr_values(Tpm2PCRValue *pcr_values, size_t n_pcr_values) { ++ typesafe_qsort(pcr_values, n_pcr_values, cmp_pcr_values); ++} ++ ++int tpm2_pcr_values_from_mask(uint32_t mask, TPMI_ALG_HASH hash, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values) { ++ _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; ++ size_t n_pcr_values = 0; ++ ++ assert(ret_pcr_values); ++ assert(ret_n_pcr_values); ++ ++ FOREACH_PCR_IN_MASK(index, mask) ++ if (!GREEDY_REALLOC_APPEND( ++ pcr_values, ++ n_pcr_values, ++ &TPM2_PCR_VALUE_MAKE(index, hash, {}), ++ 1)) ++ return log_oom_debug(); ++ ++ *ret_pcr_values = TAKE_PTR(pcr_values); ++ *ret_n_pcr_values = n_pcr_values; ++ ++ return 0; ++} ++ ++int tpm2_pcr_values_to_mask(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPMI_ALG_HASH hash, uint32_t *ret_mask) { ++ uint32_t mask = 0; ++ ++ assert(pcr_values || n_pcr_values == 0); ++ assert(ret_mask); ++ ++ if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PCR values."); ++ ++ for (size_t i = 0; i < n_pcr_values; i++) ++ if (pcr_values[i].hash == hash) ++ SET_BIT(mask, pcr_values[i].index); ++ ++ *ret_mask = mask; ++ ++ return 0; ++} ++ ++int tpm2_tpml_pcr_selection_from_pcr_values( ++ const Tpm2PCRValue *pcr_values, ++ size_t n_pcr_values, ++ TPML_PCR_SELECTION *ret_selection, ++ TPM2B_DIGEST **ret_values, ++ size_t *ret_n_values) { ++ ++ TPML_PCR_SELECTION selection = {}; ++ _cleanup_free_ TPM2B_DIGEST *values = NULL; ++ size_t n_values = 0; ++ ++ assert(pcr_values || n_pcr_values == 0); ++ ++ if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "PCR values are not valid."); ++ ++ for (size_t i = 0; i < n_pcr_values; i++) { ++ unsigned index = pcr_values[i].index; ++ TPMI_ALG_HASH hash = pcr_values[i].hash; ++ const TPM2B_DIGEST *digest = &pcr_values[i].value; ++ ++ tpm2_tpml_pcr_selection_add_mask(&selection, hash, INDEX_TO_MASK(uint32_t, index)); ++ ++ if (!GREEDY_REALLOC_APPEND(values, n_values, digest, 1)) ++ return log_oom_debug(); ++ } ++ ++ if (ret_selection) ++ *ret_selection = selection; ++ if (ret_values) ++ *ret_values = TAKE_PTR(values); ++ if (ret_n_values) ++ *ret_n_values = n_values; ++ ++ return 0; ++} ++ ++/* Count the number of different hash algorithms for all the entries. */ ++int tpm2_pcr_values_hash_count(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, size_t *ret_count) { ++ TPML_PCR_SELECTION selection; ++ int r; ++ ++ assert(pcr_values); ++ assert(ret_count); ++ ++ r = tpm2_tpml_pcr_selection_from_pcr_values( ++ pcr_values, ++ n_pcr_values, ++ &selection, ++ /* ret_values= */ NULL, ++ /* ret_n_values= */ NULL); ++ if (r < 0) ++ return r; ++ ++ *ret_count = selection.count; ++ ++ return 0; ++} ++ ++/* Parse a string argument into a Tpm2PCRValue object. ++ * ++ * The format is [:hash[=value]] where index is the index number (or name) of the PCR, e.g. 0 (or ++ * platform-code), hash is the name of the hash algorithm (e.g. sha256) and value is the hex hash digest ++ * value, optionally with a leading 0x. This does not check for validity of the fields. */ ++int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value) { ++ Tpm2PCRValue pcr_value = {}; ++ const char *p = arg; ++ int r; ++ ++ assert(arg); ++ assert(ret_pcr_value); ++ ++ _cleanup_free_ char *index = NULL; ++ r = extract_first_word(&p, &index, ":", /* flags= */ 0); ++ if (r < 1) ++ return log_error_errno(r, "Could not parse pcr value '%s': %m", p); ++ ++ r = pcr_index_from_string(index); ++ if (r < 0) ++ return log_error_errno(r, "Invalid pcr index '%s': %m", index); ++ pcr_value.index = (unsigned) r; ++ ++ if (!isempty(p)) { ++ _cleanup_free_ char *hash = NULL; ++ r = extract_first_word(&p, &hash, "=", /* flags= */ 0); ++ if (r < 1) ++ return log_error_errno(r, "Could not parse pcr hash algorithm '%s': %m", p); ++ ++ r = tpm2_hash_alg_from_string(hash); ++ if (r < 0) ++ return log_error_errno(r, "Invalid pcr hash algorithm '%s': %m", hash); ++ pcr_value.hash = (TPMI_ALG_HASH) r; ++ } ++ ++ if (!isempty(p)) { ++ /* Remove leading 0x if present */ ++ p = startswith_no_case(p, "0x") ?: p; ++ ++ _cleanup_free_ void *buf = NULL; ++ size_t buf_size = 0; ++ r = unhexmem(p, strlen(p), &buf, &buf_size); ++ if (r < 0) ++ return log_error_errno(r, "Invalid pcr hash value '%s': %m", p); ++ ++ pcr_value.value.size = buf_size; ++ assert(sizeof(pcr_value.value.buffer) >= pcr_value.value.size); ++ memcpy(pcr_value.value.buffer, buf, pcr_value.value.size); ++ } ++ ++ *ret_pcr_value = pcr_value; ++ ++ return 0; ++} ++ ++/* Return a string for the PCR value. The format is described in tpm2_pcr_value_from_string(). Note that if ++ * the hash algorithm is not recognized, neither hash name nor hash digest value is included in the ++ * string. This does not check for validity. */ ++char *tpm2_pcr_value_to_string(const Tpm2PCRValue *pcr_value) { ++ _cleanup_free_ char *index = NULL, *value = NULL; ++ int r; ++ ++ r = asprintf(&index, "%u", pcr_value->index); ++ if (r < 0) ++ return NULL; ++ ++ const char *hash = tpm2_hash_alg_to_string(pcr_value->hash); ++ ++ if (hash && pcr_value->value.size > 0) { ++ value = hexmem(pcr_value->value.buffer, pcr_value->value.size); ++ if (!value) ++ return NULL; ++ } ++ ++ return strjoin(index, hash ? ":" : "", hash ?: "", value ? "=" : "", value ?: ""); ++} ++ ++/* Parse a string argument into an array of Tpm2PCRValue objects. ++ * ++ * The format is zero or more entries separated by ',' or '+'. The format of each entry is described in ++ * tpm2_pcr_value_from_string(). This does not check for validity of the entries. */ ++int tpm2_pcr_values_from_string(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values) { ++ const char *p = arg; ++ int r; ++ ++ assert(arg); ++ assert(ret_pcr_values); ++ assert(ret_n_pcr_values); ++ ++ _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; ++ size_t n_pcr_values = 0; ++ ++ for (;;) { ++ _cleanup_free_ char *pcr_arg = NULL; ++ r = extract_first_word(&p, &pcr_arg, ",+", /* flags= */ 0); ++ if (r < 0) ++ return log_error_errno(r, "Could not parse pcr values '%s': %m", p); ++ if (r == 0) ++ break; ++ ++ Tpm2PCRValue pcr_value; ++ r = tpm2_pcr_value_from_string(pcr_arg, &pcr_value); ++ if (r < 0) ++ return r; ++ ++ if (!GREEDY_REALLOC_APPEND(pcr_values, n_pcr_values, &pcr_value, 1)) ++ return log_oom(); ++ } ++ ++ *ret_pcr_values = TAKE_PTR(pcr_values); ++ *ret_n_pcr_values = n_pcr_values; ++ ++ return 0; ++} ++ ++/* Return a string representing the array of PCR values. The format is as described in ++ * tpm2_pcr_values_from_string(). This does not check for validity. */ ++char *tpm2_pcr_values_to_string(const Tpm2PCRValue *pcr_values, size_t n_pcr_values) { ++ _cleanup_free_ char *s = NULL; ++ ++ for (size_t i = 0; i < n_pcr_values; i++) { ++ _cleanup_free_ char *pcrstr = tpm2_pcr_value_to_string(&pcr_values[i]); ++ if (!pcrstr || !strextend_with_separator(&s, "+", pcrstr)) ++ return NULL; ++ } ++ ++ return s ? TAKE_PTR(s) : strdup(""); ++} ++ + static void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg) { + if (!DEBUG_LOGGING || !l) + return; +@@ -1524,6 +1837,14 @@ static void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const + log_debug("%s: %s", msg ?: "PCR selection", strna(s)); + } + ++static void tpm2_log_debug_pcr_value(const Tpm2PCRValue *pcr_value, const char *msg) { ++ if (!DEBUG_LOGGING || !pcr_value) ++ return; ++ ++ _cleanup_free_ char *s = tpm2_pcr_value_to_string(pcr_value); ++ log_debug("%s: %s", msg ?: "PCR value", strna(s)); ++} ++ + static void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg) { + if (!DEBUG_LOGGING || !buffer || size == 0) + return; +@@ -1919,22 +2240,27 @@ int tpm2_create_loaded( + return 0; + } + ++/* Read hash values from the specified PCR selection. Provides a Tpm2PCRValue array that contains all ++ * requested PCR values, in the order provided by the TPM. Normally, the provided pcr values will match ++ * exactly what is in the provided selection, but the TPM may ignore some selected PCRs (for example, if an ++ * unimplemented PCR index is requested), in which case those PCRs will be absent from the provided pcr ++ * values. */ + static int tpm2_pcr_read( + Tpm2Context *c, + const TPML_PCR_SELECTION *pcr_selection, +- TPML_PCR_SELECTION *ret_pcr_selection, +- TPM2B_DIGEST **ret_pcr_values, ++ Tpm2PCRValue **ret_pcr_values, + size_t *ret_n_pcr_values) { + +- _cleanup_free_ TPM2B_DIGEST *pcr_values = NULL; +- TPML_PCR_SELECTION remaining, total_read = {}; ++ _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; + size_t n_pcr_values = 0; + TSS2_RC rc; + + assert(c); + assert(pcr_selection); ++ assert(ret_pcr_values); ++ assert(ret_n_pcr_values); + +- remaining = *pcr_selection; ++ TPML_PCR_SELECTION remaining = *pcr_selection; + while (!tpm2_tpml_pcr_selection_is_empty(&remaining)) { + _cleanup_(Esys_Freep) TPML_PCR_SELECTION *current_read = NULL; + _cleanup_(Esys_Freep) TPML_DIGEST *current_values = NULL; +@@ -1955,44 +2281,39 @@ static int tpm2_pcr_read( + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to read TPM2 PCRs: %s", sym_Tss2_RC_Decode(rc)); + ++ tpm2_log_debug_tpml_pcr_selection(current_read, "Read PCR selection"); ++ + if (tpm2_tpml_pcr_selection_is_empty(current_read)) { + log_warning("TPM2 refused to read possibly unimplemented PCRs, ignoring."); + break; + } + +- tpm2_tpml_pcr_selection_sub(&remaining, current_read); +- tpm2_tpml_pcr_selection_add(&total_read, current_read); ++ unsigned i = 0; ++ FOREACH_PCR_IN_TPML_PCR_SELECTION(index, tpms, current_read) { ++ assert(i < current_values->count); ++ Tpm2PCRValue pcr_value = { ++ .index = index, ++ .hash = tpms->hash, ++ .value = current_values->digests[i++], ++ }; + +- if (!GREEDY_REALLOC(pcr_values, n_pcr_values + current_values->count)) +- return log_oom(); ++ tpm2_log_debug_pcr_value(&pcr_value, /* msg= */ NULL); + +- memcpy_safe(&pcr_values[n_pcr_values], current_values->digests, +- current_values->count * sizeof(TPM2B_DIGEST)); +- n_pcr_values += current_values->count; ++ if (!GREEDY_REALLOC_APPEND(pcr_values, n_pcr_values, &pcr_value, 1)) ++ return log_oom(); ++ } ++ assert(i == current_values->count); + +- if (DEBUG_LOGGING) { +- unsigned i = 0; +- FOREACH_PCR_IN_TPML_PCR_SELECTION(pcr, s, current_read) { +- assert(i < current_values->count); ++ tpm2_tpml_pcr_selection_sub(&remaining, current_read); ++ } + +- TPM2B_DIGEST *d = ¤t_values->digests[i]; +- i++; ++ tpm2_sort_pcr_values(pcr_values, n_pcr_values); + +- TPML_PCR_SELECTION l; +- tpm2_tpml_pcr_selection_from_mask(INDEX_TO_MASK(uint32_t, pcr), s->hash, &l); ++ if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PCR values read from TPM are not valid."); + +- _cleanup_free_ char *desc = tpm2_tpml_pcr_selection_to_string(&l); +- tpm2_log_debug_digest(d, strna(desc)); +- } +- } +- } +- +- if (ret_pcr_selection) +- *ret_pcr_selection = total_read; +- if (ret_pcr_values) +- *ret_pcr_values = TAKE_PTR(pcr_values); +- if (ret_n_pcr_values) +- *ret_n_pcr_values = n_pcr_values; ++ *ret_pcr_values = TAKE_PTR(pcr_values); ++ *ret_n_pcr_values = n_pcr_values; + + return 0; + } +@@ -2002,9 +2323,7 @@ static int tpm2_pcr_mask_good( + TPMI_ALG_HASH bank, + uint32_t mask) { + +- _cleanup_free_ TPM2B_DIGEST *pcr_values = NULL; + TPML_PCR_SELECTION selection; +- size_t n_pcr_values = 0; + int r; + + assert(c); +@@ -2015,22 +2334,18 @@ static int tpm2_pcr_mask_good( + + tpm2_tpml_pcr_selection_from_mask(mask, bank, &selection); + +- r = tpm2_pcr_read(c, &selection, &selection, &pcr_values, &n_pcr_values); ++ _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; ++ size_t n_pcr_values; ++ r = tpm2_pcr_read(c, &selection, &pcr_values, &n_pcr_values); + if (r < 0) + return r; + + /* If at least one of the selected PCR values is something other than all 0x00 or all 0xFF we are happy. */ +- unsigned i = 0; +- FOREACH_PCR_IN_TPML_PCR_SELECTION(pcr, s, &selection) { +- assert(i < n_pcr_values); +- +- if (!memeqbyte(0x00, pcr_values[i].buffer, pcr_values[i].size) && +- !memeqbyte(0xFF, pcr_values[i].buffer, pcr_values[i].size)) ++ for (unsigned i = 0; i < n_pcr_values; i++) ++ if (!memeqbyte(0x00, pcr_values[i].value.buffer, pcr_values[i].value.size) && ++ !memeqbyte(0xFF, pcr_values[i].value.buffer, pcr_values[i].value.size)) + return true; + +- i++; +- } +- + return false; + } + +@@ -3254,14 +3569,26 @@ int tpm2_seal(const char *device, + + TPML_PCR_SELECTION hash_pcr_selection = {}; + _cleanup_free_ TPM2B_DIGEST *hash_pcr_values = NULL; +- size_t n_hash_pcr_values = 0; ++ size_t n_hash_pcr_values; + if (hash_pcr_mask) { + /* For now, we just read the current values from the system; we need to be able to specify + * expected values, eventually. */ + tpm2_tpml_pcr_selection_from_mask(hash_pcr_mask, pcr_bank, &hash_pcr_selection); +- r = tpm2_pcr_read(c, &hash_pcr_selection, &hash_pcr_selection, &hash_pcr_values, &n_hash_pcr_values); ++ ++ _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; ++ size_t n_pcr_values; ++ r = tpm2_pcr_read(c, &hash_pcr_selection, &pcr_values, &n_pcr_values); + if (r < 0) + return r; ++ ++ r = tpm2_tpml_pcr_selection_from_pcr_values( ++ pcr_values, ++ n_pcr_values, ++ &hash_pcr_selection, ++ &hash_pcr_values, ++ &n_hash_pcr_values); ++ if (r < 0) ++ return log_error_errno(r, "Could not get PCR selection from values: %m"); + } + + TPM2B_PUBLIC pubkey_tpm2, *authorize_key = NULL; +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index c024245915..2e25866401 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -17,7 +17,7 @@ typedef enum TPM2Flags { + * TPM2 on a Client PC must have at least 24 PCRs. This hardcodes our expectation of 24. */ + #define TPM2_PCRS_MAX 24U + #define TPM2_PCRS_MASK ((UINT32_C(1) << TPM2_PCRS_MAX) - 1) +-static inline bool TPM2_PCR_VALID(unsigned pcr) { ++static inline bool TPM2_PCR_INDEX_VALID(unsigned pcr) { + return pcr < TPM2_PCRS_MAX; + } + static inline bool TPM2_PCR_MASK_VALID(uint32_t pcr_mask) { +@@ -88,6 +88,26 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle); + Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle); + DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free); + ++typedef struct { ++ unsigned index; ++ TPMI_ALG_HASH hash; ++ TPM2B_DIGEST value; ++} Tpm2PCRValue; ++ ++#define TPM2_PCR_VALUE_MAKE(i, h, v) (Tpm2PCRValue) { .index = (i), .hash = (h), .value = ((TPM2B_DIGEST) v), } ++bool TPM2_PCR_VALUE_VALID(const Tpm2PCRValue *pcr_value); ++int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value); ++char *tpm2_pcr_value_to_string(const Tpm2PCRValue *pcr_value); ++ ++bool TPM2_PCR_VALUES_VALID(const Tpm2PCRValue *pcr_values, size_t n_pcr_values); ++void tpm2_sort_pcr_values(Tpm2PCRValue *pcr_values, size_t n_pcr_values); ++int tpm2_pcr_values_from_mask(uint32_t mask, TPMI_ALG_HASH hash, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); ++int tpm2_pcr_values_to_mask(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPMI_ALG_HASH hash, uint32_t *ret_mask); ++int tpm2_pcr_values_from_string(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); ++char *tpm2_pcr_values_to_string(const Tpm2PCRValue *pcr_values, size_t n_pcr_values); ++int tpm2_pcr_values_hash_count(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, size_t *ret_count); ++int tpm2_tpml_pcr_selection_from_pcr_values(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPML_PCR_SELECTION *ret_selection, TPM2B_DIGEST **ret_values, size_t *ret_n_values); ++ + int tpm2_create_primary(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_PUBLIC *template, const TPM2B_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, Tpm2Handle **ret_handle); + int tpm2_create(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPMT_PUBLIC *template, const TPMS_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, TPM2B_PRIVATE **ret_private); + int tpm2_create_loaded(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPMT_PUBLIC *template, const TPMS_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, TPM2B_PRIVATE **ret_private, Tpm2Handle **ret_handle); +@@ -130,6 +150,9 @@ size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l); + #else /* HAVE_TPM2 */ + typedef struct {} Tpm2Context; + typedef struct {} Tpm2Handle; ++typedef struct {} Tpm2PCRValue; ++ ++#define TPM2_PCR_VALUE_MAKE(i, h, v) (Tpm2PCRValue) {} + #endif /* HAVE_TPM2 */ + + int tpm2_list_devices(void); diff --git a/SOURCES/0565-tpm2-move-declared-functions-in-header-lower-down.patch b/SOURCES/0565-tpm2-move-declared-functions-in-header-lower-down.patch new file mode 100644 index 0000000..105d0fd --- /dev/null +++ b/SOURCES/0565-tpm2-move-declared-functions-in-header-lower-down.patch @@ -0,0 +1,76 @@ +From 4a7874eaf1810b010fd420ba2690b2f28ca40f15 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 14 Jul 2023 11:38:11 -0400 +Subject: [PATCH] tpm2: move declared functions in header lower down + +Move some function declarations lower down, below the Tpm2Context and +Tpm2Handle typedefs; later commits will reference the typedefs in some of the +functions, so the typedefs need to come first in the header. + +This only moves the declarations, none of the declarations are modified. + +(cherry picked from commit e00f46ac7ff47819602c87f5078d90f676e64e1f) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.h | 40 ++++++++++++++++++++-------------------- + 1 file changed, 20 insertions(+), 20 deletions(-) + +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 2e25866401..52016ca8d7 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -34,26 +34,6 @@ static inline bool TPM2_PCR_MASK_VALID(uint32_t pcr_mask) { + + int dlopen_tpm2(void); + +-int tpm2_digest_many(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const struct iovec data[], size_t count, bool extend); +-static inline int tpm2_digest_buffer(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const void *data, size_t len, bool extend) { +- return tpm2_digest_many(alg, digest, &IOVEC_MAKE((void*) data, len), 1, extend); +-} +-int tpm2_digest_many_digests(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const TPM2B_DIGEST data[], size_t count, bool extend); +-static inline int tpm2_digest_rehash(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { +- return tpm2_digest_many(alg, digest, NULL, 0, true); +-} +-static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { +- return tpm2_digest_many(alg, digest, NULL, 0, false); +-} +- +-int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); +-int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest); +-int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGEST *policy_ref, TPM2B_DIGEST *digest); +-int tpm2_calculate_policy_pcr(const TPML_PCR_SELECTION *pcr_selection, const TPM2B_DIGEST pcr_values[], size_t pcr_values_count, TPM2B_DIGEST *digest); +- +-int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); +-int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); +- + typedef struct { + unsigned n_ref; + +@@ -147,6 +127,26 @@ char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l); + size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l); + #define tpm2_tpml_pcr_selection_is_empty(l) (tpm2_tpml_pcr_selection_weight(l) == 0) + ++int tpm2_digest_many(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const struct iovec data[], size_t count, bool extend); ++static inline int tpm2_digest_buffer(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const void *data, size_t len, bool extend) { ++ return tpm2_digest_many(alg, digest, &IOVEC_MAKE((void*) data, len), 1, extend); ++} ++int tpm2_digest_many_digests(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest, const TPM2B_DIGEST data[], size_t count, bool extend); ++static inline int tpm2_digest_rehash(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { ++ return tpm2_digest_many(alg, digest, NULL, 0, true); ++} ++static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { ++ return tpm2_digest_many(alg, digest, NULL, 0, false); ++} ++ ++int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); ++int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest); ++int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGEST *policy_ref, TPM2B_DIGEST *digest); ++int tpm2_calculate_policy_pcr(const TPML_PCR_SELECTION *pcr_selection, const TPM2B_DIGEST pcr_values[], size_t pcr_values_count, TPM2B_DIGEST *digest); ++ ++int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); ++int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); ++ + #else /* HAVE_TPM2 */ + typedef struct {} Tpm2Context; + typedef struct {} Tpm2Handle; diff --git a/SOURCES/0566-tpm2-declare-tpm2_log_debug_-functions-in-tpm2_util..patch b/SOURCES/0566-tpm2-declare-tpm2_log_debug_-functions-in-tpm2_util..patch new file mode 100644 index 0000000..7e09f8b --- /dev/null +++ b/SOURCES/0566-tpm2-declare-tpm2_log_debug_-functions-in-tpm2_util..patch @@ -0,0 +1,79 @@ +From a7151f54a163b9b559b30d31f2d252c0c3b953a7 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 14 Jul 2023 07:23:55 -0400 +Subject: [PATCH] tpm2: declare tpm2_log_debug_*() functions in tpm2_util.h + +Allow other code to use the log debug functions; e.g. they are useful in test +code. + +(cherry picked from commit 75de375aafa78ccf2cd93a2c0a15d3bc4b0fe4b7) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 10 +++++----- + src/shared/tpm2-util.h | 6 ++++++ + 2 files changed, 11 insertions(+), 5 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index cef251e69c..c3e1ca8f3a 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1829,7 +1829,7 @@ char *tpm2_pcr_values_to_string(const Tpm2PCRValue *pcr_values, size_t n_pcr_val + return s ? TAKE_PTR(s) : strdup(""); + } + +-static void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg) { ++void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg) { + if (!DEBUG_LOGGING || !l) + return; + +@@ -1837,7 +1837,7 @@ static void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const + log_debug("%s: %s", msg ?: "PCR selection", strna(s)); + } + +-static void tpm2_log_debug_pcr_value(const Tpm2PCRValue *pcr_value, const char *msg) { ++void tpm2_log_debug_pcr_value(const Tpm2PCRValue *pcr_value, const char *msg) { + if (!DEBUG_LOGGING || !pcr_value) + return; + +@@ -1845,7 +1845,7 @@ static void tpm2_log_debug_pcr_value(const Tpm2PCRValue *pcr_value, const char * + log_debug("%s: %s", msg ?: "PCR value", strna(s)); + } + +-static void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg) { ++void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg) { + if (!DEBUG_LOGGING || !buffer || size == 0) + return; + +@@ -1853,12 +1853,12 @@ static void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *m + log_debug("%s: %s", msg ?: "Buffer", strna(h)); + } + +-static void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg) { ++void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg) { + if (digest) + tpm2_log_debug_buffer(digest->buffer, digest->size, msg ?: "Digest"); + } + +-static void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg) { ++void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg) { + if (name) + tpm2_log_debug_buffer(name->name, name->size, msg ?: "Name"); + } +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 52016ca8d7..cecf35af4d 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -139,6 +139,12 @@ static inline int tpm2_digest_init(TPMI_ALG_HASH alg, TPM2B_DIGEST *digest) { + return tpm2_digest_many(alg, digest, NULL, 0, false); + } + ++void tpm2_log_debug_tpml_pcr_selection(const TPML_PCR_SELECTION *l, const char *msg); ++void tpm2_log_debug_pcr_value(const Tpm2PCRValue *pcr_value, const char *msg); ++void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg); ++void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg); ++void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg); ++ + int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); + int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest); + int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGEST *policy_ref, TPM2B_DIGEST *digest); diff --git a/SOURCES/0567-tpm2-change-tpm2_calculate_policy_pcr-tpm2_calculate.patch b/SOURCES/0567-tpm2-change-tpm2_calculate_policy_pcr-tpm2_calculate.patch new file mode 100644 index 0000000..cb9d698 --- /dev/null +++ b/SOURCES/0567-tpm2-change-tpm2_calculate_policy_pcr-tpm2_calculate.patch @@ -0,0 +1,306 @@ +From 8e4e838d22ef7e462fa97f8d77e3d8c3904b2dba Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 12 Jul 2023 22:14:18 -0400 +Subject: [PATCH] tpm2: change tpm2_calculate_policy_pcr(), + tpm2_calculate_sealing_policy() to use Tpm2PCRValue array + +An array of Tpm2PCRValue objects effectively replaces a TPML_PCR_SELECTION +object combined with an array of (properly ordered) TPM2B_DIGEST objects. + +Also update tpm2_calculate_sealing_policy() pin parameter to boolean use_pin, +since the function does not need to know the pin value, only if a pin is being +used. + +(cherry picked from commit 6e8fb3ad5ff2dab03b9e2b189adaf463c06a8101) + +Related: RHEL-16182 +--- + src/boot/measure.c | 15 ++++---- + src/shared/tpm2-util.c | 51 ++++++++++++-------------- + src/shared/tpm2-util.h | 2 +- + src/test/test-tpm2.c | 82 +++++++++++++++++++++++------------------- + 4 files changed, 74 insertions(+), 76 deletions(-) + +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 5ce3049147..1d696e1bd9 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -799,23 +799,20 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + if (tpmalg < 0) + return log_error_errno(tpmalg, "Unsupported PCR bank"); + +- TPML_PCR_SELECTION pcr_selection; +- tpm2_tpml_pcr_selection_from_mask(1 << TPM_PCR_INDEX_KERNEL_IMAGE, +- tpmalg, +- &pcr_selection); +- +- TPM2B_DIGEST pcr_values = { ++ TPM2B_DIGEST pcr_digest = { + .size = p->value_size, + }; +- assert(sizeof(pcr_values.buffer) >= p->value_size); +- memcpy_safe(pcr_values.buffer, p->value, p->value_size); ++ assert(sizeof(pcr_digest.buffer) >= p->value_size); ++ memcpy_safe(pcr_digest.buffer, p->value, p->value_size); ++ ++ Tpm2PCRValue pcr_value = TPM2_PCR_VALUE_MAKE(TPM_PCR_INDEX_KERNEL_IMAGE, tpmalg, pcr_digest); + + TPM2B_DIGEST pcr_policy_digest; + r = tpm2_digest_init(TPM2_ALG_SHA256, &pcr_policy_digest); + if (r < 0) + return r; + +- r = tpm2_calculate_policy_pcr(&pcr_selection, &pcr_values, 1, &pcr_policy_digest); ++ r = tpm2_calculate_policy_pcr(&pcr_value, 1, &pcr_policy_digest); + if (r < 0) + return r; + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index c3e1ca8f3a..50a01f55a6 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -3152,8 +3152,7 @@ static int tpm2_policy_auth_value( + + /* Extend 'digest' with the PolicyPCR calculated hash. */ + int tpm2_calculate_policy_pcr( +- const TPML_PCR_SELECTION *pcr_selection, +- const TPM2B_DIGEST pcr_values[], ++ const Tpm2PCRValue *pcr_values, + size_t n_pcr_values, + TPM2B_DIGEST *digest) { + +@@ -3161,7 +3160,6 @@ int tpm2_calculate_policy_pcr( + TSS2_RC rc; + int r; + +- assert(pcr_selection); + assert(pcr_values || n_pcr_values == 0); + assert(digest); + assert(digest->size == SHA256_DIGEST_SIZE); +@@ -3170,13 +3168,20 @@ int tpm2_calculate_policy_pcr( + if (r < 0) + return log_error_errno(r, "TPM2 support not installed: %m"); + ++ TPML_PCR_SELECTION pcr_selection; ++ _cleanup_free_ TPM2B_DIGEST *values = NULL; ++ size_t n_values; ++ r = tpm2_tpml_pcr_selection_from_pcr_values(pcr_values, n_pcr_values, &pcr_selection, &values, &n_values); ++ if (r < 0) ++ return log_error_errno(r, "Could not convert PCR values to TPML_PCR_SELECTION: %m"); ++ + TPM2B_DIGEST hash = {}; +- r = tpm2_digest_many_digests(TPM2_ALG_SHA256, &hash, pcr_values, n_pcr_values, /* extend= */ false); ++ r = tpm2_digest_many_digests(TPM2_ALG_SHA256, &hash, values, n_values, /* extend= */ false); + if (r < 0) + return r; + + _cleanup_free_ uint8_t *buf = NULL; +- size_t size = 0, maxsize = sizeof(command) + sizeof(*pcr_selection); ++ size_t size = 0, maxsize = sizeof(command) + sizeof(pcr_selection); + + buf = malloc(maxsize); + if (!buf) +@@ -3187,7 +3192,7 @@ int tpm2_calculate_policy_pcr( + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal PolicyPCR command: %s", sym_Tss2_RC_Decode(rc)); + +- rc = sym_Tss2_MU_TPML_PCR_SELECTION_Marshal(pcr_selection, buf, maxsize, &size); ++ rc = sym_Tss2_MU_TPML_PCR_SELECTION_Marshal(&pcr_selection, buf, maxsize, &size); + if (rc != TSS2_RC_SUCCESS) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal PCR selection: %s", sym_Tss2_RC_Decode(rc)); +@@ -3413,15 +3418,15 @@ static int tpm2_policy_authorize( + + /* Extend 'digest' with the calculated policy hash. */ + static int tpm2_calculate_sealing_policy( +- const TPML_PCR_SELECTION *hash_pcr_selection, +- const TPM2B_DIGEST *hash_pcr_values, +- size_t n_hash_pcr_values, ++ const Tpm2PCRValue *pcr_values, ++ size_t n_pcr_values, + const TPM2B_PUBLIC *public, +- const char *pin, ++ bool use_pin, + TPM2B_DIGEST *digest) { + + int r; + ++ assert(pcr_values || n_pcr_values == 0); + assert(digest); + + if (public) { +@@ -3430,13 +3435,13 @@ static int tpm2_calculate_sealing_policy( + return r; + } + +- if (hash_pcr_selection && !tpm2_tpml_pcr_selection_is_empty(hash_pcr_selection)) { +- r = tpm2_calculate_policy_pcr(hash_pcr_selection, hash_pcr_values, n_hash_pcr_values, digest); ++ if (n_pcr_values > 0) { ++ r = tpm2_calculate_policy_pcr(pcr_values, n_pcr_values, digest); + if (r < 0) + return r; + } + +- if (pin) { ++ if (use_pin) { + r = tpm2_calculate_policy_auth_value(digest); + if (r < 0) + return r; +@@ -3567,28 +3572,17 @@ int tpm2_seal(const char *device, + return r; + } + +- TPML_PCR_SELECTION hash_pcr_selection = {}; +- _cleanup_free_ TPM2B_DIGEST *hash_pcr_values = NULL; ++ _cleanup_free_ Tpm2PCRValue *hash_pcr_values = NULL; + size_t n_hash_pcr_values; + if (hash_pcr_mask) { + /* For now, we just read the current values from the system; we need to be able to specify + * expected values, eventually. */ ++ TPML_PCR_SELECTION hash_pcr_selection; + tpm2_tpml_pcr_selection_from_mask(hash_pcr_mask, pcr_bank, &hash_pcr_selection); + +- _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; +- size_t n_pcr_values; +- r = tpm2_pcr_read(c, &hash_pcr_selection, &pcr_values, &n_pcr_values); ++ r = tpm2_pcr_read(c, &hash_pcr_selection, &hash_pcr_values, &n_hash_pcr_values); + if (r < 0) + return r; +- +- r = tpm2_tpml_pcr_selection_from_pcr_values( +- pcr_values, +- n_pcr_values, +- &hash_pcr_selection, +- &hash_pcr_values, +- &n_hash_pcr_values); +- if (r < 0) +- return log_error_errno(r, "Could not get PCR selection from values: %m"); + } + + TPM2B_PUBLIC pubkey_tpm2, *authorize_key = NULL; +@@ -3605,11 +3599,10 @@ int tpm2_seal(const char *device, + return r; + + r = tpm2_calculate_sealing_policy( +- &hash_pcr_selection, + hash_pcr_values, + n_hash_pcr_values, + authorize_key, +- pin, ++ !!pin, + &policy_digest); + if (r < 0) + return r; +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index cecf35af4d..c6e9339e0e 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -148,7 +148,7 @@ void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg); + int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); + int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest); + int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGEST *policy_ref, TPM2B_DIGEST *digest); +-int tpm2_calculate_policy_pcr(const TPML_PCR_SELECTION *pcr_selection, const TPM2B_DIGEST pcr_values[], size_t pcr_values_count, TPM2B_DIGEST *digest); ++int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); + + int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); + int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index c61bbf6d94..8a4e9f5142 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -618,50 +618,58 @@ TEST(calculate_policy_authorize) { + } + + TEST(calculate_policy_pcr) { +- TPML_PCR_SELECTION pcr_selection; +- TPM2B_DIGEST pcr_values[16]; +- TPM2B_DIGEST d; +- uint32_t pcr_mask; ++ TPM2B_DIGEST d, dN[16]; ++ ++ digest_init_sha256(&dN[ 0], "2124793cbbe60c3a8637d3b84a5d054e87c351e1469a285acc04755e8b204dec"); ++ digest_init_sha256(&dN[ 1], "bf7592f18adcfdc549fc0b94939f5069a24697f9cff4a0dca29014767b97559d"); ++ digest_init_sha256(&dN[ 2], "4b00cff9dee3a364979b2dc241b34568a8ad49fcf2713df259e47dff8875feed"); ++ digest_init_sha256(&dN[ 3], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); ++ digest_init_sha256(&dN[ 4], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); ++ digest_init_sha256(&dN[ 5], "c97c40369691c8e4aa78fb3a52655cd193b780a838b8e23f5f476576919db5e5"); ++ digest_init_sha256(&dN[ 6], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); ++ digest_init_sha256(&dN[ 7], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); ++ digest_init_sha256(&dN[ 8], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); ++ digest_init_sha256(&dN[ 9], "9c2bac22ef5ec84fcdb71c3ebf776cba1247e5da980e5ee08e45666a2edf0b8b"); ++ digest_init_sha256(&dN[10], "9885873f4d7348199ad286f8f2476d4f866940950f6f9fb9f945ed352dbdcbd2"); ++ digest_init_sha256(&dN[11], "42400ab950d21aa79d12cc4fdef67d1087a39ad64900619831c0974dbae54e44"); ++ digest_init_sha256(&dN[12], "767d064382e56ca1ad3bdcc6bc596112e6c2008b593d3570d24c2bfa64c4628c"); ++ digest_init_sha256(&dN[13], "30c16133175959408c9745d8dafadef5daf4b39cb2be04df0d60089bd46d3cc4"); ++ digest_init_sha256(&dN[14], "e3991b7ddd47be7e92726a832d6874c5349b52b789fa0db8b558c69fea29574e"); ++ digest_init_sha256(&dN[15], "852dae3ecb992bdeb13d6002fefeeffdd90feca8b378d56681ef2c885d0e5137"); + + digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); +- pcr_mask = (1<<4) | (1<<7) | (1<<8); +- tpm2_tpml_pcr_selection_from_mask(pcr_mask, TPM2_ALG_SHA256, &pcr_selection); +- digest_init_sha256(&pcr_values[0], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); +- digest_init_sha256(&pcr_values[1], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); +- digest_init_sha256(&pcr_values[2], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); +- assert_se(tpm2_calculate_policy_pcr(&pcr_selection, pcr_values, 3, &d) == 0); ++ Tpm2PCRValue v1[] = { ++ TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA256, dN[4]), ++ TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA256, dN[7]), ++ TPM2_PCR_VALUE_MAKE(8, TPM2_ALG_SHA256, dN[8]), ++ }; ++ assert_se(tpm2_calculate_policy_pcr(v1, ELEMENTSOF(v1), &d) == 0); + assert_se(digest_check(&d, "76532a0e16f7e6bf6b02918c11f75d99d729fab0cc81d0df2c4284a2c4fe6e05")); +- +- pcr_mask = (1<<4) | (1<<7) | (1<<8); +- tpm2_tpml_pcr_selection_from_mask(pcr_mask, TPM2_ALG_SHA256, &pcr_selection); +- digest_init_sha256(&pcr_values[0], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); +- digest_init_sha256(&pcr_values[1], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); +- digest_init_sha256(&pcr_values[2], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); +- assert_se(tpm2_calculate_policy_pcr(&pcr_selection, pcr_values, 3, &d) == 0); ++ assert_se(tpm2_calculate_policy_pcr(v1, ELEMENTSOF(v1), &d) == 0); + assert_se(digest_check(&d, "97e64bcabb64c1fa4b726528644926c8029f5b4458b0575c98c04fe225629a0b")); + + digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); +- pcr_mask = 0xffff; +- tpm2_tpml_pcr_selection_from_mask(pcr_mask, TPM2_ALG_SHA256, &pcr_selection); +- digest_init_sha256(&pcr_values[ 0], "2124793cbbe60c3a8637d3b84a5d054e87c351e1469a285acc04755e8b204dec"); +- digest_init_sha256(&pcr_values[ 1], "bf7592f18adcfdc549fc0b94939f5069a24697f9cff4a0dca29014767b97559d"); +- digest_init_sha256(&pcr_values[ 2], "4b00cff9dee3a364979b2dc241b34568a8ad49fcf2713df259e47dff8875feed"); +- digest_init_sha256(&pcr_values[ 3], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); +- digest_init_sha256(&pcr_values[ 4], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); +- digest_init_sha256(&pcr_values[ 5], "c97c40369691c8e4aa78fb3a52655cd193b780a838b8e23f5f476576919db5e5"); +- digest_init_sha256(&pcr_values[ 6], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); +- digest_init_sha256(&pcr_values[ 7], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); +- digest_init_sha256(&pcr_values[ 8], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); +- digest_init_sha256(&pcr_values[ 9], "9c2bac22ef5ec84fcdb71c3ebf776cba1247e5da980e5ee08e45666a2edf0b8b"); +- digest_init_sha256(&pcr_values[10], "9885873f4d7348199ad286f8f2476d4f866940950f6f9fb9f945ed352dbdcbd2"); +- digest_init_sha256(&pcr_values[11], "42400ab950d21aa79d12cc4fdef67d1087a39ad64900619831c0974dbae54e44"); +- digest_init_sha256(&pcr_values[12], "767d064382e56ca1ad3bdcc6bc596112e6c2008b593d3570d24c2bfa64c4628c"); +- digest_init_sha256(&pcr_values[13], "30c16133175959408c9745d8dafadef5daf4b39cb2be04df0d60089bd46d3cc4"); +- digest_init_sha256(&pcr_values[14], "e3991b7ddd47be7e92726a832d6874c5349b52b789fa0db8b558c69fea29574e"); +- digest_init_sha256(&pcr_values[15], "852dae3ecb992bdeb13d6002fefeeffdd90feca8b378d56681ef2c885d0e5137"); +- assert_se(tpm2_calculate_policy_pcr(&pcr_selection, pcr_values, 16, &d) == 0); ++ Tpm2PCRValue v2[] = { ++ TPM2_PCR_VALUE_MAKE( 0, TPM2_ALG_SHA256, dN[ 0]), ++ TPM2_PCR_VALUE_MAKE( 1, TPM2_ALG_SHA256, dN[ 1]), ++ TPM2_PCR_VALUE_MAKE( 2, TPM2_ALG_SHA256, dN[ 2]), ++ TPM2_PCR_VALUE_MAKE( 3, TPM2_ALG_SHA256, dN[ 3]), ++ TPM2_PCR_VALUE_MAKE( 4, TPM2_ALG_SHA256, dN[ 4]), ++ TPM2_PCR_VALUE_MAKE( 5, TPM2_ALG_SHA256, dN[ 5]), ++ TPM2_PCR_VALUE_MAKE( 6, TPM2_ALG_SHA256, dN[ 6]), ++ TPM2_PCR_VALUE_MAKE( 7, TPM2_ALG_SHA256, dN[ 7]), ++ TPM2_PCR_VALUE_MAKE( 8, TPM2_ALG_SHA256, dN[ 8]), ++ TPM2_PCR_VALUE_MAKE( 9, TPM2_ALG_SHA256, dN[ 9]), ++ TPM2_PCR_VALUE_MAKE(10, TPM2_ALG_SHA256, dN[10]), ++ TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA256, dN[11]), ++ TPM2_PCR_VALUE_MAKE(12, TPM2_ALG_SHA256, dN[12]), ++ TPM2_PCR_VALUE_MAKE(13, TPM2_ALG_SHA256, dN[13]), ++ TPM2_PCR_VALUE_MAKE(14, TPM2_ALG_SHA256, dN[14]), ++ TPM2_PCR_VALUE_MAKE(15, TPM2_ALG_SHA256, dN[15]), ++ }; ++ assert_se(tpm2_calculate_policy_pcr(v2, ELEMENTSOF(v2), &d) == 0); + assert_se(digest_check(&d, "22be4f1674f792d6345cea9427701068f0e8d9f42755dcc0e927e545a68f9c13")); +- assert_se(tpm2_calculate_policy_pcr(&pcr_selection, pcr_values, 16, &d) == 0); ++ assert_se(tpm2_calculate_policy_pcr(v2, ELEMENTSOF(v2), &d) == 0); + assert_se(digest_check(&d, "7481fd1b116078eb3ac2456e4ad542c9b46b9b8eb891335771ca8e7c8f8e4415")); + } + diff --git a/SOURCES/0568-tpm2-change-tpm2_parse_pcr_argument-parameters-to-pa.patch b/SOURCES/0568-tpm2-change-tpm2_parse_pcr_argument-parameters-to-pa.patch new file mode 100644 index 0000000..b79f0fe --- /dev/null +++ b/SOURCES/0568-tpm2-change-tpm2_parse_pcr_argument-parameters-to-pa.patch @@ -0,0 +1,682 @@ +From 18bff28024350b8553e89de6ed145470c26786fe Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 12 Jul 2023 22:36:37 -0400 +Subject: [PATCH] tpm2: change tpm2_parse_pcr_argument() parameters to parse to + Tpm2PCRValue array + +In order to allow users to specify expected PCR values, change the +tpm2_parse_pcr_argument() to parse the text argument into an array of +Tpm2PCRValue objects, which provide not only the selected PCR indexes, but also +(optionally) the hash algorithm and hash value for each PCR index. + +(cherry picked from commit 07c040611751facf075dab7a72ab4935142dda3c) + +Related: RHEL-16182 +--- + src/creds/creds.c | 4 +- + src/cryptenroll/cryptenroll.c | 4 +- + src/cryptsetup/cryptsetup.c | 2 +- + src/partition/repart.c | 4 +- + src/shared/tpm2-util.c | 179 ++++++++++++++++------ + src/shared/tpm2-util.h | 5 +- + src/test/test-tpm2.c | 279 ++++++++++++++++++++++++++-------- + 7 files changed, 355 insertions(+), 122 deletions(-) + +diff --git a/src/creds/creds.c b/src/creds/creds.c +index a755a52c34..7edaa5f24c 100644 +--- a/src/creds/creds.c ++++ b/src/creds/creds.c +@@ -862,7 +862,7 @@ static int parse_argv(int argc, char *argv[]) { + break; + + case ARG_TPM2_PCRS: /* For fixed hash PCR policies only */ +- r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_pcr_mask); ++ r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_pcr_mask); + if (r < 0) + return r; + +@@ -876,7 +876,7 @@ static int parse_argv(int argc, char *argv[]) { + break; + + case ARG_TPM2_PUBLIC_KEY_PCRS: /* For public key PCR policies only */ +- r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask); ++ r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + if (r < 0) + return r; + +diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c +index 6a9170f000..c84e10e567 100644 +--- a/src/cryptenroll/cryptenroll.c ++++ b/src/cryptenroll/cryptenroll.c +@@ -335,7 +335,7 @@ static int parse_argv(int argc, char *argv[]) { + } + + case ARG_TPM2_PCRS: +- r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_pcr_mask); ++ r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_pcr_mask); + if (r < 0) + return r; + +@@ -356,7 +356,7 @@ static int parse_argv(int argc, char *argv[]) { + break; + + case ARG_TPM2_PUBLIC_KEY_PCRS: +- r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask); ++ r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + if (r < 0) + return r; + +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index 866141ac44..d70516c237 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -390,7 +390,7 @@ static int parse_one_option(const char *option) { + + } else if ((val = startswith(option, "tpm2-pcrs="))) { + +- r = tpm2_parse_pcr_argument(val, &arg_tpm2_pcr_mask); ++ r = tpm2_parse_pcr_argument_to_mask(val, &arg_tpm2_pcr_mask); + if (r < 0) + return r; + +diff --git a/src/partition/repart.c b/src/partition/repart.c +index 57e1a8052a..a2c5e214f6 100644 +--- a/src/partition/repart.c ++++ b/src/partition/repart.c +@@ -5276,7 +5276,7 @@ static int parse_argv(int argc, char *argv[]) { + } + + case ARG_TPM2_PCRS: +- r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_pcr_mask); ++ r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_pcr_mask); + if (r < 0) + return r; + +@@ -5290,7 +5290,7 @@ static int parse_argv(int argc, char *argv[]) { + break; + + case ARG_TPM2_PUBLIC_KEY_PCRS: +- r = tpm2_parse_pcr_argument(optarg, &arg_tpm2_public_key_pcr_mask); ++ r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + if (r < 0) + return r; + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 50a01f55a6..0ad470b0c5 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -4192,45 +4192,6 @@ char *tpm2_pcr_mask_to_string(uint32_t mask) { + return TAKE_PTR(s); + } + +-int tpm2_pcr_mask_from_string(const char *arg, uint32_t *ret_mask) { +- uint32_t mask = 0; +- int r; +- +- assert(arg); +- assert(ret_mask); +- +- if (isempty(arg)) { +- *ret_mask = 0; +- return 0; +- } +- +- /* Parses a "," or "+" separated list of PCR indexes. We support "," since this is a list after all, +- * and most other tools expect comma separated PCR specifications. We also support "+" since in +- * /etc/crypttab the "," is already used to separate options, hence a different separator is nice to +- * avoid escaping. */ +- +- const char *p = arg; +- for (;;) { +- _cleanup_free_ char *pcr = NULL; +- unsigned n; +- +- r = extract_first_word(&p, &pcr, ",+", EXTRACT_DONT_COALESCE_SEPARATORS); +- if (r == 0) +- break; +- if (r < 0) +- return log_error_errno(r, "Failed to parse PCR list: %s", arg); +- +- r = pcr_index_from_string(pcr); +- if (r < 0) +- return log_error_errno(r, "Failed to parse specified PCR or specified PCR is out of range: %s", pcr); +- n = r; +- SET_BIT(mask, n);; +- } +- +- *ret_mask = mask; +- return 0; +-} +- + int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret) { + _cleanup_(json_variant_unrefp) JsonVariant *a = NULL; + JsonVariant* pcr_array[TPM2_PCRS_MAX]; +@@ -4609,29 +4570,147 @@ Tpm2Support tpm2_support(void) { + return support; + } + +-int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask) { +- uint32_t m; ++#if HAVE_TPM2 ++static void tpm2_pcr_values_apply_default_hash_alg(Tpm2PCRValue *pcr_values, size_t n_pcr_values) { ++ TPMI_ALG_HASH default_hash = 0; ++ for (size_t i = 0; i < n_pcr_values; i++) ++ if (pcr_values[i].hash != 0) { ++ default_hash = pcr_values[i].hash; ++ break; ++ } ++ ++ if (default_hash != 0) ++ for (size_t i = 0; i < n_pcr_values; i++) ++ if (pcr_values[i].hash == 0) ++ pcr_values[i].hash = default_hash; ++} ++#endif ++ ++/* Parse the PCR selection/value arg(s) and return a corresponding array of Tpm2PCRValue objects. ++ * ++ * The format is the same as tpm2_pcr_values_from_string(). The first provided entry with a hash algorithm ++ * set will be used as the 'default' hash algorithm. All entries with an unset hash algorithm will be updated ++ * with the 'default' hash algorithm. The resulting array will be sorted and checked for validity. ++ * ++ * This will replace *ret_pcr_values with the new array of pcr values; to append to an existing array, use ++ * tpm2_parse_pcr_argument_append(). */ ++int tpm2_parse_pcr_argument(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values) { ++#if HAVE_TPM2 + int r; + +- assert(mask); ++ assert(arg); ++ assert(ret_pcr_values); ++ assert(ret_n_pcr_values); + +- /* For use in getopt_long() command line parsers: merges masks specified on the command line */ ++ _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; ++ size_t n_pcr_values = 0; ++ r = tpm2_pcr_values_from_string(arg, &pcr_values, &n_pcr_values); ++ if (r < 0) ++ return r; ++ ++ tpm2_pcr_values_apply_default_hash_alg(pcr_values, n_pcr_values); ++ ++ tpm2_sort_pcr_values(pcr_values, n_pcr_values); ++ ++ if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parsed PCR values are not valid."); ++ ++ *ret_pcr_values = TAKE_PTR(pcr_values); ++ *ret_n_pcr_values = n_pcr_values; + +- if (isempty(arg)) { +- *mask = 0; ++ return 0; ++#else ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support is disabled."); ++#endif ++} ++ ++/* Same as tpm2_parse_pcr_argument(), but the pcr values array is appended to. If the provided pcr values ++ * array is not NULL, it must point to an allocated pcr values array and the provided number of pcr values ++ * must be correct. ++ * ++ * Note that 'arg' is parsed into a new array of pcr values independently of any previous pcr values, ++ * including application of the default hash algorithm. Then the two arrays are combined, the default hash ++ * algorithm check applied again (in case either the previous or current array had no default hash ++ * algorithm), and then the resulting array is sorted and rechecked for validity. */ ++int tpm2_parse_pcr_argument_append(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values) { ++#if HAVE_TPM2 ++ int r; ++ ++ assert(arg); ++ assert(ret_pcr_values); ++ assert(ret_n_pcr_values); ++ ++ _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; ++ size_t n_pcr_values; ++ r = tpm2_parse_pcr_argument(arg, &pcr_values, &n_pcr_values); ++ if (r < 0) ++ return r; ++ ++ /* If we got previous values, append them. */ ++ if (*ret_pcr_values && !GREEDY_REALLOC_APPEND(pcr_values, n_pcr_values, *ret_pcr_values, *ret_n_pcr_values)) ++ return log_oom(); ++ ++ tpm2_pcr_values_apply_default_hash_alg(pcr_values, n_pcr_values); ++ ++ tpm2_sort_pcr_values(pcr_values, n_pcr_values); ++ ++ if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parsed PCR values are not valid."); ++ ++ SWAP_TWO(*ret_pcr_values, pcr_values); ++ *ret_n_pcr_values = n_pcr_values; ++ ++ return 0; ++#else ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support is disabled."); ++#endif ++} ++ ++/* Same as tpm2_parse_pcr_argument() but converts the pcr values to a pcr mask. If more than one hash ++ * algorithm is included in the pcr values array this results in error. This retains the previous behavior of ++ * tpm2_parse_pcr_argument() of clearing the mask if 'arg' is empty, replacing the mask if it is set to ++ * UINT32_MAX, and or-ing the mask otherwise. */ ++int tpm2_parse_pcr_argument_to_mask(const char *arg, uint32_t *ret_mask) { ++#if HAVE_TPM2 ++ _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; ++ size_t n_pcr_values; ++ int r; ++ ++ assert(arg); ++ assert(ret_mask); ++ ++ r = tpm2_parse_pcr_argument(arg, &pcr_values, &n_pcr_values); ++ if (r < 0) ++ return r; ++ ++ if (n_pcr_values == 0) { ++ /* This retains the previous behavior of clearing the mask if the arg is empty */ ++ *ret_mask = 0; + return 0; + } + +- r = tpm2_pcr_mask_from_string(arg, &m); ++ size_t hash_count; ++ r = tpm2_pcr_values_hash_count(pcr_values, n_pcr_values, &hash_count); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not get hash count from pcr values: %m"); ++ ++ if (hash_count > 1) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR hash banks selected."); ++ ++ uint32_t new_mask; ++ r = tpm2_pcr_values_to_mask(pcr_values, n_pcr_values, pcr_values[0].hash, &new_mask); ++ if (r < 0) ++ return log_error_errno(r, "Could not get pcr values mask: %m"); + +- if (*mask == UINT32_MAX) +- *mask = m; ++ if (*ret_mask == UINT32_MAX) ++ *ret_mask = new_mask; + else +- *mask |= m; ++ *ret_mask |= new_mask; + + return 0; ++#else ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "TPM2 support is disabled."); ++#endif + } + + int tpm2_load_pcr_signature(const char *path, JsonVariant **ret) { +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index c6e9339e0e..be19c7972b 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -208,7 +208,6 @@ const char *tpm2_asym_alg_to_string(uint16_t alg); + int tpm2_asym_alg_from_string(const char *alg); + + char *tpm2_pcr_mask_to_string(uint32_t mask); +-int tpm2_pcr_mask_from_string(const char *arg, uint32_t *mask); + + typedef struct { + uint32_t search_pcr_mask; +@@ -254,7 +253,9 @@ typedef enum PcrIndex { + + Tpm2Support tpm2_support(void); + +-int tpm2_parse_pcr_argument(const char *arg, uint32_t *mask); ++int tpm2_parse_pcr_argument(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); ++int tpm2_parse_pcr_argument_append(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); ++int tpm2_parse_pcr_argument_to_mask(const char *arg, uint32_t *mask); + + int tpm2_load_pcr_signature(const char *path, JsonVariant **ret); + int tpm2_load_pcr_public_key(const char *path, void **ret_pubkey, size_t *ret_pubkey_size); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 8a4e9f5142..f121b4760a 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -4,39 +4,6 @@ + #include "tpm2-util.h" + #include "tests.h" + +-static void test_tpm2_pcr_mask_from_string_one(const char *s, uint32_t mask, int ret) { +- uint32_t m; +- +- assert_se(tpm2_pcr_mask_from_string(s, &m) == ret); +- +- if (ret >= 0) +- assert_se(m == mask); +-} +- +-TEST(tpm2_mask_from_string) { +- test_tpm2_pcr_mask_from_string_one("", 0, 0); +- test_tpm2_pcr_mask_from_string_one("0", 1, 0); +- test_tpm2_pcr_mask_from_string_one("1", 2, 0); +- test_tpm2_pcr_mask_from_string_one("0,1", 3, 0); +- test_tpm2_pcr_mask_from_string_one("0+1", 3, 0); +- test_tpm2_pcr_mask_from_string_one("0-1", 0, -EINVAL); +- test_tpm2_pcr_mask_from_string_one("0,1,2", 7, 0); +- test_tpm2_pcr_mask_from_string_one("0+1+2", 7, 0); +- test_tpm2_pcr_mask_from_string_one("0+1,2", 7, 0); +- test_tpm2_pcr_mask_from_string_one("0,1+2", 7, 0); +- test_tpm2_pcr_mask_from_string_one("0,2", 5, 0); +- test_tpm2_pcr_mask_from_string_one("0+2", 5, 0); +- test_tpm2_pcr_mask_from_string_one("foo", 0, -EINVAL); +- test_tpm2_pcr_mask_from_string_one("7+application-support", 8388736, 0); +- test_tpm2_pcr_mask_from_string_one("8+boot-loader-code", 272, 0); +- test_tpm2_pcr_mask_from_string_one("6+boot-loader-code,44", 0, -EINVAL); +- test_tpm2_pcr_mask_from_string_one("7,shim-policy,4", 16528, 0); +- test_tpm2_pcr_mask_from_string_one("sysexts,shim-policy+kernel-boot", 26624, 0); +- test_tpm2_pcr_mask_from_string_one("sysexts,shim+kernel-boot", 0, -EINVAL); +- test_tpm2_pcr_mask_from_string_one("sysexts+17+23", 8527872, 0); +- test_tpm2_pcr_mask_from_string_one("debug+24", 16842752, 0); +-} +- + TEST(pcr_index_from_string) { + assert_se(pcr_index_from_string("platform-code") == 0); + assert_se(pcr_index_from_string("0") == 0); +@@ -462,18 +429,19 @@ static bool digest_check(const TPM2B_DIGEST *digest, const char *expect) { + h = hexmem(digest->buffer, digest->size); + assert_se(h); + +- return streq(expect, h); ++ return strcaseeq(expect, h); + } + +-static void digest_init_sha256(TPM2B_DIGEST *digest, const char *hash) { ++static void digest_init(TPM2B_DIGEST *digest, const char *hash) { + _cleanup_free_ void *h = NULL; + size_t s = 0; + +- assert_se(strlen(hash) == SHA256_DIGEST_SIZE * 2); + assert_se(strlen(hash) <= sizeof(digest->buffer) * 2); + + assert_se(unhexmem(hash, strlen(hash), &h, &s) == 0); +- assert_se(s == SHA256_DIGEST_SIZE); ++ ++ /* Make sure the length matches a known hash algorithm */ ++ assert_se(IN_SET(s, TPM2_SHA1_DIGEST_SIZE, TPM2_SHA256_DIGEST_SIZE, TPM2_SHA384_DIGEST_SIZE, TPM2_SHA512_DIGEST_SIZE)); + + memcpy_safe(digest->buffer, h, s); + digest->size = s; +@@ -484,11 +452,11 @@ static void digest_init_sha256(TPM2B_DIGEST *digest, const char *hash) { + TEST(digest_many) { + TPM2B_DIGEST d, d0, d1, d2, d3, d4; + +- digest_init_sha256(&d0, "0000000000000000000000000000000000000000000000000000000000000000"); +- digest_init_sha256(&d1, "17b7703d9d00776310ba032e88c1a8c2a9c630ebdd799db622f6631530789175"); +- digest_init_sha256(&d2, "12998c017066eb0d2a70b94e6ed3192985855ce390f321bbdb832022888bd251"); +- digest_init_sha256(&d3, "c3a65887fedd3fb4f5d0047e906dff830bcbd1293160909eb4b05f485e7387ad"); +- digest_init_sha256(&d4, "6491fb4bc08fc0b2ef47fc63db57e249917885e69d8c0d99667df83a59107a33"); ++ digest_init(&d0, "0000000000000000000000000000000000000000000000000000000000000000"); ++ digest_init(&d1, "17b7703d9d00776310ba032e88c1a8c2a9c630ebdd799db622f6631530789175"); ++ digest_init(&d2, "12998c017066eb0d2a70b94e6ed3192985855ce390f321bbdb832022888bd251"); ++ digest_init(&d3, "c3a65887fedd3fb4f5d0047e906dff830bcbd1293160909eb4b05f485e7387ad"); ++ digest_init(&d4, "6491fb4bc08fc0b2ef47fc63db57e249917885e69d8c0d99667df83a59107a33"); + + /* tpm2_digest_init, tpm2_digest_rehash */ + d = (TPM2B_DIGEST){ .size = 1, .buffer = { 2, }, }; +@@ -552,6 +520,191 @@ TEST(digest_many) { + assert_se(digest_check(&d, "02ecb0628264235111e0053e271092981c8b15d59cd46617836bee3149a4ecb0")); + } + ++static void check_parse_pcr_argument( ++ const char *arg, ++ const Tpm2PCRValue *prev_values, ++ size_t n_prev_values, ++ const Tpm2PCRValue *expected_values, ++ size_t n_expected_values) { ++ ++ _cleanup_free_ Tpm2PCRValue *values = NULL; ++ size_t n_values = 0; ++ ++ if (n_prev_values > 0) { ++ assert_se(GREEDY_REALLOC_APPEND(values, n_values, prev_values, n_prev_values)); ++ assert_se(tpm2_parse_pcr_argument_append(arg, &values, &n_values) == 0); ++ } else ++ assert_se(tpm2_parse_pcr_argument(arg, &values, &n_values) == 0); ++ ++ assert_se(n_values == n_expected_values); ++ for (size_t i = 0; i < n_values; i++) { ++ const Tpm2PCRValue *v = &values[i], *e = &expected_values[i]; ++ //tpm2_log_debug_pcr_value(e, "Expected value"); ++ //tpm2_log_debug_pcr_value(v, "Actual value"); ++ ++ assert_se(v->index == e->index); ++ assert_se(v->hash == e->hash); ++ assert_se(v->value.size == e->value.size); ++ assert_se(memcmp(v->value.buffer, e->value.buffer, e->value.size) == 0); ++ } ++ ++ size_t hash_count; ++ assert_se(tpm2_pcr_values_hash_count(expected_values, n_expected_values, &hash_count) == 0); ++ if (hash_count == 1) { ++ uint32_t mask = UINT32_MAX, expected_mask = 0; ++ ++ if (n_prev_values > 0) ++ assert_se(tpm2_pcr_values_to_mask(prev_values, n_prev_values, prev_values[0].hash, &mask) == 0); ++ ++ assert_se(tpm2_pcr_values_to_mask(expected_values, n_expected_values, expected_values[0].hash, &expected_mask) == 0); ++ ++ assert_se(tpm2_parse_pcr_argument_to_mask(arg, &mask) == 0); ++ assert_se(mask == expected_mask); ++ } ++ ++ size_t old_n_values = n_values; ++ assert_se(tpm2_parse_pcr_argument_append("", &values, &n_values) == 0); ++ assert_se(values); ++ assert_se(n_values == old_n_values); ++} ++ ++static void check_parse_pcr_argument_to_mask(const char *arg, int mask) { ++ uint32_t m = 0; ++ int r = tpm2_parse_pcr_argument_to_mask(arg, &m); ++ ++ if (mask < 0) ++ assert_se(mask == r); ++ else ++ assert_se((uint32_t) mask == m); ++} ++ ++TEST(parse_pcr_argument) { ++ _cleanup_free_ Tpm2PCRValue *t0p = NULL; ++ size_t n_t0p; ++ assert_se(tpm2_parse_pcr_argument("", &t0p, &n_t0p) == 0); ++ assert_se(n_t0p == 0); ++ assert_se(tpm2_parse_pcr_argument_append("", &t0p, &n_t0p) == 0); ++ assert_se(n_t0p == 0); ++ uint32_t m0 = 0xf; ++ assert_se(tpm2_parse_pcr_argument_to_mask("", &m0) == 0); ++ assert_se(m0 == 0); ++ assert_se(tpm2_parse_pcr_argument_to_mask("", &m0) == 0); ++ assert_se(m0 == 0); ++ ++ Tpm2PCRValue t1[] = { ++ TPM2_PCR_VALUE_MAKE(0, 0, {}), ++ TPM2_PCR_VALUE_MAKE(4, 0, {}), ++ TPM2_PCR_VALUE_MAKE(7, 0, {}), ++ TPM2_PCR_VALUE_MAKE(11, 0, {}), ++ }; ++ check_parse_pcr_argument("0,4,7,11", NULL, 0, t1, ELEMENTSOF(t1)); ++ check_parse_pcr_argument("11,4,7,0", NULL, 0, t1, ELEMENTSOF(t1)); ++ check_parse_pcr_argument("7,4,0,11", NULL, 0, t1, ELEMENTSOF(t1)); ++ check_parse_pcr_argument("11,7,4,0", NULL, 0, t1, ELEMENTSOF(t1)); ++ check_parse_pcr_argument("0+4+7+11", NULL, 0, t1, ELEMENTSOF(t1)); ++ check_parse_pcr_argument("0,4+7,11", NULL, 0, t1, ELEMENTSOF(t1)); ++ ++ Tpm2PCRValue t2[] = { ++ TPM2_PCR_VALUE_MAKE(0, TPM2_ALG_SHA1, {}), ++ TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA1, {}), ++ TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA1, {}), ++ TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA1, {}), ++ }; ++ check_parse_pcr_argument("0:sha1,4,7,11", NULL, 0, t2, ELEMENTSOF(t2)); ++ check_parse_pcr_argument("11,4,7,0:sha1", NULL, 0, t2, ELEMENTSOF(t2)); ++ check_parse_pcr_argument("7,4:sha1,0,11", NULL, 0, t2, ELEMENTSOF(t2)); ++ check_parse_pcr_argument("0:sha1,4:sha1,7:sha1,11:sha1", NULL, 0, t2, ELEMENTSOF(t2)); ++ check_parse_pcr_argument("0:sha1+4:sha1,11:sha1+7:sha1", NULL, 0, t2, ELEMENTSOF(t2)); ++ ++ Tpm2PCRValue t3[] = { ++ TPM2_PCR_VALUE_MAKE(0, TPM2_ALG_SHA1, {}), ++ TPM2_PCR_VALUE_MAKE(1, TPM2_ALG_SHA1, {}), ++ TPM2_PCR_VALUE_MAKE(2, TPM2_ALG_SHA1, {}), ++ TPM2_PCR_VALUE_MAKE(3, TPM2_ALG_SHA1, {}), ++ TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA1, {}), ++ TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA1, {}), ++ TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA1, {}), ++ TPM2_PCR_VALUE_MAKE(12, TPM2_ALG_SHA1, {}), ++ }; ++ check_parse_pcr_argument("1,2,3,12", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); ++ check_parse_pcr_argument("12,2,3,1", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); ++ check_parse_pcr_argument("1,2,3,12:sha1", t1, ELEMENTSOF(t1), t3, ELEMENTSOF(t3)); ++ check_parse_pcr_argument("1,2,3,12:sha1", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); ++ check_parse_pcr_argument("1:sha1,2,3,12", t1, ELEMENTSOF(t1), t3, ELEMENTSOF(t3)); ++ check_parse_pcr_argument("1:sha1,2,3,12", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); ++ check_parse_pcr_argument("1:sha1,2:sha1,3:sha1,12:sha1", t1, ELEMENTSOF(t1), t3, ELEMENTSOF(t3)); ++ check_parse_pcr_argument("1:sha1,2:sha1,3:sha1,12:sha1", t2, ELEMENTSOF(t2), t3, ELEMENTSOF(t3)); ++ ++ TPM2B_DIGEST d4; ++ digest_init(&d4, "FCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2"); ++ Tpm2PCRValue t4[] = { ++ TPM2_PCR_VALUE_MAKE(0, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(1, TPM2_ALG_SHA256, d4), ++ TPM2_PCR_VALUE_MAKE(2, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(3, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(12, TPM2_ALG_SHA256, {}), ++ }; ++ check_parse_pcr_argument("1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2,3,12", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); ++ check_parse_pcr_argument("12,2,3,1:sha256=FCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); ++ check_parse_pcr_argument("12,2,3,1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); ++ check_parse_pcr_argument("1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2,3,12:SHA256", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); ++ check_parse_pcr_argument("1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2,3,12", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); ++ check_parse_pcr_argument("1:sha256=FCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2:sha256,3:sha256,12:sha256", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); ++ check_parse_pcr_argument("1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,2:sha256,3:sha256,12:sha256", t1, ELEMENTSOF(t1), t4, ELEMENTSOF(t4)); ++ ++ TPM2B_DIGEST d5; ++ digest_init(&d5, "0F21EADB7F27377668E3C8069BE88D116491FBEE"); ++ Tpm2PCRValue t5[] = { ++ TPM2_PCR_VALUE_MAKE(1, TPM2_ALG_SHA1, d5), ++ TPM2_PCR_VALUE_MAKE(0, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(1, TPM2_ALG_SHA256, d4), ++ TPM2_PCR_VALUE_MAKE(2, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(3, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(11, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(12, TPM2_ALG_SHA256, {}), ++ TPM2_PCR_VALUE_MAKE(5, TPM2_ALG_SHA384, {}), ++ TPM2_PCR_VALUE_MAKE(6, TPM2_ALG_SHA512, {}), ++ }; ++ check_parse_pcr_argument("0,1:sha256=0xFCE7F1083082B16CFE2B085DD7858BB11A37C09B78E36C79E5A2FD529353C4E2,1:sha1=0F21EADB7F27377668E3C8069BE88D116491FBEE,2,3,4,7,11,12,5:sha384,6:sha512", NULL, 0, t5, ELEMENTSOF(t5)); ++ check_parse_pcr_argument("1:sha1=0F21EADB7F27377668E3C8069BE88D116491FBEE,6:sha512,5:sha384", t4, ELEMENTSOF(t4), t5, ELEMENTSOF(t5)); ++ ++ Tpm2PCRValue *v = NULL; ++ size_t n_v = 0; ++ assert_se(tpm2_parse_pcr_argument("1,100", &v, &n_v) < 0); ++ assert_se(tpm2_parse_pcr_argument("1,2=123456abc", &v, &n_v) < 0); ++ assert_se(tpm2_parse_pcr_argument("1,2:invalid", &v, &n_v) < 0); ++ assert_se(tpm2_parse_pcr_argument("1:sha1=invalid", &v, &n_v) < 0); ++ assert_se(v == NULL); ++ assert_se(n_v == 0); ++ ++ check_parse_pcr_argument_to_mask("", 0x0); ++ check_parse_pcr_argument_to_mask("0", 0x1); ++ check_parse_pcr_argument_to_mask("1", 0x2); ++ check_parse_pcr_argument_to_mask("0,1", 0x3); ++ check_parse_pcr_argument_to_mask("0+1", 0x3); ++ check_parse_pcr_argument_to_mask("0-1", -EINVAL); ++ check_parse_pcr_argument_to_mask("foo", -EINVAL); ++ check_parse_pcr_argument_to_mask("0,1,2", 0x7); ++ check_parse_pcr_argument_to_mask("0+1+2", 0x7); ++ check_parse_pcr_argument_to_mask("0+1,2", 0x7); ++ check_parse_pcr_argument_to_mask("0,1+2", 0x7); ++ check_parse_pcr_argument_to_mask("0,2", 0x5); ++ check_parse_pcr_argument_to_mask("0+2", 0x5); ++ check_parse_pcr_argument_to_mask("7+application-support", 0x800080); ++ check_parse_pcr_argument_to_mask("8+boot-loader-code", 0x110); ++ check_parse_pcr_argument_to_mask("7,shim-policy,4", 0x4090); ++ check_parse_pcr_argument_to_mask("sysexts,shim-policy+kernel-boot", 0x6800); ++ check_parse_pcr_argument_to_mask("sysexts,shim+kernel-boot", -EINVAL); ++ check_parse_pcr_argument_to_mask("sysexts+17+23", 0x822000); ++ check_parse_pcr_argument_to_mask("6+boot-loader-code,44", -EINVAL); ++ check_parse_pcr_argument_to_mask("debug+24", -EINVAL); ++} ++ + static void tpm2b_public_init(TPM2B_PUBLIC *public) { + TPMT_PUBLIC tpmt = { + .type = TPM2_ALG_RSA, +@@ -598,7 +751,7 @@ TEST(calculate_name) { + TEST(calculate_policy_auth_value) { + TPM2B_DIGEST d; + +- digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); ++ digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + assert_se(tpm2_calculate_policy_auth_value(&d) == 0); + assert_se(digest_check(&d, "8fcd2169ab92694e0c633f1ab772842b8241bbc20288981fc7ac1eddc1fddb0e")); + assert_se(tpm2_calculate_policy_auth_value(&d) == 0); +@@ -610,7 +763,7 @@ TEST(calculate_policy_authorize) { + TPM2B_DIGEST d; + + tpm2b_public_init(&public); +- digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); ++ digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); + assert_se(digest_check(&d, "95213a3784eaab04f427bc7e8851c2f1df0903be8e42428ec25dcefd907baff1")); + assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); +@@ -620,24 +773,24 @@ TEST(calculate_policy_authorize) { + TEST(calculate_policy_pcr) { + TPM2B_DIGEST d, dN[16]; + +- digest_init_sha256(&dN[ 0], "2124793cbbe60c3a8637d3b84a5d054e87c351e1469a285acc04755e8b204dec"); +- digest_init_sha256(&dN[ 1], "bf7592f18adcfdc549fc0b94939f5069a24697f9cff4a0dca29014767b97559d"); +- digest_init_sha256(&dN[ 2], "4b00cff9dee3a364979b2dc241b34568a8ad49fcf2713df259e47dff8875feed"); +- digest_init_sha256(&dN[ 3], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); +- digest_init_sha256(&dN[ 4], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); +- digest_init_sha256(&dN[ 5], "c97c40369691c8e4aa78fb3a52655cd193b780a838b8e23f5f476576919db5e5"); +- digest_init_sha256(&dN[ 6], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); +- digest_init_sha256(&dN[ 7], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); +- digest_init_sha256(&dN[ 8], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); +- digest_init_sha256(&dN[ 9], "9c2bac22ef5ec84fcdb71c3ebf776cba1247e5da980e5ee08e45666a2edf0b8b"); +- digest_init_sha256(&dN[10], "9885873f4d7348199ad286f8f2476d4f866940950f6f9fb9f945ed352dbdcbd2"); +- digest_init_sha256(&dN[11], "42400ab950d21aa79d12cc4fdef67d1087a39ad64900619831c0974dbae54e44"); +- digest_init_sha256(&dN[12], "767d064382e56ca1ad3bdcc6bc596112e6c2008b593d3570d24c2bfa64c4628c"); +- digest_init_sha256(&dN[13], "30c16133175959408c9745d8dafadef5daf4b39cb2be04df0d60089bd46d3cc4"); +- digest_init_sha256(&dN[14], "e3991b7ddd47be7e92726a832d6874c5349b52b789fa0db8b558c69fea29574e"); +- digest_init_sha256(&dN[15], "852dae3ecb992bdeb13d6002fefeeffdd90feca8b378d56681ef2c885d0e5137"); +- +- digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); ++ digest_init(&dN[ 0], "2124793cbbe60c3a8637d3b84a5d054e87c351e1469a285acc04755e8b204dec"); ++ digest_init(&dN[ 1], "bf7592f18adcfdc549fc0b94939f5069a24697f9cff4a0dca29014767b97559d"); ++ digest_init(&dN[ 2], "4b00cff9dee3a364979b2dc241b34568a8ad49fcf2713df259e47dff8875feed"); ++ digest_init(&dN[ 3], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); ++ digest_init(&dN[ 4], "368f85b3013041dfe203faaa364f00b07c5da7b1e5f1dbf2efb06fa6b9bd92de"); ++ digest_init(&dN[ 5], "c97c40369691c8e4aa78fb3a52655cd193b780a838b8e23f5f476576919db5e5"); ++ digest_init(&dN[ 6], "3d458cfe55cc03ea1f443f1562beec8df51c75e14a9fcf9a7234a13f198e7969"); ++ digest_init(&dN[ 7], "aa1154c9e0a774854ccbed4c8ce7e9b906b3d700a1a8db1772d0341a62dbe51b"); ++ digest_init(&dN[ 8], "cfde439a2c06af3479ca6bdc60429b90553d65300c5cfcc40004a08c6b5ad81a"); ++ digest_init(&dN[ 9], "9c2bac22ef5ec84fcdb71c3ebf776cba1247e5da980e5ee08e45666a2edf0b8b"); ++ digest_init(&dN[10], "9885873f4d7348199ad286f8f2476d4f866940950f6f9fb9f945ed352dbdcbd2"); ++ digest_init(&dN[11], "42400ab950d21aa79d12cc4fdef67d1087a39ad64900619831c0974dbae54e44"); ++ digest_init(&dN[12], "767d064382e56ca1ad3bdcc6bc596112e6c2008b593d3570d24c2bfa64c4628c"); ++ digest_init(&dN[13], "30c16133175959408c9745d8dafadef5daf4b39cb2be04df0d60089bd46d3cc4"); ++ digest_init(&dN[14], "e3991b7ddd47be7e92726a832d6874c5349b52b789fa0db8b558c69fea29574e"); ++ digest_init(&dN[15], "852dae3ecb992bdeb13d6002fefeeffdd90feca8b378d56681ef2c885d0e5137"); ++ ++ digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + Tpm2PCRValue v1[] = { + TPM2_PCR_VALUE_MAKE(4, TPM2_ALG_SHA256, dN[4]), + TPM2_PCR_VALUE_MAKE(7, TPM2_ALG_SHA256, dN[7]), +@@ -648,7 +801,7 @@ TEST(calculate_policy_pcr) { + assert_se(tpm2_calculate_policy_pcr(v1, ELEMENTSOF(v1), &d) == 0); + assert_se(digest_check(&d, "97e64bcabb64c1fa4b726528644926c8029f5b4458b0575c98c04fe225629a0b")); + +- digest_init_sha256(&d, "0000000000000000000000000000000000000000000000000000000000000000"); ++ digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + Tpm2PCRValue v2[] = { + TPM2_PCR_VALUE_MAKE( 0, TPM2_ALG_SHA256, dN[ 0]), + TPM2_PCR_VALUE_MAKE( 1, TPM2_ALG_SHA256, dN[ 1]), diff --git a/SOURCES/0569-tpm2-add-TPM2B_-_MAKE-TPM2B_-_CHECK_SIZE-macros.patch b/SOURCES/0569-tpm2-add-TPM2B_-_MAKE-TPM2B_-_CHECK_SIZE-macros.patch new file mode 100644 index 0000000..574ab8c --- /dev/null +++ b/SOURCES/0569-tpm2-add-TPM2B_-_MAKE-TPM2B_-_CHECK_SIZE-macros.patch @@ -0,0 +1,178 @@ +From d2d9d213c8e519e219399101d54f503ff7e9d8c0 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 19 Jul 2023 07:49:07 -0400 +Subject: [PATCH] tpm2: add TPM2B_*_MAKE(), TPM2B_*_CHECK_SIZE() macros + +The tpm2-tss library has many structs with only an array and size; these macros +make it easy to assign to these structs. + +(cherry picked from commit 53b91e1981993f49e079d977e2ac651eaac5cc5a) + +Related: RHEL-16182 +--- + src/boot/measure.c | 15 +++-------- + src/shared/tpm2-util.c | 17 +++++++------ + src/shared/tpm2-util.h | 56 ++++++++++++++++++++++++++++++++++++++++++ + src/test/test-tpm2.c | 7 ++---- + 4 files changed, 72 insertions(+), 23 deletions(-) + +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 1d696e1bd9..76f7f3fbfa 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -799,18 +799,11 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + if (tpmalg < 0) + return log_error_errno(tpmalg, "Unsupported PCR bank"); + +- TPM2B_DIGEST pcr_digest = { +- .size = p->value_size, +- }; +- assert(sizeof(pcr_digest.buffer) >= p->value_size); +- memcpy_safe(pcr_digest.buffer, p->value, p->value_size); ++ Tpm2PCRValue pcr_value = TPM2_PCR_VALUE_MAKE(TPM_PCR_INDEX_KERNEL_IMAGE, ++ tpmalg, ++ TPM2B_DIGEST_MAKE(p->value, p->value_size)); + +- Tpm2PCRValue pcr_value = TPM2_PCR_VALUE_MAKE(TPM_PCR_INDEX_KERNEL_IMAGE, tpmalg, pcr_digest); +- +- TPM2B_DIGEST pcr_policy_digest; +- r = tpm2_digest_init(TPM2_ALG_SHA256, &pcr_policy_digest); +- if (r < 0) +- return r; ++ TPM2B_DIGEST pcr_policy_digest = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + + r = tpm2_calculate_policy_pcr(&pcr_value, 1, &pcr_policy_digest); + if (r < 0) +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 0ad470b0c5..cd48988ab1 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1745,9 +1745,11 @@ int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value) { + if (r < 0) + return log_error_errno(r, "Invalid pcr hash value '%s': %m", p); + +- pcr_value.value.size = buf_size; +- assert(sizeof(pcr_value.value.buffer) >= pcr_value.value.size); +- memcpy(pcr_value.value.buffer, buf, pcr_value.value.size); ++ r = TPM2B_DIGEST_CHECK_SIZE(buf_size); ++ if (r < 0) ++ return log_error_errno(r, "PCR hash value size %zu too large.", buf_size); ++ ++ pcr_value.value = TPM2B_DIGEST_MAKE(buf, buf_size); + } + + *ret_pcr_value = pcr_value; +@@ -3364,16 +3366,17 @@ static int tpm2_policy_authorize( + if (r < 0) + return r; + ++ r = TPM2B_PUBLIC_KEY_RSA_CHECK_SIZE(signature_size); ++ if (r < 0) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Signature larger than buffer."); ++ + TPMT_SIGNATURE policy_signature = { + .sigAlg = TPM2_ALG_RSASSA, + .signature.rsassa = { + .hash = TPM2_ALG_SHA256, +- .sig.size = signature_size, ++ .sig = TPM2B_PUBLIC_KEY_RSA_MAKE(signature_raw, signature_size), + }, + }; +- if (signature_size > sizeof(policy_signature.signature.rsassa.sig.buffer)) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Signature larger than buffer."); +- memcpy(policy_signature.signature.rsassa.sig.buffer, signature_raw, signature_size); + + rc = sym_Esys_VerifySignature( + c->esys_context, +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index be19c7972b..dc496a0135 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -153,6 +153,62 @@ int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_value + int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); + int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); + ++/* The tpm2-tss library has many structs that are simply a combination of an array (or object) and ++ * size. These macros allow easily initializing or assigning instances of such structs from an existing ++ * buffer/object and size, while also checking the size for safety with the struct buffer/object size. If the ++ * provided buffer/object is NULL, the resulting struct's buffer/object will be 0s. If the provided size is ++ * larger than the struct's buffer/object size, this results in assertion failure; to check the size, use one ++ * of the TPM2B_*_CHECK_SIZE() macros. */ ++#define TPM2B_AUTH_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_AUTH, buffer, size) ++#define TPM2B_DATA_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_DATA, buffer, size) ++#define TPM2B_DIGEST_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_DIGEST, buffer, size) ++#define TPM2B_ECC_PARAMETER_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_ECC_PARAMETER, buffer, size) ++#define TPM2B_ENCRYPTED_SECRET_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_ENCRYPTED_SECRET, secret, size) ++#define TPM2B_MAX_BUFFER_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_MAX_BUFFER, buffer, size) ++#define TPM2B_NAME_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_NAME, name, size) ++#define TPM2B_PRIVATE_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_PRIVATE, buffer, size) ++#define TPM2B_PRIVATE_KEY_RSA_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_PRIVATE_KEY_RSA, buffer, size) ++#define TPM2B_PUBLIC_KEY_RSA_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_PUBLIC_KEY_RSA, buffer, size) ++#define TPM2B_SENSITIVE_DATA_MAKE(b, s) TPM2B_BUF_SIZE_STRUCT_MAKE(b, s, TPM2B_SENSITIVE_DATA, buffer, size) ++#define TPM2B_BUF_SIZE_STRUCT_MAKE(buf, size, struct_type, buffer_field, size_field) \ ++ _TPM2B_BUF_SIZE_STRUCT_MAKE(buf, size, UNIQ, struct_type, buffer_field, size_field) ++#define _TPM2B_BUF_SIZE_STRUCT_MAKE(buf, size, uniq, struct_type, buffer_field, size_field) \ ++ ({ \ ++ typeof(buf) UNIQ_T(BUF, uniq) = (buf); \ ++ typeof(size) UNIQ_T(SIZE, uniq) = (size); \ ++ struct_type UNIQ_T(STRUCT, uniq) = { .size_field = UNIQ_T(SIZE, uniq), }; \ ++ assert(sizeof(UNIQ_T(STRUCT, uniq).buffer_field) >= (size_t) UNIQ_T(SIZE, uniq)); \ ++ if (UNIQ_T(BUF, uniq)) \ ++ memcpy(UNIQ_T(STRUCT, uniq).buffer_field, UNIQ_T(BUF, uniq), UNIQ_T(SIZE, uniq)); \ ++ UNIQ_T(STRUCT, uniq); \ ++ }) ++ ++/* Check if the size will fit in the TPM2B struct buffer. Returns 0 if the size will fit, otherwise this logs ++ * a debug message and returns < 0. */ ++#define TPM2B_AUTH_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_AUTH, buffer) ++#define TPM2B_DATA_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_DATA, buffer) ++#define TPM2B_DIGEST_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_DIGEST, buffer) ++#define TPM2B_ECC_PARAMETER_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_ECC_PARAMETER, buffer) ++#define TPM2B_ENCRYPTED_SECRET_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_ENCRYPTED_SECRET, buffer) ++#define TPM2B_MAX_BUFFER_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_MAX_BUFFER, buffer) ++#define TPM2B_NAME_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_NAME, name) ++#define TPM2B_PRIVATE_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_PRIVATE, buffer) ++#define TPM2B_PRIVATE_KEY_RSA_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_PRIVATE_KEY_RSA, buffer) ++#define TPM2B_PUBLIC_KEY_RSA_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_PUBLIC_KEY_RSA, buffer) ++#define TPM2B_SENSITIVE_DATA_CHECK_SIZE(s) TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(s, TPM2B_SENSITIVE_DATA, buffer) ++#define TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(size, struct_type, buffer_field) \ ++ _TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(size, UNIQ, struct_type, buffer_field) ++#define _TPM2B_BUF_SIZE_STRUCT_CHECK_SIZE(size, uniq, struct_type, buffer_field) \ ++ ({ \ ++ size_t UNIQ_T(SIZE, uniq) = (size_t) (size); \ ++ size_t UNIQ_T(BUFSIZE, uniq) = sizeof_field(struct_type, buffer_field); \ ++ UNIQ_T(BUFSIZE, uniq) < UNIQ_T(SIZE, uniq) ? \ ++ log_debug_errno(SYNTHETIC_ERRNO(EINVAL), \ ++ "Size %zu larger than " #struct_type " buffer size %zu.", \ ++ UNIQ_T(SIZE, uniq), UNIQ_T(BUFSIZE, uniq)) : \ ++ 0; \ ++ }) ++ + #else /* HAVE_TPM2 */ + typedef struct {} Tpm2Context; + typedef struct {} Tpm2Handle; +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index f121b4760a..4c22d7c691 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -443,8 +443,7 @@ static void digest_init(TPM2B_DIGEST *digest, const char *hash) { + /* Make sure the length matches a known hash algorithm */ + assert_se(IN_SET(s, TPM2_SHA1_DIGEST_SIZE, TPM2_SHA256_DIGEST_SIZE, TPM2_SHA384_DIGEST_SIZE, TPM2_SHA512_DIGEST_SIZE)); + +- memcpy_safe(digest->buffer, h, s); +- digest->size = s; ++ *digest = TPM2B_DIGEST_MAKE(h, s); + + assert_se(digest_check(digest, hash)); + } +@@ -725,9 +724,7 @@ static void tpm2b_public_init(TPM2B_PUBLIC *public) { + _cleanup_free_ void *mem = NULL; + size_t len = 0; + assert_se(unhexmem(key, strlen(key), &mem, &len) == 0); +- assert_se(len <= sizeof(tpmt.unique.rsa.buffer)); +- memcpy_safe(tpmt.unique.rsa.buffer, mem, len); +- tpmt.unique.rsa.size = len; ++ tpmt.unique.rsa = TPM2B_PUBLIC_KEY_RSA_MAKE(mem, len); + + public->publicArea = tpmt; + } diff --git a/SOURCES/0570-tpm2-add-tpm2_pcr_read_missing_values.patch b/SOURCES/0570-tpm2-add-tpm2_pcr_read_missing_values.patch new file mode 100644 index 0000000..0c69e56 --- /dev/null +++ b/SOURCES/0570-tpm2-add-tpm2_pcr_read_missing_values.patch @@ -0,0 +1,132 @@ +From 84ac655ce759bb906e9c428261c2a25b0f42241d Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 18 Jul 2023 12:56:25 -0400 +Subject: [PATCH] tpm2: add tpm2_pcr_read_missing_values() + +Add function to read all unset values in an array of Tpm2PCRValue entries. + +Also publish tpm2_pcr_read() in header. + +(cherry picked from commit b4a6fcd5c5cf95dde2d08769a86ff0d3f907b974) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 68 ++++++++++++++++++++++++++++++++++++++++-- + src/shared/tpm2-util.h | 4 +++ + 2 files changed, 70 insertions(+), 2 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index cd48988ab1..fadf3af9d6 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -2247,7 +2247,7 @@ int tpm2_create_loaded( + * exactly what is in the provided selection, but the TPM may ignore some selected PCRs (for example, if an + * unimplemented PCR index is requested), in which case those PCRs will be absent from the provided pcr + * values. */ +-static int tpm2_pcr_read( ++int tpm2_pcr_read( + Tpm2Context *c, + const TPML_PCR_SELECTION *pcr_selection, + Tpm2PCRValue **ret_pcr_values, +@@ -2320,6 +2320,70 @@ static int tpm2_pcr_read( + return 0; + } + ++/* Read the PCR value for each TPM2PCRValue entry in the array that does not have a value set. If all entries ++ * have an unset hash (i.e. hash == 0), this first detects the "best" PCR bank to use; otherwise, all entries ++ * must have a valid hash set. All entries must have a valid index. If this cannot read a PCR value for all ++ * appropriate entries, this returns an error. This does not check the array for validity. */ ++int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_t n_pcr_values) { ++ TPMI_ALG_HASH pcr_bank = 0; ++ int r; ++ ++ assert(c); ++ assert(pcr_values || n_pcr_values == 0); ++ ++ if (n_pcr_values > 0) { ++ size_t hash_count; ++ r = tpm2_pcr_values_hash_count(pcr_values, n_pcr_values, &hash_count); ++ if (r < 0) ++ return log_error_errno(r, "Could not get hash count from pcr values: %m"); ++ ++ if (hash_count == 1 && pcr_values[0].hash == 0) { ++ uint32_t mask; ++ r = tpm2_pcr_values_to_mask(pcr_values, n_pcr_values, 0, &mask); ++ if (r < 0) ++ return r; ++ ++ r = tpm2_get_best_pcr_bank(c, mask, &pcr_bank); ++ if (r < 0) ++ return r; ++ } ++ } ++ ++ for (size_t i = 0; i < n_pcr_values; i++) { ++ Tpm2PCRValue *v = &pcr_values[i]; ++ ++ if (v->hash == 0) ++ v->hash = pcr_bank; ++ ++ if (v->value.size > 0) ++ continue; ++ ++ TPML_PCR_SELECTION selection; ++ r = tpm2_tpml_pcr_selection_from_pcr_values(v, 1, &selection, NULL, NULL); ++ if (r < 0) ++ return r; ++ ++ _cleanup_free_ Tpm2PCRValue *read_values = NULL; ++ size_t n_read_values; ++ r = tpm2_pcr_read(c, &selection, &read_values, &n_read_values); ++ if (r < 0) ++ return r; ++ ++ if (n_read_values == 0) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Could not read PCR hash 0x%" PRIu16 " index %u", ++ v->hash, v->index); ++ ++ assert(n_read_values == 1); ++ assert(read_values[0].hash == v->hash); ++ assert(read_values[0].index == v->index); ++ ++ v->value = read_values[0].value; ++ } ++ ++ return 0; ++} ++ + static int tpm2_pcr_mask_good( + Tpm2Context *c, + TPMI_ALG_HASH bank, +@@ -2381,7 +2445,7 @@ static int tpm2_bank_has24(const TPMS_PCR_SELECTION *selection) { + return valid; + } + +-static int tpm2_get_best_pcr_bank( ++int tpm2_get_best_pcr_bank( + Tpm2Context *c, + uint32_t pcr_mask, + TPMI_ALG_HASH *ret) { +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index dc496a0135..2dffcf922d 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -99,6 +99,7 @@ bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARM + + int tpm2_get_good_pcr_banks(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH **ret_banks); + int tpm2_get_good_pcr_banks_strv(Tpm2Context *c, uint32_t pcr_mask, char ***ret); ++int tpm2_get_best_pcr_bank(Tpm2Context *c, uint32_t pcr_mask, TPMI_ALG_HASH *ret); + + int tpm2_extend_bytes(Tpm2Context *c, char **banks, unsigned pcr_index, const void *data, size_t data_size, const void *secret, size_t secret_size); + +@@ -145,6 +146,9 @@ void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg); + void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg); + void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg); + ++int tpm2_pcr_read(Tpm2Context *c, const TPML_PCR_SELECTION *pcr_selection, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); ++int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_t n_pcr_values); ++ + int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); + int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest); + int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGEST *policy_ref, TPM2B_DIGEST *digest); diff --git a/SOURCES/0571-openssl-add-openssl_pkey_from_pem.patch b/SOURCES/0571-openssl-add-openssl_pkey_from_pem.patch new file mode 100644 index 0000000..d3bd477 --- /dev/null +++ b/SOURCES/0571-openssl-add-openssl_pkey_from_pem.patch @@ -0,0 +1,62 @@ +From baa3ecd821018d699b039259ac80b999d28f88ae Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 18 Jul 2023 22:48:34 -0400 +Subject: [PATCH] openssl: add openssl_pkey_from_pem() + +Add function to create EVP_PKEY from PEM buffer. + +(cherry picked from commit 4af788c70c985b6b87435a90594e2a301929fb5b) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 19 +++++++++++++++++++ + src/shared/openssl-util.h | 2 ++ + 2 files changed, 21 insertions(+) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index c7fcbd9ea4..9021d91077 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -1,10 +1,29 @@ + /* SPDX-License-Identifier: LGPL-2.1-or-later */ + ++#include "fd-util.h" + #include "openssl-util.h" + #include "alloc-util.h" + #include "hexdecoct.h" + + #if HAVE_OPENSSL ++int openssl_pkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) { ++ assert(pem); ++ assert(ret); ++ ++ _cleanup_fclose_ FILE *f = NULL; ++ f = fmemopen((void*) pem, pem_size, "r"); ++ if (!f) ++ return log_oom_debug(); ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL); ++ if (!pkey) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM."); ++ ++ *ret = TAKE_PTR(pkey); ++ ++ return 0; ++} ++ + int openssl_hash(const EVP_MD *alg, + const void *msg, + size_t msg_len, +diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h +index 4fa0a95966..231bcc2bf8 100644 +--- a/src/shared/openssl-util.h ++++ b/src/shared/openssl-util.h +@@ -43,6 +43,8 @@ static inline void sk_X509_free_allp(STACK_OF(X509) **sk) { + sk_X509_pop_free(*sk, X509_free); + } + ++int openssl_pkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret); ++ + int openssl_hash(const EVP_MD *alg, const void *msg, size_t msg_len, uint8_t *ret_hash, size_t *ret_hash_len); + + int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); diff --git a/SOURCES/0572-openssl-add-rsa_pkey_new-rsa_pkey_from_n_e-rsa_pkey_.patch b/SOURCES/0572-openssl-add-rsa_pkey_new-rsa_pkey_from_n_e-rsa_pkey_.patch new file mode 100644 index 0000000..3eef1d8 --- /dev/null +++ b/SOURCES/0572-openssl-add-rsa_pkey_new-rsa_pkey_from_n_e-rsa_pkey_.patch @@ -0,0 +1,225 @@ +From 9a70ae84dcf12fa5e8c5a02a0451badba7f9018d Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 27 Jun 2023 14:53:46 -0400 +Subject: [PATCH] openssl: add rsa_pkey_new(), rsa_pkey_from_n_e(), + rsa_pkey_to_n_e() + +Add function to generate an EVP_PKEY for a specific 'n' and 'e', and function +to get 'n' and 'e' values from existing RSA public key. Also add a function to +generate a new RSA key with a specified number of bits. + +(cherry picked from commit dcec950ca1c122a3e02798f9501db459cb97552f) + +Related: RHEL-16182 +--- + src/resolve/resolved-dns-dnssec.c | 2 +- + src/shared/openssl-util.c | 143 ++++++++++++++++++++++++++++++ + src/shared/openssl-util.h | 14 +++ + 3 files changed, 158 insertions(+), 1 deletion(-) + +diff --git a/src/resolve/resolved-dns-dnssec.c b/src/resolve/resolved-dns-dnssec.c +index f63cd9b48c..426ea945ca 100644 +--- a/src/resolve/resolved-dns-dnssec.c ++++ b/src/resolve/resolved-dns-dnssec.c +@@ -13,7 +13,7 @@ + #include "sort-util.h" + #include "string-table.h" + +-#if PREFER_OPENSSL ++#if PREFER_OPENSSL && OPENSSL_VERSION_MAJOR >= 3 + # pragma GCC diagnostic push + # pragma GCC diagnostic ignored "-Wdeprecated-declarations" + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(RSA*, RSA_free, NULL); +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index 9021d91077..c02440495d 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -128,6 +128,149 @@ int rsa_pkey_to_suitable_key_size( + return 0; + } + ++/* Generate RSA public key from provided "n" and "e" values. Note that if "e" is a number (e.g. uint32_t), it ++ * must be provided here big-endian, e.g. wrap it with htobe32(). */ ++int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret) { ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ++ ++ assert(n); ++ assert(e); ++ assert(ret); ++ ++ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); ++ if (!ctx) ++ return log_oom_debug(); ++ ++ _cleanup_(BN_freep) BIGNUM *bn_n = BN_bin2bn(n, n_size, NULL); ++ if (!bn_n) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create BIGNUM for RSA n."); ++ ++ _cleanup_(BN_freep) BIGNUM *bn_e = BN_bin2bn(e, e_size, NULL); ++ if (!bn_e) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create BIGNUM for RSA e."); ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ if (EVP_PKEY_fromdata_init(ctx) <= 0) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize EVP_PKEY_CTX."); ++ ++ _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); ++ if (!bld) ++ return log_oom_debug(); ++ ++ if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, bn_n)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set RSA OSSL_PKEY_PARAM_RSA_N."); ++ ++ if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, bn_e)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set RSA OSSL_PKEY_PARAM_RSA_E."); ++ ++ _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); ++ if (!params) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to build RSA OSSL_PARAM."); ++ ++ if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create RSA EVP_PKEY."); ++#else ++ _cleanup_(RSA_freep) RSA *rsa_key = RSA_new(); ++ if (!rsa_key) ++ return log_oom_debug(); ++ ++ if (!RSA_set0_key(rsa_key, bn_n, bn_e, NULL)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set RSA n/e."); ++ /* rsa_key owns these now, don't free */ ++ TAKE_PTR(bn_n); ++ TAKE_PTR(bn_e); ++ ++ pkey = EVP_PKEY_new(); ++ if (!pkey) ++ return log_oom_debug(); ++ ++ if (!EVP_PKEY_assign_RSA(pkey, rsa_key)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to assign RSA key."); ++ /* pkey owns this now, don't free */ ++ TAKE_PTR(rsa_key); ++#endif ++ ++ *ret = TAKE_PTR(pkey); ++ ++ return 0; ++} ++ ++/* Get the "n" and "e" values from the pkey. The values are returned in "bin" format, i.e. BN_bn2bin(). */ ++int rsa_pkey_to_n_e( ++ const EVP_PKEY *pkey, ++ void **ret_n, ++ size_t *ret_n_size, ++ void **ret_e, ++ size_t *ret_e_size) { ++ ++ assert(pkey); ++ assert(ret_n); ++ assert(ret_n_size); ++ assert(ret_e); ++ assert(ret_e_size); ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ _cleanup_(BN_freep) BIGNUM *bn_n = NULL; ++ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &bn_n)) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get RSA n."); ++ ++ _cleanup_(BN_freep) BIGNUM *bn_e = NULL; ++ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &bn_e)) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get RSA e."); ++#else ++ const RSA *rsa = EVP_PKEY_get0_RSA((EVP_PKEY*) pkey); ++ if (!rsa) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), ++ "Failed to get RSA key from public key."); ++ ++ const BIGNUM *bn_n = RSA_get0_n(rsa); ++ if (!bn_n) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get RSA n."); ++ ++ const BIGNUM *bn_e = RSA_get0_e(rsa); ++ if (!bn_e) ++ return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get RSA e."); ++#endif ++ ++ size_t n_size = BN_num_bytes(bn_n), e_size = BN_num_bytes(bn_e); ++ _cleanup_free_ void *n = malloc(n_size), *e = malloc(e_size); ++ if (!n || !e) ++ return log_oom_debug(); ++ ++ assert(BN_bn2bin(bn_n, n) == (int) n_size); ++ assert(BN_bn2bin(bn_e, e) == (int) e_size); ++ ++ *ret_n = TAKE_PTR(n); ++ *ret_n_size = n_size; ++ *ret_e = TAKE_PTR(e); ++ *ret_e_size = e_size; ++ ++ return 0; ++} ++ ++/* Generate a new RSA key with the specified number of bits. */ ++int rsa_pkey_new(size_t bits, EVP_PKEY **ret) { ++ assert(ret); ++ ++ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); ++ if (!ctx) ++ return log_oom_debug(); ++ ++ if (EVP_PKEY_keygen_init(ctx) <= 0) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize EVP_PKEY_CTX."); ++ ++ if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, (int) bits) <= 0) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set RSA bits to %zu.", bits); ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ++ if (EVP_PKEY_keygen(ctx, &pkey) <= 0) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate ECC key."); ++ ++ *ret = TAKE_PTR(pkey); ++ ++ return 0; ++} ++ + int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size) { + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* m = NULL; + _cleanup_free_ void *d = NULL, *h = NULL; +diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h +index 231bcc2bf8..b9aacfd276 100644 +--- a/src/shared/openssl-util.h ++++ b/src/shared/openssl-util.h +@@ -21,6 +21,7 @@ + # endif + # if OPENSSL_VERSION_MAJOR >= 3 + # include ++# include + # endif + + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509_NAME*, X509_NAME_free, NULL); +@@ -35,6 +36,13 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(PKCS7*, PKCS7_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL); ++#if OPENSSL_VERSION_MAJOR >= 3 ++DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM*, OSSL_PARAM_free, NULL); ++DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM_BLD*, OSSL_PARAM_BLD_free, NULL); ++#else ++DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_KEY*, EC_KEY_free, NULL); ++DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(RSA*, RSA_free, NULL); ++#endif + + static inline void sk_X509_free_allp(STACK_OF(X509) **sk) { + if (!sk || !*sk) +@@ -51,6 +59,12 @@ int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypte + + int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size); + ++int rsa_pkey_new(size_t bits, EVP_PKEY **ret); ++ ++int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size, EVP_PKEY **ret); ++ ++int rsa_pkey_to_n_e(const EVP_PKEY *pkey, void **ret_n, size_t *ret_n_size, void **ret_e, size_t *ret_e_size); ++ + int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size); + + #else diff --git a/SOURCES/0573-openssl-add-ecc_pkey_new-ecc_pkey_from_curve_x_y-ecc.patch b/SOURCES/0573-openssl-add-ecc_pkey_new-ecc_pkey_from_curve_x_y-ecc.patch new file mode 100644 index 0000000..9e03f1a --- /dev/null +++ b/SOURCES/0573-openssl-add-ecc_pkey_new-ecc_pkey_from_curve_x_y-ecc.patch @@ -0,0 +1,280 @@ +From b36e02a241389dc00ceee9abbaa3a89b731477ed Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 4 Jul 2023 18:52:59 -0400 +Subject: [PATCH] openssl: add ecc_pkey_new(), ecc_pkey_from_curve_x_y(), + ecc_pkey_to_curve_x_y() + +Add function to create openssl pkey from ECC curve and point, and function to +get curve id and x/y point from existing ECC pkey. Also add function to create +new ECC key for specified curve. + +Also add DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO() to handle case when func() is +a macro, not a function symbol; specifically in this case it is used for +OPENSSL_free() which is a macro. + +(cherry picked from commit 900e73f80e87df2295faabd66f66d42c973d8ad6) + +Related: RHEL-16182 +--- + src/basic/macro.h | 9 ++ + src/shared/openssl-util.c | 193 ++++++++++++++++++++++++++++++++++++++ + src/shared/openssl-util.h | 8 ++ + 3 files changed, 210 insertions(+) + +diff --git a/src/basic/macro.h b/src/basic/macro.h +index 9c36683ef9..9cb7ae5077 100644 +--- a/src/basic/macro.h ++++ b/src/basic/macro.h +@@ -359,6 +359,15 @@ static inline int __coverity_check_and_return__(int condition) { + } \ + } + ++/* When func() doesn't return the appropriate type, and is also a macro, set variable to empty afterwards. */ ++#define DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(type, func, empty) \ ++ static inline void func##p(type *p) { \ ++ if (*p != (empty)) { \ ++ func(*p); \ ++ *p = (empty); \ ++ } \ ++ } ++ + #define _DEFINE_TRIVIAL_REF_FUNC(type, name, scope) \ + scope type *name##_ref(type *p) { \ + if (!p) \ +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index c02440495d..c3f8f91eb3 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -271,6 +271,199 @@ int rsa_pkey_new(size_t bits, EVP_PKEY **ret) { + return 0; + } + ++/* Generate ECC public key from provided curve ID and x/y points. */ ++int ecc_pkey_from_curve_x_y( ++ int curve_id, ++ const void *x, ++ size_t x_size, ++ const void *y, ++ size_t y_size, ++ EVP_PKEY **ret) { ++ ++ assert(x); ++ assert(y); ++ assert(ret); ++ ++ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); ++ if (!ctx) ++ return log_oom_debug(); ++ ++ _cleanup_(BN_freep) BIGNUM *bn_x = BN_bin2bn(x, x_size, NULL), *bn_y = BN_bin2bn(y, y_size, NULL); ++ if (!bn_x || !bn_y) ++ return log_oom_debug(); ++ ++ _cleanup_(EC_GROUP_freep) EC_GROUP *group = EC_GROUP_new_by_curve_name(curve_id); ++ if (!group) ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "ECC curve id %d not supported.", curve_id); ++ ++ _cleanup_(EC_POINT_freep) EC_POINT *point = EC_POINT_new(group); ++ if (!point) ++ return log_oom_debug(); ++ ++ if (!EC_POINT_set_affine_coordinates(group, point, bn_x, bn_y, NULL)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ECC coordinates."); ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ if (EVP_PKEY_fromdata_init(ctx) <= 0) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to initialize EVP_PKEY_CTX."); ++ ++ _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); ++ if (!bld) ++ return log_oom_debug(); ++ ++ if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, (char*) OSSL_EC_curve_nid2name(curve_id), 0)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to add ECC OSSL_PKEY_PARAM_GROUP_NAME."); ++ ++ _cleanup_(OPENSSL_freep) void *pbuf = NULL; ++ size_t pbuf_len = 0; ++ pbuf_len = EC_POINT_point2buf(group, point, POINT_CONVERSION_UNCOMPRESSED, (unsigned char**) &pbuf, NULL); ++ if (pbuf_len == 0) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert ECC point to buffer."); ++ ++ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pbuf, pbuf_len)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to add ECC OSSL_PKEY_PARAM_PUB_KEY."); ++ ++ _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); ++ if (!params) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to build ECC OSSL_PARAM."); ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ++ if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "Failed to create ECC EVP_PKEY."); ++#else ++ _cleanup_(EC_KEY_freep) EC_KEY *eckey = EC_KEY_new(); ++ if (!eckey) ++ return log_oom_debug(); ++ ++ if (!EC_KEY_set_group(eckey, group)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ECC group."); ++ ++ if (!EC_KEY_set_public_key(eckey, point)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ECC point."); ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = EVP_PKEY_new(); ++ if (!pkey) ++ return log_oom_debug(); ++ ++ if (!EVP_PKEY_assign_EC_KEY(pkey, eckey)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to assign ECC key."); ++ /* pkey owns this now, don't free */ ++ TAKE_PTR(eckey); ++#endif ++ ++ *ret = TAKE_PTR(pkey); ++ ++ return 0; ++} ++ ++int ecc_pkey_to_curve_x_y( ++ const EVP_PKEY *pkey, ++ int *ret_curve_id, ++ void **ret_x, ++ size_t *ret_x_size, ++ void **ret_y, ++ size_t *ret_y_size) { ++ ++ _cleanup_(BN_freep) BIGNUM *bn_x = NULL, *bn_y = NULL; ++ int curve_id; ++ ++ assert(pkey); ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ size_t name_size; ++ if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, NULL, 0, &name_size)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC group name size."); ++ ++ _cleanup_free_ char *name = malloc(name_size + 1); ++ if (!name) ++ return log_oom_debug(); ++ ++ if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, name, name_size + 1, NULL)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC group name."); ++ ++ curve_id = OBJ_sn2nid(name); ++ if (curve_id == NID_undef) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC curve id."); ++ ++ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &bn_x)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC point x."); ++ ++ if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &bn_y)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC point y."); ++#else ++ const EC_KEY *eckey = EVP_PKEY_get0_EC_KEY((EVP_PKEY*) pkey); ++ if (!eckey) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get EC_KEY."); ++ ++ const EC_GROUP *group = EC_KEY_get0_group(eckey); ++ if (!group) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get EC_GROUP."); ++ ++ curve_id = EC_GROUP_get_curve_name(group); ++ if (curve_id == NID_undef) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC curve id."); ++ ++ const EC_POINT *point = EC_KEY_get0_public_key(eckey); ++ if (!point) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get EC_POINT."); ++ ++ bn_x = BN_new(); ++ bn_y = BN_new(); ++ if (!bn_x || !bn_y) ++ return log_oom_debug(); ++ ++ if (!EC_POINT_get_affine_coordinates(group, point, bn_x, bn_y, NULL)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC x/y."); ++#endif ++ ++ size_t x_size = BN_num_bytes(bn_x), y_size = BN_num_bytes(bn_y); ++ _cleanup_free_ void *x = malloc(x_size), *y = malloc(y_size); ++ if (!x || !y) ++ return log_oom_debug(); ++ ++ assert(BN_bn2bin(bn_x, x) == (int) x_size); ++ assert(BN_bn2bin(bn_y, y) == (int) y_size); ++ ++ if (ret_curve_id) ++ *ret_curve_id = curve_id; ++ if (ret_x) ++ *ret_x = TAKE_PTR(x); ++ if (ret_x_size) ++ *ret_x_size = x_size; ++ if (ret_y) ++ *ret_y = TAKE_PTR(y); ++ if (ret_y_size) ++ *ret_y_size = y_size; ++ ++ return 0; ++} ++ ++/* Generate a new ECC key for the specified ECC curve id. */ ++int ecc_pkey_new(int curve_id, EVP_PKEY **ret) { ++ assert(ret); ++ ++ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); ++ if (!ctx) ++ return log_oom_debug(); ++ ++ if (EVP_PKEY_keygen_init(ctx) <= 0) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize EVP_PKEY_CTX."); ++ ++ if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_id) <= 0) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ECC curve %d.", curve_id); ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ++ if (EVP_PKEY_keygen(ctx, &pkey) <= 0) ++ return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate ECC key."); ++ ++ *ret = TAKE_PTR(pkey); ++ ++ return 0; ++} ++ + int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size) { + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* m = NULL; + _cleanup_free_ void *d = NULL, *h = NULL; +diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h +index b9aacfd276..90158f589b 100644 +--- a/src/shared/openssl-util.h ++++ b/src/shared/openssl-util.h +@@ -9,6 +9,7 @@ + #if HAVE_OPENSSL + # include + # include ++# include + # include + # include + # include +@@ -24,6 +25,7 @@ + # include + # endif + ++DEFINE_TRIVIAL_CLEANUP_FUNC_FULL_MACRO(void*, OPENSSL_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(X509_NAME*, X509_NAME_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_PKEY_CTX*, EVP_PKEY_CTX_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER_CTX*, EVP_CIPHER_CTX_free, NULL); +@@ -65,6 +67,12 @@ int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size + + int rsa_pkey_to_n_e(const EVP_PKEY *pkey, void **ret_n, size_t *ret_n_size, void **ret_e, size_t *ret_e_size); + ++int ecc_pkey_from_curve_x_y(int curve_id, const void *x, size_t x_size, const void *y, size_t y_size, EVP_PKEY **ret); ++ ++int ecc_pkey_to_curve_x_y(const EVP_PKEY *pkey, int *ret_curve_id, void **ret_x, size_t *ret_x_size, void **ret_y, size_t *ret_y_size); ++ ++int ecc_pkey_new(int curve_id, EVP_PKEY **ret); ++ + int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size); + + #else diff --git a/SOURCES/0574-test-add-DEFINE_HEX_PTR-helper-function.patch b/SOURCES/0574-test-add-DEFINE_HEX_PTR-helper-function.patch new file mode 100644 index 0000000..e25a583 --- /dev/null +++ b/SOURCES/0574-test-add-DEFINE_HEX_PTR-helper-function.patch @@ -0,0 +1,87 @@ +From a5f127f8196793c7a4acdfe3fb39e3dfabc1635e Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 6 Jul 2023 11:21:29 -0400 +Subject: [PATCH] test: add DEFINE_HEX_PTR() helper function + +Use function in test-tpm2 to convert hex strings to void* and len vars. + +(cherry picked from commit 0fdcfa7812d88fb40b0cd5496e2f96d77b42efb4) + +Related: RHEL-16182 +--- + src/shared/tests.h | 6 ++++++ + src/test/test-tpm2.c | 25 ++++++++----------------- + 2 files changed, 14 insertions(+), 17 deletions(-) + +diff --git a/src/shared/tests.h b/src/shared/tests.h +index a2f7f38c41..6c2a2f1df2 100644 +--- a/src/shared/tests.h ++++ b/src/shared/tests.h +@@ -38,6 +38,12 @@ bool have_namespaces(void); + #define CAN_MEMLOCK_SIZE (512 * 1024U) + bool can_memlock(void); + ++/* Define void* buffer and size_t length variables from a hex string. */ ++#define DEFINE_HEX_PTR(name, hex) \ ++ _cleanup_free_ void *name = NULL; \ ++ size_t name##_len = 0; \ ++ assert_se(unhexmem(hex, strlen(hex), &name, &name##_len) >= 0); ++ + #define TEST_REQ_RUNNING_SYSTEMD(x) \ + if (sd_booted() > 0) { \ + x; \ +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 4c22d7c691..31988ff6f1 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -433,17 +433,14 @@ static bool digest_check(const TPM2B_DIGEST *digest, const char *expect) { + } + + static void digest_init(TPM2B_DIGEST *digest, const char *hash) { +- _cleanup_free_ void *h = NULL; +- size_t s = 0; +- + assert_se(strlen(hash) <= sizeof(digest->buffer) * 2); + +- assert_se(unhexmem(hash, strlen(hash), &h, &s) == 0); ++ DEFINE_HEX_PTR(h, hash); + + /* Make sure the length matches a known hash algorithm */ +- assert_se(IN_SET(s, TPM2_SHA1_DIGEST_SIZE, TPM2_SHA256_DIGEST_SIZE, TPM2_SHA384_DIGEST_SIZE, TPM2_SHA512_DIGEST_SIZE)); ++ assert_se(IN_SET(h_len, TPM2_SHA1_DIGEST_SIZE, TPM2_SHA256_DIGEST_SIZE, TPM2_SHA384_DIGEST_SIZE, TPM2_SHA512_DIGEST_SIZE)); + +- *digest = TPM2B_DIGEST_MAKE(h, s); ++ *digest = TPM2B_DIGEST_MAKE(h, h_len); + + assert_se(digest_check(digest, hash)); + } +@@ -720,11 +717,8 @@ static void tpm2b_public_init(TPM2B_PUBLIC *public) { + }, + }; + +- const char *key = "9ec7341c52093ac40a1965a5df10432513c539adcf905e30577ab6ebc88ffe53cd08cef12ed9bec6125432f4fada3629b8b96d31b8f507aa35029188fe396da823fcb236027f7fbb01b0da3d87be7f999390449ced604bdf7e26c48657cc0671000f1147da195c3861c96642e54427cb7a11572e07567ec3fd6316978abc4bd92b27bb0a0e4958e599804eeb41d682b3b7fc1f960209f80a4fb8a1b64abfd96bf5d554e73cdd6ad1c8becb4fcf5e8f0c3e621d210e5e2f308f6520ad9a966779231b99f06c5989e5a23a9415c8808ab89ce81117632e2f8461cd4428bded40979236aeadafe8de3f51660a45e1dbc87694e6a36360201cca3ff9e7263e712727"; +- _cleanup_free_ void *mem = NULL; +- size_t len = 0; +- assert_se(unhexmem(key, strlen(key), &mem, &len) == 0); +- tpmt.unique.rsa = TPM2B_PUBLIC_KEY_RSA_MAKE(mem, len); ++ DEFINE_HEX_PTR(key, "9ec7341c52093ac40a1965a5df10432513c539adcf905e30577ab6ebc88ffe53cd08cef12ed9bec6125432f4fada3629b8b96d31b8f507aa35029188fe396da823fcb236027f7fbb01b0da3d87be7f999390449ced604bdf7e26c48657cc0671000f1147da195c3861c96642e54427cb7a11572e07567ec3fd6316978abc4bd92b27bb0a0e4958e599804eeb41d682b3b7fc1f960209f80a4fb8a1b64abfd96bf5d554e73cdd6ad1c8becb4fcf5e8f0c3e621d210e5e2f308f6520ad9a966779231b99f06c5989e5a23a9415c8808ab89ce81117632e2f8461cd4428bded40979236aeadafe8de3f51660a45e1dbc87694e6a36360201cca3ff9e7263e712727"); ++ tpmt.unique.rsa = TPM2B_PUBLIC_KEY_RSA_MAKE(key, key_len); + + public->publicArea = tpmt; + } +@@ -737,12 +731,9 @@ TEST(calculate_name) { + assert_se(tpm2_calculate_name(&public.publicArea, &name) == 0); + assert_se(name.size == SHA256_DIGEST_SIZE + 2); + +- const char *expect = "000be78f74a470dd92e979ca067cdb2293a35f075e8560b436bd2ccea5da21486a07"; +- _cleanup_free_ char *h = hexmem(name.name, name.size); +- assert_se(h); +- +- assert_se(strlen(expect) == strlen(h)); +- assert_se(streq(expect, h)); ++ DEFINE_HEX_PTR(e, "000be78f74a470dd92e979ca067cdb2293a35f075e8560b436bd2ccea5da21486a07"); ++ assert_se(name.size == e_len); ++ assert_se(memcmp(name.name, e, e_len) == 0); + } + + TEST(calculate_policy_auth_value) { diff --git a/SOURCES/0575-openssl-add-test-openssl.patch b/SOURCES/0575-openssl-add-test-openssl.patch new file mode 100644 index 0000000..8ce997a --- /dev/null +++ b/SOURCES/0575-openssl-add-test-openssl.patch @@ -0,0 +1,133 @@ +From 69bea63314682fb1313bd2962460e27db7c1c184 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 6 Jul 2023 11:21:29 -0400 +Subject: [PATCH] openssl: add test-openssl + +Add openssl unit tests. + +(cherry picked from commit cffeee92bbcbd9954a7d3fc909b43d8cc33ff6e3) + +Related: RHEL-16182 +--- + src/test/meson.build | 3 ++ + src/test/test-openssl.c | 97 +++++++++++++++++++++++++++++++++++++++++ + 2 files changed, 100 insertions(+) + create mode 100644 src/test/test-openssl.c + +diff --git a/src/test/meson.build b/src/test/meson.build +index 976794b22b..726f34426c 100644 +--- a/src/test/meson.build ++++ b/src/test/meson.build +@@ -685,6 +685,9 @@ tests += [ + [files('test-sha256.c')], + + [files('test-bitfield.c')], ++ ++ [files('test-openssl.c'), ++ [], [libopenssl], [], 'HAVE_OPENSSL'], + ] + + ############################################################ +diff --git a/src/test/test-openssl.c b/src/test/test-openssl.c +new file mode 100644 +index 0000000000..7672c8959d +--- /dev/null ++++ b/src/test/test-openssl.c +@@ -0,0 +1,97 @@ ++/* SPDX-License-Identifier: LGPL-2.1-or-later */ ++ ++#include "hexdecoct.h" ++#include "openssl-util.h" ++#include "tests.h" ++ ++TEST(openssl_pkey_from_pem) { ++ DEFINE_HEX_PTR(key_ecc, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451674145726a6e4575424c73496c3972687068777976584e50686a346a426e500a44586e794a304b395579724e6764365335413532542b6f5376746b436a365a726c34685847337741515558706f426c532b7448717452714c35513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a"); ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_ecc = NULL; ++ assert_se(openssl_pkey_from_pem(key_ecc, key_ecc_len, &pkey_ecc) >= 0); ++ ++ _cleanup_free_ void *x = NULL, *y = NULL; ++ size_t x_len, y_len; ++ int curve_id; ++ assert_se(ecc_pkey_to_curve_x_y(pkey_ecc, &curve_id, &x, &x_len, &y, &y_len) >= 0); ++ assert_se(curve_id == NID_X9_62_prime256v1); ++ ++ DEFINE_HEX_PTR(expected_x, "ae39c4b812ec225f6b869870caf5cd3e18f88c19cf0d79f22742bd532acd81de"); ++ assert_se(x_len == expected_x_len); ++ assert_se(memcmp(x, expected_x, x_len) == 0); ++ ++ DEFINE_HEX_PTR(expected_y, "92e40e764fea12bed9028fa66b9788571b7c004145e9a01952fad1eab51a8be5"); ++ assert_se(y_len == expected_y_len); ++ assert_se(memcmp(y, expected_y, y_len) == 0); ++ ++ DEFINE_HEX_PTR(key_rsa, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541795639434950652f505852337a436f63787045300a6a575262546c3568585844436b472f584b79374b6d2f4439584942334b734f5a31436a5937375571372f674359363170697838697552756a73413464503165380a593445336c68556d374a332b6473766b626f4b64553243626d52494c2f6675627771694c4d587a41673342575278747234547545443533527a373634554650640a307a70304b68775231496230444c67772f344e67566f314146763378784b4d6478774d45683567676b73733038326332706c354a504e32587677426f744e6b4d0a5471526c745a4a35355244436170696e7153334577376675646c4e735851357746766c7432377a7637344b585165616d704c59433037584f6761304c676c536b0a79754774586b6a50542f735542544a705374615769674d5a6f714b7479563463515a58436b4a52684459614c47587673504233687a766d5671636e6b47654e540a65774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a"); ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_rsa = NULL; ++ assert_se(openssl_pkey_from_pem(key_rsa, key_rsa_len, &pkey_rsa) >= 0); ++ ++ _cleanup_free_ void *n = NULL, *e = NULL; ++ size_t n_len, e_len; ++ assert_se(rsa_pkey_to_n_e(pkey_rsa, &n, &n_len, &e, &e_len) >= 0); ++ ++ DEFINE_HEX_PTR(expected_n, "c95f4220f7bf3d7477cc2a1cc691348d645b4e5e615d70c2906fd72b2eca9bf0fd5c80772ac399d428d8efb52aeff80263ad698b1f22b91ba3b00e1d3f57bc638137961526ec9dfe76cbe46e829d53609b99120bfdfb9bc2a88b317cc0837056471b6be13b840f9dd1cfbeb85053ddd33a742a1c11d486f40cb830ff8360568d4016fdf1c4a31dc7030487982092cb34f36736a65e493cdd97bf0068b4d90c4ea465b59279e510c26a98a7a92dc4c3b7ee76536c5d0e7016f96ddbbcefef829741e6a6a4b602d3b5ce81ad0b8254a4cae1ad5e48cf4ffb140532694ad6968a0319a2a2adc95e1c4195c29094610d868b197bec3c1de1cef995a9c9e419e3537b"); ++ assert_se(n_len == expected_n_len); ++ assert_se(memcmp(n, expected_n, n_len) == 0); ++ ++ DEFINE_HEX_PTR(expected_e, "010001"); ++ assert_se(e_len == expected_e_len); ++ assert_se(memcmp(e, expected_e, e_len) == 0); ++} ++ ++TEST(rsa_pkey_n_e) { ++ DEFINE_HEX_PTR(n, "e3975a2124a7c9fe57752d106314ff62f6da731632eac221f1c0255bdcf2a34eeb21e3ab89ba8759ddad3b68be99463c7f03f3d004028a35e6f7c6596aeab2558d490f1e1c38aed2ff796bda8d6d55704eefb6ac55842dd6e606bb707f66acc02f0db2aed0dabab885bd0c850f1bdc8ac4b6bc1f74858db8ca2ab57a3d4217c091e9cd78727a2e36b8126ea629e81fecc69b0bea601000a6c0b749c5be16f53f4fa9f208a581d804234eb6526ba3fee9822d58d1ab9cac2761d7f630eb7ad6054dff0856d41aea219e1adfd87256aa1532202a070f4b1044e718d1f38bbc5a4b1fcb024f04afaafda5edeacfdf0d0bdf35c359acd059e3edb5024e588458f9b5"); ++ uint32_t e = htobe32(0x10001); ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ++ assert_se(rsa_pkey_from_n_e(n, n_len, &e, sizeof(e), &pkey) >= 0); ++ ++ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL); ++ assert_se(ctx); ++ assert_se(EVP_PKEY_verify_init(ctx) == 1); ++ ++ const char *msg = "this is a secret"; ++ DEFINE_HEX_PTR(sig, "14b53e0c6ad99a350c3d7811e8160f4ae03ad159815bb91bddb9735b833588df2eac221fbd3fc4ece0dd63bfaeddfdaf4ae67021e759f3638bc194836413414f54e8c4d01c9c37fa4488ea2ef772276b8a33822a53c97b1c35acfb4bc621cfb8fad88f0cf7d5491f05236886afbf9ed47f9469536482f50f74a20defa59d99676bed62a17b5eb98641df5a2f8080fa4b24f2749cc152fa65ba34c14022fcb27f1b36f52021950d7b9b6c3042c50b84cfb7d55a5f9235bfd58e1bf1f604eb93416c5fb5fd90cb68f1270dfa9daf67f52c604f62c2f2beee5e7e672b0e6e9833dd43dba99b77668540c850c9a81a5ea7aaf6297383e6135bd64572362333121fc7"); ++ assert_se(EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); ++ ++ DEFINE_HEX_PTR(invalid_sig, "1234"); ++ assert_se(EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); ++ ++ _cleanup_free_ void *n2 = NULL, *e2 = NULL; ++ size_t n2_size, e2_size; ++ assert_se(rsa_pkey_to_n_e(pkey, &n2, &n2_size, &e2, &e2_size) >= 0); ++ assert_se(memcmp_nn(n, n_len, n2, n2_size) == 0); ++ assert_se(e2_size <= sizeof(uint32_t)); ++ assert_se(memcmp(&((uint8_t*) &e)[sizeof(uint32_t) - e2_size], e2, e2_size) == 0); ++} ++ ++TEST(ecc_pkey_curve_x_y) { ++ int curveid = NID_X9_62_prime256v1; ++ DEFINE_HEX_PTR(x, "2830d2c8f65d3efbef12303b968b91692f8bd04045dcb8a9656374e4ae61d818"); ++ DEFINE_HEX_PTR(y, "8a80750f76729defdcc2a4bc1a91c22e60109dd6e1ffde634a650a20bab172e9"); ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ++ assert_se(ecc_pkey_from_curve_x_y(curveid, x, x_len, y, y_len, &pkey) >= 0); ++ ++ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL); ++ assert_se(ctx); ++ assert_se(EVP_PKEY_verify_init(ctx) == 1); ++ ++ const char *msg = "this is a secret"; ++ DEFINE_HEX_PTR(sig, "3045022100f6ca10f7ed57a020679899b26dd5ac5a1079265885e2a6477f527b6a3f02b5ca02207b550eb3e7b69360aff977f7f6afac99c3f28266b6c5338ce373f6b59263000a"); ++ assert_se(EVP_PKEY_verify(ctx, sig, sig_len, (unsigned char*) msg, strlen(msg)) == 1); ++ ++ DEFINE_HEX_PTR(invalid_sig, "1234"); ++ assert_se(EVP_PKEY_verify(ctx, invalid_sig, invalid_sig_len, (unsigned char*) msg, strlen(msg)) != 1); ++ ++ _cleanup_free_ void *x2 = NULL, *y2 = NULL; ++ size_t x2_size, y2_size; ++ int curveid2; ++ assert_se(ecc_pkey_to_curve_x_y(pkey, &curveid2, &x2, &x2_size, &y2, &y2_size) >= 0); ++ assert_se(curveid == curveid2); ++ assert_se(memcmp_nn(x, x_len, x2, x2_size) == 0); ++ assert_se(memcmp_nn(y, y_len, y2, y2_size) == 0); ++} ++ ++DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0576-tpm2-add-functions-to-convert-TPM2B_PUBLIC-to-from-o.patch b/SOURCES/0576-tpm2-add-functions-to-convert-TPM2B_PUBLIC-to-from-o.patch new file mode 100644 index 0000000..d2bf3c8 --- /dev/null +++ b/SOURCES/0576-tpm2-add-functions-to-convert-TPM2B_PUBLIC-to-from-o.patch @@ -0,0 +1,672 @@ +From 4276edfa925b9cd2f15685712a58fe68790b8d77 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 27 Jun 2023 15:03:08 -0400 +Subject: [PATCH] tpm2: add functions to convert TPM2B_PUBLIC to/from openssl + pkey or PEM + +Add functions to convert a PEM or pkey to TPM2B_PUBLIC, and functions to +convert TPM2B_PUBLIC to pkey or fingerprint. + +Supports both RSA and ECC keys. + +Add ECC support to some test-tpm2 tests, and tests to cover the newly added functions. + +(cherry picked from commit e3acb4d24c68291376b11bea5787112978e2775f) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 347 ++++++++++++++++++++++++++--------------- + src/shared/tpm2-util.h | 9 ++ + src/test/meson.build | 5 +- + src/test/test-tpm2.c | 153 ++++++++++++++++-- + 4 files changed, 373 insertions(+), 141 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index fadf3af9d6..b73457843d 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -15,7 +15,6 @@ + #include "hmac.h" + #include "lockfile-util.h" + #include "memory-util.h" +-#include "openssl-util.h" + #include "parse-util.h" + #include "random-util.h" + #include "sha256.h" +@@ -2848,124 +2847,6 @@ static int tpm2_make_policy_session( + return 0; + } + +-static int openssl_pubkey_to_tpm2_pubkey( +- const void *pubkey, +- size_t pubkey_size, +- TPM2B_PUBLIC *output, +- void **ret_fp, +- size_t *ret_fp_size) { +- +-#if HAVE_OPENSSL +-#if OPENSSL_VERSION_MAJOR >= 3 +- _cleanup_(BN_freep) BIGNUM *n = NULL, *e = NULL; +-#else +- const BIGNUM *n = NULL, *e = NULL; +- const RSA *rsa = NULL; +-#endif +- int r, n_bytes, e_bytes; +- +- assert(pubkey); +- assert(pubkey_size > 0); +- assert(output); +- +- /* Converts an OpenSSL public key to a structure that the TPM chip can process. */ +- +- _cleanup_fclose_ FILE *f = NULL; +- f = fmemopen((void*) pubkey, pubkey_size, "r"); +- if (!f) +- return log_oom(); +- +- _cleanup_(EVP_PKEY_freep) EVP_PKEY *input = NULL; +- input = PEM_read_PUBKEY(f, NULL, NULL, NULL); +- if (!input) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to parse PEM public key."); +- +- if (EVP_PKEY_base_id(input) != EVP_PKEY_RSA) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Provided public key is not an RSA key."); +- +-#if OPENSSL_VERSION_MAJOR >= 3 +- if (!EVP_PKEY_get_bn_param(input, OSSL_PKEY_PARAM_RSA_N, &n)) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA modulus from public key."); +-#else +- rsa = EVP_PKEY_get0_RSA(input); +- if (!rsa) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to extract RSA key from public key."); +- +- n = RSA_get0_n(rsa); +- if (!n) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA modulus from public key."); +-#endif +- +- n_bytes = BN_num_bytes(n); +- assert_se(n_bytes > 0); +- if ((size_t) n_bytes > sizeof_field(TPM2B_PUBLIC, publicArea.unique.rsa.buffer)) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RSA modulus too large for TPM2 public key object."); +- +-#if OPENSSL_VERSION_MAJOR >= 3 +- if (!EVP_PKEY_get_bn_param(input, OSSL_PKEY_PARAM_RSA_E, &e)) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA exponent from public key."); +-#else +- e = RSA_get0_e(rsa); +- if (!e) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to get RSA exponent from public key."); +-#endif +- +- e_bytes = BN_num_bytes(e); +- assert_se(e_bytes > 0); +- if ((size_t) e_bytes > sizeof_field(TPM2B_PUBLIC, publicArea.parameters.rsaDetail.exponent)) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "RSA exponent too large for TPM2 public key object."); +- +- *output = (TPM2B_PUBLIC) { +- .size = sizeof(TPMT_PUBLIC), +- .publicArea = { +- .type = TPM2_ALG_RSA, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_DECRYPT | TPMA_OBJECT_SIGN_ENCRYPT | TPMA_OBJECT_USERWITHAUTH, +- .parameters.rsaDetail = { +- .scheme = { +- .scheme = TPM2_ALG_NULL, +- .details.anySig.hashAlg = TPM2_ALG_NULL, +- }, +- .symmetric = { +- .algorithm = TPM2_ALG_NULL, +- .mode.sym = TPM2_ALG_NULL, +- }, +- .keyBits = n_bytes * 8, +- /* .exponent will be filled in below. */ +- }, +- .unique = { +- .rsa.size = n_bytes, +- /* .rsa.buffer will be filled in below. */ +- }, +- }, +- }; +- +- if (BN_bn2bin(n, output->publicArea.unique.rsa.buffer) <= 0) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to convert RSA modulus."); +- +- if (BN_bn2bin(e, (unsigned char*) &output->publicArea.parameters.rsaDetail.exponent) <= 0) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to convert RSA exponent."); +- +- if (ret_fp) { +- _cleanup_free_ void *fp = NULL; +- size_t fp_size; +- +- assert(ret_fp_size); +- +- r = pubkey_fingerprint(input, EVP_sha256(), &fp, &fp_size); +- if (r < 0) +- return log_error_errno(r, "Failed to calculate public key fingerprint: %m"); +- +- *ret_fp = TAKE_PTR(fp); +- *ret_fp_size = fp_size; +- } +- +- return 0; +-#else /* HAVE_OPENSSL */ +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); +-#endif +-} +- + static int find_signature( + JsonVariant *v, + const TPML_PCR_SELECTION *pcr_selection, +@@ -3575,6 +3456,212 @@ static int tpm2_build_sealing_policy( + return 0; + } + ++#if HAVE_OPENSSL ++static int tpm2_ecc_curve_from_openssl_curve_id(int curve_id, TPM2_ECC_CURVE *ret) { ++ assert(ret); ++ ++ switch (curve_id) { ++ case NID_X9_62_prime192v1: *ret = TPM2_ECC_NIST_P192; return 0; ++ case NID_secp224r1: *ret = TPM2_ECC_NIST_P192; return 0; ++ case NID_X9_62_prime256v1: *ret = TPM2_ECC_NIST_P256; return 0; ++ case NID_secp384r1: *ret = TPM2_ECC_NIST_P384; return 0; ++ case NID_secp521r1: *ret = TPM2_ECC_NIST_P521; return 0; ++ case NID_sm2: *ret = TPM2_ECC_SM2_P256; return 0; ++ } ++ ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "Openssl ECC curve id %d not supported.", curve_id); ++} ++ ++static int tpm2_ecc_curve_to_openssl_curve_id(TPM2_ECC_CURVE curve, int *ret) { ++ assert(ret); ++ ++ switch (curve) { ++ case TPM2_ECC_NIST_P192: *ret = NID_X9_62_prime192v1; return 0; ++ case TPM2_ECC_NIST_P224: *ret = NID_secp224r1; return 0; ++ case TPM2_ECC_NIST_P256: *ret = NID_X9_62_prime256v1; return 0; ++ case TPM2_ECC_NIST_P384: *ret = NID_secp384r1; return 0; ++ case TPM2_ECC_NIST_P521: *ret = NID_secp521r1; return 0; ++ case TPM2_ECC_SM2_P256: *ret = NID_sm2; return 0; ++ } ++ ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "TPM2 ECC curve %u not supported.", curve); ++} ++ ++#define TPM2_RSA_DEFAULT_EXPONENT UINT32_C(0x10001) ++ ++int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret) { ++ int r; ++ ++ assert(public); ++ assert(ret); ++ ++ const TPMT_PUBLIC *p = &public->publicArea; ++ if (p->type == TPM2_ALG_ECC) { ++ int curve_id; ++ r = tpm2_ecc_curve_to_openssl_curve_id(p->parameters.eccDetail.curveID, &curve_id); ++ if (r < 0) ++ return r; ++ ++ const TPMS_ECC_POINT *point = &p->unique.ecc; ++ return ecc_pkey_from_curve_x_y( ++ curve_id, ++ point->x.buffer, ++ point->x.size, ++ point->y.buffer, ++ point->y.size, ++ ret); ++ } ++ ++ if (p->type == TPM2_ALG_RSA) { ++ /* TPM specification Part 2 ("Structures") section for TPMS_RSA_PARAMS states "An exponent of ++ * zero indicates that the exponent is the default of 2^16 + 1". */ ++ uint32_t exponent = htobe32(p->parameters.rsaDetail.exponent ?: TPM2_RSA_DEFAULT_EXPONENT); ++ return rsa_pkey_from_n_e( ++ p->unique.rsa.buffer, ++ p->unique.rsa.size, ++ &exponent, ++ sizeof(exponent), ++ ret); ++ } ++ ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "TPM2 asymmetric algorithm 0x%" PRIx16 " not supported.", p->type); ++} ++ ++int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) { ++ int key_id, r; ++ ++ assert(pkey); ++ assert(ret); ++ ++ TPMT_PUBLIC public = { ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_DECRYPT | TPMA_OBJECT_SIGN_ENCRYPT | TPMA_OBJECT_USERWITHAUTH, ++ .parameters.asymDetail = { ++ .symmetric.algorithm = TPM2_ALG_NULL, ++ .scheme.scheme = TPM2_ALG_NULL, ++ }, ++ }; ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ key_id = EVP_PKEY_get_id(pkey); ++#else ++ key_id = EVP_PKEY_id(pkey); ++#endif ++ ++ if (key_id == EVP_PKEY_EC) { ++ public.type = TPM2_ALG_ECC; ++ ++ int curve_id; ++ _cleanup_free_ void *x = NULL, *y = NULL; ++ size_t x_size, y_size; ++ r = ecc_pkey_to_curve_x_y(pkey, &curve_id, &x, &x_size, &y, &y_size); ++ if (r < 0) ++ return log_error_errno(r, "Could not get ECC key curve/x/y: %m"); ++ ++ TPM2_ECC_CURVE curve; ++ r = tpm2_ecc_curve_from_openssl_curve_id(curve_id, &curve); ++ if (r < 0) ++ return r; ++ ++ public.parameters.eccDetail.curveID = curve; ++ ++ public.parameters.eccDetail.kdf.scheme = TPM2_ALG_NULL; ++ ++ r = TPM2B_ECC_PARAMETER_CHECK_SIZE(x_size); ++ if (r < 0) ++ return log_error_errno(r, "ECC key x size %zu too large.", x_size); ++ ++ public.unique.ecc.x = TPM2B_ECC_PARAMETER_MAKE(x, x_size); ++ ++ r = TPM2B_ECC_PARAMETER_CHECK_SIZE(y_size); ++ if (r < 0) ++ return log_error_errno(r, "ECC key y size %zu too large.", y_size); ++ ++ public.unique.ecc.y = TPM2B_ECC_PARAMETER_MAKE(y, y_size); ++ } else if (key_id == EVP_PKEY_RSA) { ++ public.type = TPM2_ALG_RSA; ++ ++ _cleanup_free_ void *n = NULL, *e = NULL; ++ size_t n_size, e_size; ++ r = rsa_pkey_to_n_e(pkey, &n, &n_size, &e, &e_size); ++ if (r < 0) ++ return log_error_errno(r, "Could not get RSA key n/e: %m"); ++ ++ r = TPM2B_PUBLIC_KEY_RSA_CHECK_SIZE(n_size); ++ if (r < 0) ++ return log_error_errno(r, "RSA key n size %zu too large.", n_size); ++ ++ public.unique.rsa = TPM2B_PUBLIC_KEY_RSA_MAKE(n, n_size); ++ public.parameters.rsaDetail.keyBits = n_size * 8; ++ ++ if (sizeof(uint32_t) < e_size) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "RSA key e size %zu too large.", e_size); ++ ++ uint32_t exponent = 0; ++ memcpy((void*) &exponent, e, e_size); ++ exponent = be32toh(exponent) >> (32 - e_size * 8); ++ if (exponent == TPM2_RSA_DEFAULT_EXPONENT) ++ exponent = 0; ++ public.parameters.rsaDetail.exponent = exponent; ++ } else ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "EVP_PKEY type %d not supported.", key_id); ++ ++ *ret = (TPM2B_PUBLIC) { ++ .size = sizeof(public), ++ .publicArea = public, ++ }; ++ ++ return 0; ++} ++#endif ++ ++int tpm2_tpm2b_public_to_fingerprint( ++ const TPM2B_PUBLIC *public, ++ void **ret_fingerprint, ++ size_t *ret_fingerprint_size) { ++ ++#if HAVE_OPENSSL ++ int r; ++ ++ assert(public); ++ assert(ret_fingerprint); ++ assert(ret_fingerprint_size); ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ++ r = tpm2_tpm2b_public_to_openssl_pkey(public, &pkey); ++ if (r < 0) ++ return r; ++ ++ /* Hardcode fingerprint to SHA256 */ ++ return pubkey_fingerprint(pkey, EVP_sha256(), ret_fingerprint, ret_fingerprint_size); ++#else ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); ++#endif ++} ++ ++int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret) { ++#if HAVE_OPENSSL ++ int r; ++ ++ assert(pem); ++ assert(ret); ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ++ r = openssl_pkey_from_pem(pem, pem_size, &pkey); ++ if (r < 0) ++ return r; ++ ++ return tpm2_tpm2b_public_from_openssl_pkey(pkey, ret); ++#else ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); ++#endif ++} ++ + int tpm2_seal(const char *device, + uint32_t hash_pcr_mask, + const void *pubkey, +@@ -3652,12 +3739,11 @@ int tpm2_seal(const char *device, + return r; + } + +- TPM2B_PUBLIC pubkey_tpm2, *authorize_key = NULL; ++ TPM2B_PUBLIC pubkey_tpm2b; + if (pubkey) { +- r = openssl_pubkey_to_tpm2_pubkey(pubkey, pubkey_size, &pubkey_tpm2, NULL, NULL); ++ r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &pubkey_tpm2b); + if (r < 0) +- return r; +- authorize_key = &pubkey_tpm2; ++ return log_error_errno(r, "Could not create TPMT_PUBLIC: %m"); + } + + TPM2B_DIGEST policy_digest; +@@ -3668,7 +3754,7 @@ int tpm2_seal(const char *device, + r = tpm2_calculate_sealing_policy( + hash_pcr_values, + n_hash_pcr_values, +- authorize_key, ++ pubkey ? &pubkey_tpm2b : NULL, + !!pin, + &policy_digest); + if (r < 0) +@@ -3943,14 +4029,17 @@ int tpm2_unseal(const char *device, + if (r < 0) + return r; + +- TPM2B_PUBLIC pubkey_tpm2, *authorize_key = NULL; ++ TPM2B_PUBLIC pubkey_tpm2b; + _cleanup_free_ void *fp = NULL; + size_t fp_size = 0; + if (pubkey) { +- r = openssl_pubkey_to_tpm2_pubkey(pubkey, pubkey_size, &pubkey_tpm2, &fp, &fp_size); ++ r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &pubkey_tpm2b); + if (r < 0) +- return r; +- authorize_key = &pubkey_tpm2; ++ return log_error_errno(r, "Could not create TPMT_PUBLIC: %m"); ++ ++ r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp, &fp_size); ++ if (r < 0) ++ return log_error_errno(r, "Could not get key fingerprint: %m"); + } + + /* +@@ -3987,7 +4076,7 @@ int tpm2_unseal(const char *device, + policy_session, + hash_pcr_mask, + pcr_bank, +- authorize_key, ++ pubkey ? &pubkey_tpm2b : NULL, + fp, fp_size, + pubkey_pcr_mask, + signature, +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 2dffcf922d..0938fe35eb 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -7,6 +7,7 @@ + #include "io-util.h" + #include "json.h" + #include "macro.h" ++#include "openssl-util.h" + #include "sha256.h" + + typedef enum TPM2Flags { +@@ -157,6 +158,14 @@ int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_value + int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); + int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); + ++#if HAVE_OPENSSL ++int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret); ++int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret); ++#endif ++ ++int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret); ++int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fingerprint, size_t *ret_fingerprint_size); ++ + /* The tpm2-tss library has many structs that are simply a combination of an array (or object) and + * size. These macros allow easily initializing or assigning instances of such structs from an existing + * buffer/object and size, while also checking the size for safety with the struct buffer/object size. If the +diff --git a/src/test/meson.build b/src/test/meson.build +index 726f34426c..5430e72ab5 100644 +--- a/src/test/meson.build ++++ b/src/test/meson.build +@@ -496,7 +496,8 @@ tests += [ + + [files('test-sleep.c')], + +- [files('test-tpm2.c')], ++ [files('test-tpm2.c'), ++ [], [libopenssl], [], 'HAVE_OPENSSL'], + + [files('test-replace-var.c')], + +@@ -690,8 +691,6 @@ tests += [ + [], [libopenssl], [], 'HAVE_OPENSSL'], + ] + +-############################################################ +- + # define some tests here, because the link_with deps were not defined earlier + + tests += [ +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 31988ff6f1..a3a2700a8a 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -701,7 +701,7 @@ TEST(parse_pcr_argument) { + check_parse_pcr_argument_to_mask("debug+24", -EINVAL); + } + +-static void tpm2b_public_init(TPM2B_PUBLIC *public) { ++static void tpm2b_public_rsa_init(TPM2B_PUBLIC *public, const char *rsa_n) { + TPMT_PUBLIC tpmt = { + .type = TPM2_ALG_RSA, + .nameAlg = TPM2_ALG_SHA256, +@@ -717,23 +717,149 @@ static void tpm2b_public_init(TPM2B_PUBLIC *public) { + }, + }; + +- DEFINE_HEX_PTR(key, "9ec7341c52093ac40a1965a5df10432513c539adcf905e30577ab6ebc88ffe53cd08cef12ed9bec6125432f4fada3629b8b96d31b8f507aa35029188fe396da823fcb236027f7fbb01b0da3d87be7f999390449ced604bdf7e26c48657cc0671000f1147da195c3861c96642e54427cb7a11572e07567ec3fd6316978abc4bd92b27bb0a0e4958e599804eeb41d682b3b7fc1f960209f80a4fb8a1b64abfd96bf5d554e73cdd6ad1c8becb4fcf5e8f0c3e621d210e5e2f308f6520ad9a966779231b99f06c5989e5a23a9415c8808ab89ce81117632e2f8461cd4428bded40979236aeadafe8de3f51660a45e1dbc87694e6a36360201cca3ff9e7263e712727"); ++ DEFINE_HEX_PTR(key, rsa_n); + tpmt.unique.rsa = TPM2B_PUBLIC_KEY_RSA_MAKE(key, key_len); + ++ public->size = sizeof(tpmt); + public->publicArea = tpmt; + } + ++static void tpm2b_public_ecc_init(TPM2B_PUBLIC *public, TPMI_ECC_CURVE curve, const char *x, const char *y) { ++ TPMT_PUBLIC tpmt = { ++ .type = TPM2_ALG_ECC, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, ++ .parameters.eccDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .curveID = curve, ++ .kdf.scheme = TPM2_ALG_NULL, ++ }, ++ }; ++ ++ DEFINE_HEX_PTR(buf_x, x); ++ tpmt.unique.ecc.x = TPM2B_ECC_PARAMETER_MAKE(buf_x, buf_x_len); ++ ++ DEFINE_HEX_PTR(buf_y, y); ++ tpmt.unique.ecc.y = TPM2B_ECC_PARAMETER_MAKE(buf_y, buf_y_len); ++ ++ public->size = sizeof(tpmt); ++ public->publicArea = tpmt; ++} ++ ++#if HAVE_OPENSSL ++TEST(tpm2b_public_to_openssl_pkey) { ++ DEFINE_HEX_PTR(msg, "edc64c6523778961fe9ba03ab7d624b27ca1dd5b01e7734cc6c891d50db04269"); ++ TPM2B_PUBLIC public; ++ ++ /* RSA */ ++ tpm2b_public_rsa_init(&public, "d71cff5bba2173f0434a389171048e7da8cf8409b892c62946481cc383089bc754324620967fea3d00a02a717cdda4bfe1525ad957d294b88434e0a3933e86fb40f234e4935fd2ba27eb1d21da87efa466b74eb4ad18d26059904643441cf402ee933d138a2151f40459c49d87fef59e2cb822768b2d8689a9b58f82bf9a37e70693f2b2d40dfa388d365c1b1f029a14c4fc8dadb68978ef377d20ff2ca24e7078464c705eab42f531557c9c6dc0df66b506d0c26ef604f8110c64867099267453c71871e7ed22505a09daf102afc34355209ca7680eccc0ed368d148f402fa58cbb6c9d52351f535f09e4e24ad805e149f130edaa2f5e7efed3a4d2d03adb85"); ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_rsa = NULL; ++ assert_se(tpm2_tpm2b_public_to_openssl_pkey(&public, &pkey_rsa) >= 0); ++ ++ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_rsa = EVP_PKEY_CTX_new((EVP_PKEY*) pkey_rsa, NULL); ++ assert_se(ctx_rsa); ++ assert_se(EVP_PKEY_verify_init(ctx_rsa) == 1); ++ assert_se(EVP_PKEY_CTX_set_signature_md(ctx_rsa, EVP_sha256()) > 0); ++ ++ DEFINE_HEX_PTR(sig_rsa, "9f70a9e68911be3ec464cae91126328307bf355872127e042d6c61e0a80982872c151033bcf727abfae5fc9500c923120011e7ef4aa5fc690a59a034697b6022c141b4b209e2df6f4b282288cd9181073fbe7158ce113c79d87623423c1f3996ff931e59cc91db74f8e8656215b1436fc93ddec0f1f8fa8510826e674b250f047e6cba94c95ff98072a286baca94646b577974a1e00d56c21944e38960d8ee90511a2f938e5cf1ac7b7cc7ff8e3ac001d321254d3e4f988b90e9f6f873c26ecd0a12a626b3474833cdbb9e9f793238f6c97ee5b75a1a89bb7a7858d34ecfa6d34ac58d95085e6c4fbbebd47a4364be2725c2c6b3fa15d916f3c0b62a66fe76ae"); ++ assert_se(EVP_PKEY_verify(ctx_rsa, sig_rsa, sig_rsa_len, (unsigned char*) msg, msg_len) == 1); ++ ++ /* ECC */ ++ tpm2b_public_ecc_init(&public, TPM2_ECC_NIST_P256, "6fc0ecf3645c673ab7e86d1ec5b315afb950257c5f68ab23296160006711fac2", "8dd2ef7a2c9ecede91493ba98c8fb3f893aff325c6a1e0f752c657b2d6ca1413"); ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_ecc = NULL; ++ assert_se(tpm2_tpm2b_public_to_openssl_pkey(&public, &pkey_ecc) >= 0); ++ ++ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx_ecc = EVP_PKEY_CTX_new((EVP_PKEY*) pkey_ecc, NULL); ++ assert_se(ctx_ecc); ++ assert_se(EVP_PKEY_verify_init(ctx_ecc) == 1); ++ ++ DEFINE_HEX_PTR(sig_ecc, "304602210092447ac0b5b32e90923f79bb4aba864b9c546a9900cf193a83243d35d189a2110221009a8b4df1dfa85e225eff9c606694d4d205a7a3968c9552f50bc2790209a90001"); ++ assert_se(EVP_PKEY_verify(ctx_ecc, sig_ecc, sig_ecc_len, (unsigned char*) msg, msg_len) == 1); ++} ++ ++static void get_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *ret) { ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ++ TPM2B_PUBLIC p1 = {}, p2 = {}; ++ ++ assert(pem); ++ assert(ret); ++ ++ assert_se(openssl_pkey_from_pem(pem, pem_size, &pkey) >= 0); ++ assert_se(tpm2_tpm2b_public_from_openssl_pkey(pkey, &p1) >= 0); ++ assert_se(tpm2_tpm2b_public_from_pem(pem, pem_size, &p2) >= 0); ++ assert_se(memcmp_nn(&p1, sizeof(p1), &p2, sizeof(p2)) == 0); ++ ++ *ret = p1; ++} ++ ++static void check_tpm2b_public_fingerprint(const TPM2B_PUBLIC *public, const char *hexfp) { ++ DEFINE_HEX_PTR(expected, hexfp); ++ _cleanup_free_ void *fp = NULL; ++ size_t fp_size; ++ ++ assert_se(tpm2_tpm2b_public_to_fingerprint(public, &fp, &fp_size) >= 0); ++ assert_se(memcmp_nn(fp, fp_size, expected, expected_len) == 0); ++} ++ ++TEST(tpm2b_public_from_openssl_pkey) { ++ TPM2B_PUBLIC public; ++ TPMT_PUBLIC *p = &public.publicArea; ++ ++ DEFINE_HEX_PTR(key_ecc, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b77457759484b6f5a497a6a3043415159494b6f5a497a6a30444151634451674145726a6e4575424c73496c3972687068777976584e50686a346a426e500a44586e794a304b395579724e6764365335413532542b6f5376746b436a365a726c34685847337741515558706f426c532b7448717452714c35513d3d0a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a"); ++ get_tpm2b_public_from_pem(key_ecc, key_ecc_len, &public); ++ ++ assert_se(p->type == TPM2_ALG_ECC); ++ assert_se(p->parameters.eccDetail.curveID == TPM2_ECC_NIST_P256); ++ ++ DEFINE_HEX_PTR(expected_x, "ae39c4b812ec225f6b869870caf5cd3e18f88c19cf0d79f22742bd532acd81de"); ++ assert_se(memcmp_nn(p->unique.ecc.x.buffer, p->unique.ecc.x.size, expected_x, expected_x_len) == 0); ++ ++ DEFINE_HEX_PTR(expected_y, "92e40e764fea12bed9028fa66b9788571b7c004145e9a01952fad1eab51a8be5"); ++ assert_se(memcmp_nn(p->unique.ecc.y.buffer, p->unique.ecc.y.size, expected_y, expected_y_len) == 0); ++ ++ check_tpm2b_public_fingerprint(&public, "cd3373293b62a52b48c12100e80ea9bfd806266ce76893a5ec31cb128052d97c"); ++ ++ DEFINE_HEX_PTR(key_rsa, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541795639434950652f505852337a436f63787045300a6a575262546c3568585844436b472f584b79374b6d2f4439584942334b734f5a31436a5937375571372f674359363170697838697552756a73413464503165380a593445336c68556d374a332b6473766b626f4b64553243626d52494c2f6675627771694c4d587a41673342575278747234547545443533527a373634554650640a307a70304b68775231496230444c67772f344e67566f314146763378784b4d6478774d45683567676b73733038326332706c354a504e32587677426f744e6b4d0a5471526c745a4a35355244436170696e7153334577376675646c4e735851357746766c7432377a7637344b585165616d704c59433037584f6761304c676c536b0a79754774586b6a50542f735542544a705374615769674d5a6f714b7479563463515a58436b4a52684459614c47587673504233687a766d5671636e6b47654e540a65774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a"); ++ get_tpm2b_public_from_pem(key_rsa, key_rsa_len, &public); ++ ++ DEFINE_HEX_PTR(expected_n, "c95f4220f7bf3d7477cc2a1cc691348d645b4e5e615d70c2906fd72b2eca9bf0fd5c80772ac399d428d8efb52aeff80263ad698b1f22b91ba3b00e1d3f57bc638137961526ec9dfe76cbe46e829d53609b99120bfdfb9bc2a88b317cc0837056471b6be13b840f9dd1cfbeb85053ddd33a742a1c11d486f40cb830ff8360568d4016fdf1c4a31dc7030487982092cb34f36736a65e493cdd97bf0068b4d90c4ea465b59279e510c26a98a7a92dc4c3b7ee76536c5d0e7016f96ddbbcefef829741e6a6a4b602d3b5ce81ad0b8254a4cae1ad5e48cf4ffb140532694ad6968a0319a2a2adc95e1c4195c29094610d868b197bec3c1de1cef995a9c9e419e3537b"); ++ assert_se(p->unique.rsa.size == expected_n_len); ++ assert_se(memcmp(p->unique.rsa.buffer, expected_n, expected_n_len) == 0); ++ ++ assert_se(p->parameters.rsaDetail.keyBits == expected_n_len * 8); ++ ++ assert_se(p->parameters.rsaDetail.exponent == 0); ++ ++ check_tpm2b_public_fingerprint(&public, "d9186d13a7fd5b3644cee05448f49ad3574e82a2942ff93cf89598d36cca78a9"); ++} ++#endif ++ ++static void check_name(const TPM2B_NAME *name, const char *expect) { ++ assert_se(name->size == SHA256_DIGEST_SIZE + 2); ++ ++ DEFINE_HEX_PTR(e, expect); ++ assert_se(name->size == e_len); ++ assert_se(memcmp(name->name, e, e_len) == 0); ++} ++ + TEST(calculate_name) { + TPM2B_PUBLIC public; + TPM2B_NAME name; + +- tpm2b_public_init(&public); +- assert_se(tpm2_calculate_name(&public.publicArea, &name) == 0); +- assert_se(name.size == SHA256_DIGEST_SIZE + 2); ++ /* RSA */ ++ tpm2b_public_rsa_init(&public, "9ec7341c52093ac40a1965a5df10432513c539adcf905e30577ab6ebc88ffe53cd08cef12ed9bec6125432f4fada3629b8b96d31b8f507aa35029188fe396da823fcb236027f7fbb01b0da3d87be7f999390449ced604bdf7e26c48657cc0671000f1147da195c3861c96642e54427cb7a11572e07567ec3fd6316978abc4bd92b27bb0a0e4958e599804eeb41d682b3b7fc1f960209f80a4fb8a1b64abfd96bf5d554e73cdd6ad1c8becb4fcf5e8f0c3e621d210e5e2f308f6520ad9a966779231b99f06c5989e5a23a9415c8808ab89ce81117632e2f8461cd4428bded40979236aeadafe8de3f51660a45e1dbc87694e6a36360201cca3ff9e7263e712727"); ++ assert_se(tpm2_calculate_name(&public.publicArea, &name) >= 0); ++ check_name(&name, "000be78f74a470dd92e979ca067cdb2293a35f075e8560b436bd2ccea5da21486a07"); + +- DEFINE_HEX_PTR(e, "000be78f74a470dd92e979ca067cdb2293a35f075e8560b436bd2ccea5da21486a07"); +- assert_se(name.size == e_len); +- assert_se(memcmp(name.name, e, e_len) == 0); ++ /* ECC */ ++ tpm2b_public_ecc_init(&public, TPM2_ECC_NIST_P256, "238e02ee4fd5598add6b502429f1815418515e4b0d6551c8e816b38cb15451d1", "70c2d491769775ec43ccd5a571c429233e9d30cf0f486c2e01acd6cb32ba93b6"); ++ assert_se(tpm2_calculate_name(&public.publicArea, &name) >= 0); ++ check_name(&name, "000b302787187ba19c82011c987bd2dcdbb652b3a543ccc5cb0b49c33d4caae604a6"); + } + + TEST(calculate_policy_auth_value) { +@@ -750,12 +876,21 @@ TEST(calculate_policy_authorize) { + TPM2B_PUBLIC public; + TPM2B_DIGEST d; + +- tpm2b_public_init(&public); ++ /* RSA */ ++ tpm2b_public_rsa_init(&public, "9ec7341c52093ac40a1965a5df10432513c539adcf905e30577ab6ebc88ffe53cd08cef12ed9bec6125432f4fada3629b8b96d31b8f507aa35029188fe396da823fcb236027f7fbb01b0da3d87be7f999390449ced604bdf7e26c48657cc0671000f1147da195c3861c96642e54427cb7a11572e07567ec3fd6316978abc4bd92b27bb0a0e4958e599804eeb41d682b3b7fc1f960209f80a4fb8a1b64abfd96bf5d554e73cdd6ad1c8becb4fcf5e8f0c3e621d210e5e2f308f6520ad9a966779231b99f06c5989e5a23a9415c8808ab89ce81117632e2f8461cd4428bded40979236aeadafe8de3f51660a45e1dbc87694e6a36360201cca3ff9e7263e712727"); + digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); + assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); + assert_se(digest_check(&d, "95213a3784eaab04f427bc7e8851c2f1df0903be8e42428ec25dcefd907baff1")); + assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); + assert_se(digest_check(&d, "95213a3784eaab04f427bc7e8851c2f1df0903be8e42428ec25dcefd907baff1")); ++ ++ /* ECC */ ++ tpm2b_public_ecc_init(&public, TPM2_ECC_NIST_P256, "423a89da6f0998f510489ab9682706e762031ef8f9faef2a185eff67065a187e", "996f73291670cef9e303d6cd9fa19ddf2c9c1fb1e283324ca9acca07c405c8d0"); ++ digest_init(&d, "0000000000000000000000000000000000000000000000000000000000000000"); ++ assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); ++ assert_se(digest_check(&d, "2a5b705e83f949c27ac4d2e79e54fb5fb0a60f0b37bbd54a0ee1022ba00d3628")); ++ assert_se(tpm2_calculate_policy_authorize(&public, NULL, &d) == 0); ++ assert_se(digest_check(&d, "2a5b705e83f949c27ac4d2e79e54fb5fb0a60f0b37bbd54a0ee1022ba00d3628")); + } + + TEST(calculate_policy_pcr) { diff --git a/SOURCES/0577-tpm2-move-policy-calculation-out-of-tpm2_seal.patch b/SOURCES/0577-tpm2-move-policy-calculation-out-of-tpm2_seal.patch new file mode 100644 index 0000000..0297e1a --- /dev/null +++ b/SOURCES/0577-tpm2-move-policy-calculation-out-of-tpm2_seal.patch @@ -0,0 +1,728 @@ +From ddf00c82e41a9c10acb92473c7800089dde96d62 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 12 Jul 2023 17:35:54 -0400 +Subject: [PATCH] tpm2: move policy calculation out of tpm2_seal() + +Move the calculation of the sealed object policy hash out of the tpm2_seal() +function. Instead, callers of tpm2_seal() can directly call +tpm2_calculate_sealing_policy() and then provide the policy hash to +tpm2_seal(). + +(cherry picked from commit 9e4379945b74ee7920fe375be0bcb04d8ef53873) + +Related: RHEL-16182 +--- + src/cryptenroll/cryptenroll-tpm2.c | 88 ++++++++++++++++++------ + src/cryptenroll/cryptenroll-tpm2.h | 5 +- + src/cryptenroll/cryptenroll.c | 28 +++++--- + src/partition/repart.c | 89 +++++++++++++++++------- + src/shared/creds-util.c | 51 ++++++++++++-- + src/shared/tpm2-util.c | 104 +++++++---------------------- + src/shared/tpm2-util.h | 6 +- + 7 files changed, 228 insertions(+), 143 deletions(-) + +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index a2f57ecff4..8a26ec692c 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -133,7 +133,8 @@ int enroll_tpm2(struct crypt_device *cd, + const void *volume_key, + size_t volume_key_size, + const char *device, +- uint32_t hash_pcr_mask, ++ Tpm2PCRValue *hash_pcr_values, ++ size_t n_hash_pcr_values, + const char *pubkey_path, + uint32_t pubkey_pcr_mask, + const char *signature_path, +@@ -143,9 +144,8 @@ int enroll_tpm2(struct crypt_device *cd, + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL, *signature_json = NULL; + _cleanup_(erase_and_freep) char *base64_encoded = NULL; + _cleanup_free_ void *srk_buf = NULL; +- size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0; +- _cleanup_free_ void *blob = NULL, *hash = NULL, *pubkey = NULL; +- uint16_t pcr_bank, primary_alg; ++ size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0; ++ _cleanup_free_ void *blob = NULL, *pubkey = NULL; + const char *node; + _cleanup_(erase_and_freep) char *pin_str = NULL; + ssize_t base64_encoded_size; +@@ -163,7 +163,7 @@ int enroll_tpm2(struct crypt_device *cd, + assert(cd); + assert(volume_key); + assert(volume_key_size > 0); +- assert(TPM2_PCR_MASK_VALID(hash_pcr_mask)); ++ assert(TPM2_PCR_VALUES_VALID(hash_pcr_values, n_hash_pcr_values)); + assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); + + assert_se(node = crypt_get_device_name(cd)); +@@ -209,23 +209,69 @@ int enroll_tpm2(struct crypt_device *cd, + } + } + +- r = tpm2_seal(device, +- hash_pcr_mask, +- pubkey, pubkey_size, +- pubkey_pcr_mask, ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; ++ r = tpm2_context_new(device, &tpm2_context); ++ if (r < 0) ++ return r; ++ ++ bool pcr_value_specified = false; ++ for (size_t i = 0; i < n_hash_pcr_values; i++) ++ if (hash_pcr_values[i].value.size > 0) { ++ pcr_value_specified = true; ++ break; ++ } ++ ++ r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values); ++ if (r < 0) ++ return r; ++ ++ uint16_t hash_pcr_bank = 0; ++ uint32_t hash_pcr_mask = 0; ++ if (n_hash_pcr_values > 0) { ++ size_t hash_count; ++ r = tpm2_pcr_values_hash_count(hash_pcr_values, n_hash_pcr_values, &hash_count); ++ if (r < 0) ++ return log_error_errno(r, "Could not get hash count: %m"); ++ ++ if (hash_count > 1) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected."); ++ ++ hash_pcr_bank = hash_pcr_values[0].hash; ++ r = tpm2_pcr_values_to_mask(hash_pcr_values, n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask); ++ if (r < 0) ++ return log_error_errno(r, "Could not get hash mask: %m"); ++ } ++ ++ TPM2B_PUBLIC public; ++ if (pubkey) { ++ r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public); ++ if (r < 0) ++ return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); ++ } ++ ++ TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); ++ r = tpm2_calculate_sealing_policy( ++ hash_pcr_values, ++ n_hash_pcr_values, ++ pubkey ? &public : NULL, ++ use_pin, ++ &policy); ++ if (r < 0) ++ return r; ++ ++ r = tpm2_seal(tpm2_context, ++ &policy, + pin_str, + &secret, &secret_size, + &blob, &blob_size, +- &hash, &hash_size, +- &pcr_bank, +- &primary_alg, ++ /* ret_primary_alg= */ NULL, + &srk_buf, + &srk_buf_size); + if (r < 0) + return r; + + /* Let's see if we already have this specific PCR policy hash enrolled, if so, exit early. */ +- r = search_policy_hash(cd, hash, hash_size); ++ r = search_policy_hash(cd, policy.buffer, policy.size); + if (r == -ENOENT) + log_debug_errno(r, "PCR policy hash not yet enrolled, enrolling now."); + else if (r < 0) +@@ -235,22 +281,22 @@ int enroll_tpm2(struct crypt_device *cd, + return r; /* return existing keyslot, so that wiping won't kill it */ + } + +- /* Quick verification that everything is in order, we are not in a hurry after all.*/ +- if (!pubkey || signature_json) { ++ /* Quick verification that everything is in order, we are not in a hurry after all. */ ++ if ((!pubkey || signature_json) && !pcr_value_specified) { + _cleanup_(erase_and_freep) void *secret2 = NULL; + size_t secret2_size; + + log_debug("Unsealing for verification..."); + r = tpm2_unseal(device, + hash_pcr_mask, +- pcr_bank, ++ hash_pcr_bank, + pubkey, pubkey_size, + pubkey_pcr_mask, + signature_json, + pin_str, +- primary_alg, ++ /* primary_alg= */ 0, + blob, blob_size, +- hash, hash_size, ++ policy.buffer, policy.size, + srk_buf, srk_buf_size, + &secret2, &secret2_size); + if (r < 0) +@@ -282,12 +328,12 @@ int enroll_tpm2(struct crypt_device *cd, + r = tpm2_make_luks2_json( + keyslot, + hash_pcr_mask, +- pcr_bank, ++ hash_pcr_bank, + pubkey, pubkey_size, + pubkey_pcr_mask, +- primary_alg, ++ /* primary_alg= */ 0, + blob, blob_size, +- hash, hash_size, ++ policy.buffer, policy.size, + use_pin ? binary_salt : NULL, + use_pin ? sizeof(binary_salt) : 0, + srk_buf, srk_buf_size, +diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h +index b6e0c2808e..d43a9a8ffe 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.h ++++ b/src/cryptenroll/cryptenroll-tpm2.h +@@ -5,11 +5,12 @@ + + #include "cryptsetup-util.h" + #include "log.h" ++#include "tpm2-util.h" + + #if HAVE_TPM2 +-int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t hash_pcr_mask, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin); ++int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin); + #else +-static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t hash_pcr_mask, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin) { ++static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin) { + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 key enrollment not supported."); + } +diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c +index c84e10e567..aeef45eea6 100644 +--- a/src/cryptenroll/cryptenroll.c ++++ b/src/cryptenroll/cryptenroll.c +@@ -27,17 +27,19 @@ + #include "strv.h" + #include "terminal-util.h" + #include "tpm-pcr.h" +-#include "tpm2-util.h" + + static EnrollType arg_enroll_type = _ENROLL_TYPE_INVALID; + static char *arg_unlock_keyfile = NULL; + static char *arg_pkcs11_token_uri = NULL; + static char *arg_fido2_device = NULL; + static char *arg_tpm2_device = NULL; +-static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; ++static Tpm2PCRValue *arg_tpm2_hash_pcr_values = NULL; ++static size_t arg_tpm2_n_hash_pcr_values = 0; ++static bool arg_tpm2_hash_pcr_values_use_default = true; + static bool arg_tpm2_pin = false; + static char *arg_tpm2_public_key = NULL; +-static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX; ++static uint32_t arg_tpm2_public_key_pcr_mask = 0; ++static bool arg_tpm2_public_key_pcr_mask_use_default = true; + static char *arg_tpm2_signature = NULL; + static char *arg_node = NULL; + static int *arg_wipe_slots = NULL; +@@ -57,6 +59,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_unlock_keyfile, freep); + STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep); + STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep); + STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); ++STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep); + STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep); + STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep); + STATIC_DESTRUCTOR_REGISTER(arg_node, freep); +@@ -335,7 +338,8 @@ static int parse_argv(int argc, char *argv[]) { + } + + case ARG_TPM2_PCRS: +- r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_pcr_mask); ++ arg_tpm2_hash_pcr_values_use_default = false; ++ r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); + if (r < 0) + return r; + +@@ -356,6 +360,7 @@ static int parse_argv(int argc, char *argv[]) { + break; + + case ARG_TPM2_PUBLIC_KEY_PCRS: ++ arg_tpm2_public_key_pcr_mask_use_default = false; + r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + if (r < 0) + return r; +@@ -443,10 +448,15 @@ static int parse_argv(int argc, char *argv[]) { + if (r < 0) + return r; + +- if (arg_tpm2_pcr_mask == UINT32_MAX) +- arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT; +- if (arg_tpm2_public_key_pcr_mask == UINT32_MAX) +- arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE; ++ if (arg_tpm2_public_key_pcr_mask_use_default && arg_tpm2_public_key) ++ arg_tpm2_public_key_pcr_mask = INDEX_TO_MASK(uint32_t, TPM_PCR_INDEX_KERNEL_IMAGE); ++ ++ if (arg_tpm2_hash_pcr_values_use_default && !GREEDY_REALLOC_APPEND( ++ arg_tpm2_hash_pcr_values, ++ arg_tpm2_n_hash_pcr_values, ++ &TPM2_PCR_VALUE_MAKE(TPM2_PCR_INDEX_DEFAULT, /* hash= */ 0, /* value= */ {}), ++ 1)) ++ return log_oom(); + + return 1; + } +@@ -657,7 +667,7 @@ static int run(int argc, char *argv[]) { + break; + + case ENROLL_TPM2: +- slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_pcr_mask, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin); ++ slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin); + break; + + case _ENROLL_TYPE_INVALID: +diff --git a/src/partition/repart.c b/src/partition/repart.c +index a2c5e214f6..611bd541b0 100644 +--- a/src/partition/repart.c ++++ b/src/partition/repart.c +@@ -121,9 +121,12 @@ static size_t arg_key_size = 0; + static EVP_PKEY *arg_private_key = NULL; + static X509 *arg_certificate = NULL; + static char *arg_tpm2_device = NULL; +-static uint32_t arg_tpm2_pcr_mask = UINT32_MAX; ++static Tpm2PCRValue *arg_tpm2_hash_pcr_values = NULL; ++static size_t arg_tpm2_n_hash_pcr_values = 0; ++static bool arg_tpm2_hash_pcr_values_use_default = true; + static char *arg_tpm2_public_key = NULL; +-static uint32_t arg_tpm2_public_key_pcr_mask = UINT32_MAX; ++static uint32_t arg_tpm2_public_key_pcr_mask = 0; ++static bool arg_tpm2_public_key_pcr_mask_use_default = true; + static bool arg_split = false; + + STATIC_DESTRUCTOR_REGISTER(arg_root, freep); +@@ -133,6 +136,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_key, erase_and_freep); + STATIC_DESTRUCTOR_REGISTER(arg_private_key, EVP_PKEY_freep); + STATIC_DESTRUCTOR_REGISTER(arg_certificate, X509_freep); + STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); ++STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep); + STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep); + + typedef struct Partition Partition; +@@ -3020,10 +3024,9 @@ static int partition_encrypt( + _cleanup_(json_variant_unrefp) JsonVariant *v = NULL; + _cleanup_(erase_and_freep) void *secret = NULL; + _cleanup_free_ void *pubkey = NULL; +- _cleanup_free_ void *blob = NULL, *hash = NULL, *srk_buf = NULL; +- size_t secret_size, blob_size, hash_size, pubkey_size = 0, srk_buf_size = 0; ++ _cleanup_free_ void *blob = NULL, *srk_buf = NULL; ++ size_t secret_size, blob_size, pubkey_size = 0, srk_buf_size = 0; + ssize_t base64_encoded_size; +- uint16_t pcr_bank, primary_alg; + int keyslot; + + if (arg_tpm2_public_key_pcr_mask != 0) { +@@ -3037,18 +3040,51 @@ static int partition_encrypt( + } + } + +- r = tpm2_seal(arg_tpm2_device, +- arg_tpm2_pcr_mask, +- pubkey, pubkey_size, +- arg_tpm2_public_key_pcr_mask, ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; ++ r = tpm2_context_new(arg_tpm2_device, &tpm2_context); ++ if (r < 0) ++ return r; ++ ++ TPM2B_PUBLIC public; ++ if (pubkey) { ++ r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public); ++ if (r < 0) ++ return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); ++ } ++ ++ r = tpm2_pcr_read_missing_values(tpm2_context, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values); ++ if (r < 0) ++ return r; ++ ++ uint16_t hash_pcr_bank = 0; ++ uint32_t hash_pcr_mask = 0; ++ if (arg_tpm2_n_hash_pcr_values > 0) { ++ size_t hash_count; ++ r = tpm2_pcr_values_hash_count(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, &hash_count); ++ if (r < 0) ++ return log_error_errno(r, "Could not get hash count: %m"); ++ ++ if (hash_count > 1) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Multiple PCR banks selected."); ++ ++ hash_pcr_bank = arg_tpm2_hash_pcr_values[0].hash; ++ r = tpm2_pcr_values_to_mask(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, hash_pcr_bank, &hash_pcr_mask); ++ if (r < 0) ++ return log_error_errno(r, "Could not get hash mask: %m"); ++ } ++ ++ TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); ++ r = tpm2_calculate_sealing_policy(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, &public, /* use_pin= */ false, &policy); ++ if (r < 0) ++ return r; ++ ++ r = tpm2_seal(tpm2_context, ++ &policy, + /* pin= */ NULL, + &secret, &secret_size, + &blob, &blob_size, +- &hash, &hash_size, +- &pcr_bank, +- &primary_alg, +- &srk_buf, +- &srk_buf_size); ++ /* ret_primary_alg= */ NULL, ++ &srk_buf, &srk_buf_size); + if (r < 0) + return log_error_errno(r, "Failed to seal to TPM2: %m"); + +@@ -3072,13 +3108,13 @@ static int partition_encrypt( + + r = tpm2_make_luks2_json( + keyslot, +- arg_tpm2_pcr_mask, +- pcr_bank, ++ hash_pcr_mask, ++ hash_pcr_bank, + pubkey, pubkey_size, + arg_tpm2_public_key_pcr_mask, +- primary_alg, ++ /* primary_alg= */ 0, + blob, blob_size, +- hash, hash_size, ++ policy.buffer, policy.size, + NULL, 0, /* no salt because tpm2_seal has no pin */ + srk_buf, srk_buf_size, + 0, +@@ -5276,7 +5312,8 @@ static int parse_argv(int argc, char *argv[]) { + } + + case ARG_TPM2_PCRS: +- r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_pcr_mask); ++ arg_tpm2_hash_pcr_values_use_default = false; ++ r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); + if (r < 0) + return r; + +@@ -5290,6 +5327,7 @@ static int parse_argv(int argc, char *argv[]) { + break; + + case ARG_TPM2_PUBLIC_KEY_PCRS: ++ arg_tpm2_public_key_pcr_mask_use_default = false; + r = tpm2_parse_pcr_argument_to_mask(optarg, &arg_tpm2_public_key_pcr_mask); + if (r < 0) + return r; +@@ -5359,10 +5397,15 @@ static int parse_argv(int argc, char *argv[]) { + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), + "A path to a loopback file must be specified when --split is used."); + +- if (arg_tpm2_pcr_mask == UINT32_MAX) +- arg_tpm2_pcr_mask = TPM2_PCR_MASK_DEFAULT; +- if (arg_tpm2_public_key_pcr_mask == UINT32_MAX) +- arg_tpm2_public_key_pcr_mask = UINT32_C(1) << TPM_PCR_INDEX_KERNEL_IMAGE; ++ if (arg_tpm2_public_key_pcr_mask_use_default && arg_tpm2_public_key) ++ arg_tpm2_public_key_pcr_mask = INDEX_TO_MASK(uint32_t, TPM_PCR_INDEX_KERNEL_IMAGE); ++ ++ if (arg_tpm2_hash_pcr_values_use_default && !GREEDY_REALLOC_APPEND( ++ arg_tpm2_hash_pcr_values, ++ arg_tpm2_n_hash_pcr_values, ++ &TPM2_PCR_VALUE_MAKE(TPM2_PCR_INDEX_DEFAULT, /* hash= */ 0, /* value= */ {}), ++ 1)) ++ return log_oom(); + + if (arg_pretty < 0 && isatty(STDOUT_FILENO)) + arg_pretty = true; +diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c +index 902275215a..ae7919631b 100644 +--- a/src/shared/creds-util.c ++++ b/src/shared/creds-util.c +@@ -648,18 +648,49 @@ int encrypt_credential_and_warn( + if (!pubkey) + tpm2_pubkey_pcr_mask = 0; + +- r = tpm2_seal(tpm2_device, +- tpm2_hash_pcr_mask, +- pubkey, pubkey_size, +- tpm2_pubkey_pcr_mask, ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; ++ r = tpm2_context_new(tpm2_device, &tpm2_context); ++ if (r < 0) ++ return r; ++ ++ r = tpm2_get_best_pcr_bank(tpm2_context, tpm2_hash_pcr_mask | tpm2_pubkey_pcr_mask, &tpm2_pcr_bank); ++ if (r < 0) ++ return r; ++ ++ TPML_PCR_SELECTION tpm2_hash_pcr_selection; ++ tpm2_tpml_pcr_selection_from_mask(tpm2_hash_pcr_mask, tpm2_pcr_bank, &tpm2_hash_pcr_selection); ++ ++ _cleanup_free_ Tpm2PCRValue *tpm2_hash_pcr_values = NULL; ++ size_t tpm2_n_hash_pcr_values; ++ r = tpm2_pcr_read(tpm2_context, &tpm2_hash_pcr_selection, &tpm2_hash_pcr_values, &tpm2_n_hash_pcr_values); ++ if (r < 0) ++ return r; ++ ++ TPM2B_PUBLIC public; ++ if (pubkey) { ++ r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &public); ++ if (r < 0) ++ return log_error_errno(r, "Could not convert public key to TPM2B_PUBLIC: %m"); ++ } ++ ++ TPM2B_DIGEST tpm2_policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); ++ r = tpm2_calculate_sealing_policy( ++ tpm2_hash_pcr_values, ++ tpm2_n_hash_pcr_values, ++ pubkey ? &public : NULL, ++ /* use_pin= */ false, ++ &tpm2_policy); ++ if (r < 0) ++ return r; ++ ++ r = tpm2_seal(tpm2_context, ++ &tpm2_policy, + /* pin= */ NULL, + &tpm2_key, &tpm2_key_size, + &tpm2_blob, &tpm2_blob_size, +- &tpm2_policy_hash, &tpm2_policy_hash_size, +- &tpm2_pcr_bank, + &tpm2_primary_alg, + /* ret_srk_buf= */ NULL, +- /* ret_srk_buf_size= */ 0); ++ /* ret_srk_buf_size= */ NULL); + if (r < 0) { + if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) + log_warning("Firmware reported a TPM2 being present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled."); +@@ -669,6 +700,12 @@ int encrypt_credential_and_warn( + log_notice_errno(r, "TPM2 sealing didn't work, continuing without TPM2: %m"); + } + ++ tpm2_policy_hash_size = tpm2_policy.size; ++ tpm2_policy_hash = malloc(tpm2_policy_hash_size); ++ if (!tpm2_policy_hash) ++ return log_oom(); ++ memcpy(tpm2_policy_hash, tpm2_policy.buffer, tpm2_policy_hash_size); ++ + assert(tpm2_blob_size <= CREDENTIAL_FIELD_SIZE_MAX); + assert(tpm2_policy_hash_size <= CREDENTIAL_FIELD_SIZE_MAX); + } +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index b73457843d..e4565e4034 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -3365,7 +3365,7 @@ static int tpm2_policy_authorize( + } + + /* Extend 'digest' with the calculated policy hash. */ +-static int tpm2_calculate_sealing_policy( ++int tpm2_calculate_sealing_policy( + const Tpm2PCRValue *pcr_values, + size_t n_pcr_values, + const TPM2B_PUBLIC *public, +@@ -3662,38 +3662,25 @@ int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *r + #endif + } + +-int tpm2_seal(const char *device, +- uint32_t hash_pcr_mask, +- const void *pubkey, +- const size_t pubkey_size, +- uint32_t pubkey_pcr_mask, ++int tpm2_seal(Tpm2Context *c, ++ const TPM2B_DIGEST *policy, + const char *pin, + void **ret_secret, + size_t *ret_secret_size, + void **ret_blob, + size_t *ret_blob_size, +- void **ret_pcr_hash, +- size_t *ret_pcr_hash_size, +- uint16_t *ret_pcr_bank, + uint16_t *ret_primary_alg, + void **ret_srk_buf, + size_t *ret_srk_buf_size) { + ++ uint16_t primary_alg = 0; + TSS2_RC rc; + int r; + +- assert(pubkey || pubkey_size == 0); +- + assert(ret_secret); + assert(ret_secret_size); + assert(ret_blob); + assert(ret_blob_size); +- assert(ret_pcr_hash); +- assert(ret_pcr_hash_size); +- assert(ret_pcr_bank); +- +- assert(TPM2_PCR_MASK_VALID(hash_pcr_mask)); +- assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); + + /* So here's what we do here: we connect to the TPM2 chip. It persistently contains a "seed" key that + * is randomized when the TPM2 is first initialized or reset and remains stable across boots. We +@@ -3713,53 +3700,6 @@ int tpm2_seal(const char *device, + + usec_t start = now(CLOCK_MONOTONIC); + +- _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; +- r = tpm2_context_new(device, &c); +- if (r < 0) +- return r; +- +- TPMI_ALG_HASH pcr_bank = 0; +- if (hash_pcr_mask | pubkey_pcr_mask) { +- /* Some TPM2 devices only can do SHA1. Prefer SHA256 but allow SHA1. */ +- r = tpm2_get_best_pcr_bank(c, hash_pcr_mask|pubkey_pcr_mask, &pcr_bank); +- if (r < 0) +- return r; +- } +- +- _cleanup_free_ Tpm2PCRValue *hash_pcr_values = NULL; +- size_t n_hash_pcr_values; +- if (hash_pcr_mask) { +- /* For now, we just read the current values from the system; we need to be able to specify +- * expected values, eventually. */ +- TPML_PCR_SELECTION hash_pcr_selection; +- tpm2_tpml_pcr_selection_from_mask(hash_pcr_mask, pcr_bank, &hash_pcr_selection); +- +- r = tpm2_pcr_read(c, &hash_pcr_selection, &hash_pcr_values, &n_hash_pcr_values); +- if (r < 0) +- return r; +- } +- +- TPM2B_PUBLIC pubkey_tpm2b; +- if (pubkey) { +- r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &pubkey_tpm2b); +- if (r < 0) +- return log_error_errno(r, "Could not create TPMT_PUBLIC: %m"); +- } +- +- TPM2B_DIGEST policy_digest; +- r = tpm2_digest_init(TPM2_ALG_SHA256, &policy_digest); +- if (r < 0) +- return r; +- +- r = tpm2_calculate_sealing_policy( +- hash_pcr_values, +- n_hash_pcr_values, +- pubkey ? &pubkey_tpm2b : NULL, +- !!pin, +- &policy_digest); +- if (r < 0) +- return r; +- + /* We use a keyed hash object (i.e. HMAC) to store the secret key we want to use for unlocking the + * LUKS2 volume with. We don't ever use for HMAC/keyed hash operations however, we just use it + * because it's a key type that is universally supported and suitable for symmetric binary blobs. */ +@@ -3769,7 +3709,7 @@ int tpm2_seal(const char *device, + .objectAttributes = TPMA_OBJECT_FIXEDTPM | TPMA_OBJECT_FIXEDPARENT, + .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL, + .unique.keyedHash.size = SHA256_DIGEST_SIZE, +- .authPolicy = policy_digest, ++ .authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE), + }; + + TPMS_SENSITIVE_CREATE hmac_sensitive = { +@@ -3794,21 +3734,33 @@ int tpm2_seal(const char *device, + if (r < 0) + return log_error_errno(r, "Failed to generate secret key: %m"); + +- _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; + _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; + if (ret_srk_buf) { +- r = tpm2_get_or_create_srk(c, NULL, &primary_public, NULL, NULL, &primary_handle); ++ _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; ++ r = tpm2_get_or_create_srk( ++ c, ++ /* session= */ NULL, ++ &primary_public, ++ /* ret_name= */ NULL, ++ /* ret_qname= */ NULL, ++ &primary_handle); + if (r < 0) + return r; ++ ++ primary_alg = primary_public->publicArea.type; + } else { + /* TODO: force all callers to provide ret_srk_buf, so we can stop sealing with the legacy templates. */ ++ primary_alg = TPM2_ALG_ECC; ++ + TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), }; +- r = tpm2_get_legacy_template(TPM2_ALG_ECC, &template.publicArea); ++ r = tpm2_get_legacy_template(primary_alg, &template.publicArea); + if (r < 0) + return log_error_errno(r, "Could not get legacy ECC template: %m"); + + if (!tpm2_supports_tpmt_public(c, &template.publicArea)) { +- r = tpm2_get_legacy_template(TPM2_ALG_RSA, &template.publicArea); ++ primary_alg = TPM2_ALG_RSA; ++ ++ r = tpm2_get_legacy_template(primary_alg, &template.publicArea); + if (r < 0) + return log_error_errno(r, "Could not get legacy RSA template: %m"); + +@@ -3822,7 +3774,7 @@ int tpm2_seal(const char *device, + /* session= */ NULL, + &template, + /* sensitive= */ NULL, +- &primary_public, ++ /* ret_public= */ NULL, + &primary_handle); + if (r < 0) + return r; +@@ -3863,11 +3815,6 @@ int tpm2_seal(const char *device, + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal public key: %s", sym_Tss2_RC_Decode(rc)); + +- _cleanup_free_ void *hash = NULL; +- hash = memdup(policy_digest.buffer, policy_digest.size); +- if (!hash) +- return log_oom(); +- + /* serialize the key for storage in the LUKS header. A deserialized ESYS_TR provides both + * the raw TPM handle as well as the object name. The object name is used to verify that + * the key we use later is the key we expect to establish the session with. +@@ -3903,10 +3850,9 @@ int tpm2_seal(const char *device, + *ret_secret_size = hmac_sensitive.data.size; + *ret_blob = TAKE_PTR(blob); + *ret_blob_size = blob_size; +- *ret_pcr_hash = TAKE_PTR(hash); +- *ret_pcr_hash_size = policy_digest.size; +- *ret_pcr_bank = pcr_bank; +- *ret_primary_alg = primary_public->publicArea.type; ++ ++ if (ret_primary_alg) ++ *ret_primary_alg = primary_alg; + + return 0; + } +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 0938fe35eb..2e03eb85ab 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -154,8 +154,9 @@ int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); + int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest); + int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGEST *policy_ref, TPM2B_DIGEST *digest); + int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); ++int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, TPM2B_DIGEST *digest); + +-int tpm2_seal(const char *device, uint32_t hash_pcr_mask, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_pcr_hash, size_t *ret_pcr_hash_size, uint16_t *ret_pcr_bank, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); ++int tpm2_seal(Tpm2Context *c, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); + int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); + + #if HAVE_OPENSSL +@@ -240,7 +241,8 @@ int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, + int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, void **ret_srk_buf, size_t *ret_srk_buf_size, TPM2Flags *ret_flags); + + /* Default to PCR 7 only */ +-#define TPM2_PCR_MASK_DEFAULT (UINT32_C(1) << 7) ++#define TPM2_PCR_INDEX_DEFAULT (7) ++#define TPM2_PCR_MASK_DEFAULT INDEX_TO_MASK(uint32_t, TPM2_PCR_INDEX_DEFAULT) + + /* We want the helpers below to work also if TPM2 libs are not available, hence define these four defines if + * they are missing. */ diff --git a/SOURCES/0578-man-update-systemd-cryptenroll-man-page-with-details.patch b/SOURCES/0578-man-update-systemd-cryptenroll-man-page-with-details.patch new file mode 100644 index 0000000..b1c27bf --- /dev/null +++ b/SOURCES/0578-man-update-systemd-cryptenroll-man-page-with-details.patch @@ -0,0 +1,48 @@ +From 9a7d568845164a5d9621a2028c4a8333928e2e2a Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 14 Jul 2023 18:36:20 -0400 +Subject: [PATCH] man: update systemd-cryptenroll man page with details on + --tpm2-pcrs format change + +The previous commit extended the accepted format of --tpm2-pcrs to allow +specifying the hash algorithm (i.e. PCR bank) and hash digest value, this +updates the man page with those changes. + +(cherry picked from commit 1782b0b88ce531a674102445cdf95f86e6b54cae) + +Related: RHEL-16182 +--- + man/systemd-cryptenroll.xml | 17 +++++++++++++---- + 1 file changed, 13 insertions(+), 4 deletions(-) + +diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml +index 5ddaab40bc..3f36f320b6 100644 +--- a/man/systemd-cryptenroll.xml ++++ b/man/systemd-cryptenroll.xml +@@ -379,13 +379,22 @@ + PCR + + Configures the TPM2 PCRs (Platform Configuration Registers) to bind to when +- enrollment is requested via . Takes a list of PCR names or numeric +- indices in the range 0…23. Multiple PCR indexes are separated by +. If not +- specified, the default is to use PCR 7 only. If an empty string is specified, binds the enrollment to +- no PCRs at all. See the table above for a list of available PCRs. ++ enrollment is requested via . Takes a list of PCR entries, where each ++ entry starts with a name or numeric index in the range 0…23, optionally followed by ++ : and a hash algorithm name (specifying the PCR bank), optionally followed by ++ = and a hash digest value. Multiple PCR entries are separated by ++ +. If not specified, the default is to use PCR 7 only. If an empty string is ++ specified, binds the enrollment to no PCRs at all. See the table above for a list of available ++ PCRs. + + Example: + specifies that PCR registers 4, 1, and 5 should be used. ++ Example: specifies that PCR register 7 from the SHA256 ++ bank should be used. ++ Example: ++ specifies that PCR register 4 from the SHA1 bank should be used, and a hash digest value of ++ 0x3A3F780F11A4B49969FCAA80CD6E3957C33B2275 will be used instead of reading the current PCR ++ value. + + + diff --git a/SOURCES/0579-tpm2-update-TEST-70-TPM2-to-test-passing-PCR-value-t.patch b/SOURCES/0579-tpm2-update-TEST-70-TPM2-to-test-passing-PCR-value-t.patch new file mode 100644 index 0000000..fc639f5 --- /dev/null +++ b/SOURCES/0579-tpm2-update-TEST-70-TPM2-to-test-passing-PCR-value-t.patch @@ -0,0 +1,64 @@ +From 256cdef442be3b5d1faffaccd7ed7684b68109e0 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 13 Jul 2023 16:02:37 -0400 +Subject: [PATCH] tpm2: update TEST-70-TPM2 to test passing PCR value to + systemd-cryptenroll + +Add tests to use expected, not current, PCR values during sealing. + +(cherry picked from commit e85ddd96446abec938ef75d4e3c256b170920575) + +Related: RHEL-16182 +--- + test/units/testsuite-70.sh | 38 +++++++++++++++++++++++++++++++++++++- + 1 file changed, 37 insertions(+), 1 deletion(-) + +diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh +index 5d4b155286..91f31cd169 100755 +--- a/test/units/testsuite-70.sh ++++ b/test/units/testsuite-70.sh +@@ -56,7 +56,43 @@ env PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs=0+7 $ + + # Check with wrong PCR 0 + tpm2_pcrextend 0:sha256=0000000000000000000000000000000000000000000000000000000000000000 +-/usr/lib/systemd/systemd-cryptsetup attach test-volume $img - tpm2-device=auto,headless=1 && exit 1 ++(! /usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1) ++ ++if tpm_has_pcr sha256 12; then ++ # Enroll using an explict PCR value (that does match current PCR value) ++ systemd-cryptenroll --wipe-slot=tpm2 "$img" ++ EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) ++ PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$img" ++ /usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 ++ /usr/lib/systemd/systemd-cryptsetup detach test-volume ++ ++ # Same as above plus more PCRs without the value or alg specified ++ systemd-cryptenroll --wipe-slot=tpm2 "$img" ++ EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) ++ PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="1,12:sha256=$EXPECTED_PCR_VALUE,3" "$img" ++ /usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 ++ /usr/lib/systemd/systemd-cryptsetup detach test-volume ++ ++ # Same as above plus more PCRs with hash alg specified but hash value not specified ++ systemd-cryptenroll --wipe-slot=tpm2 "$img" ++ EXPECTED_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) ++ PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="1:sha256,12:sha256=$EXPECTED_PCR_VALUE,3" "$img" ++ /usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 ++ /usr/lib/systemd/systemd-cryptsetup detach test-volume ++ ++ # Now the interesting part, enrolling using a hash value that doesn't match the current PCR value ++ systemd-cryptenroll --wipe-slot=tpm2 "$img" ++ tpm2_pcrread -Q -o /tmp/pcr.dat sha256:12 ++ CURRENT_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) ++ EXPECTED_PCR_VALUE=$(cat /tmp/pcr.dat /tmp/pcr.dat | openssl dgst -sha256 -r | cut -d ' ' -f 1) ++ PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-pcrs="12:sha256=$EXPECTED_PCR_VALUE" "$img" ++ (! /usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1) ++ tpm2_pcrextend "12:sha256=$CURRENT_PCR_VALUE" ++ /usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 ++ /usr/lib/systemd/systemd-cryptsetup detach test-volume ++ ++ rm -f /tmp/pcr.dat ++fi + + rm $img + diff --git a/SOURCES/0580-tpm2-change-alg_to_-functions-to-use-switch.patch b/SOURCES/0580-tpm2-change-alg_to_-functions-to-use-switch.patch new file mode 100644 index 0000000..67ae33f --- /dev/null +++ b/SOURCES/0580-tpm2-change-alg_to_-functions-to-use-switch.patch @@ -0,0 +1,82 @@ +From d5da1c6d6ae5a98806b42404143c82019f97314e Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 10:34:57 -0400 +Subject: [PATCH] tpm2: change *alg_to_* functions to use switch() + +(cherry picked from commit 7354a7ccd4aa27bc8c94280c82d0ae54fd947c2a) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 39 ++++++++++++++++++++++++--------------- + 1 file changed, 24 insertions(+), 15 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index e4565e4034..491e8885d3 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -4588,28 +4588,34 @@ int tpm2_parse_luks2_json( + } + + int tpm2_hash_alg_to_size(uint16_t alg) { +- if (alg == TPM2_ALG_SHA1) ++ switch (alg) { ++ case TPM2_ALG_SHA1: + return 20; +- if (alg == TPM2_ALG_SHA256) ++ case TPM2_ALG_SHA256: + return 32; +- if (alg == TPM2_ALG_SHA384) ++ case TPM2_ALG_SHA384: + return 48; +- if (alg == TPM2_ALG_SHA512) ++ case TPM2_ALG_SHA512: + return 64; +- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown hash algorithm id 0x%" PRIx16, alg); ++ default: ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown hash algorithm id 0x%" PRIx16, alg); ++ } + } + + const char *tpm2_hash_alg_to_string(uint16_t alg) { +- if (alg == TPM2_ALG_SHA1) ++ switch (alg) { ++ case TPM2_ALG_SHA1: + return "sha1"; +- if (alg == TPM2_ALG_SHA256) ++ case TPM2_ALG_SHA256: + return "sha256"; +- if (alg == TPM2_ALG_SHA384) ++ case TPM2_ALG_SHA384: + return "sha384"; +- if (alg == TPM2_ALG_SHA512) ++ case TPM2_ALG_SHA512: + return "sha512"; +- log_debug("Unknown hash algorithm id 0x%" PRIx16, alg); +- return NULL; ++ default: ++ log_debug("Unknown hash algorithm id 0x%" PRIx16, alg); ++ return NULL; ++ } + } + + int tpm2_hash_alg_from_string(const char *alg) { +@@ -4625,12 +4631,15 @@ int tpm2_hash_alg_from_string(const char *alg) { + } + + const char *tpm2_asym_alg_to_string(uint16_t alg) { +- if (alg == TPM2_ALG_ECC) ++ switch (alg) { ++ case TPM2_ALG_ECC: + return "ecc"; +- if (alg == TPM2_ALG_RSA) ++ case TPM2_ALG_RSA: + return "rsa"; +- log_debug("Unknown asymmetric algorithm id 0x%" PRIx16, alg); +- return NULL; ++ default: ++ log_debug("Unknown asymmetric algorithm id 0x%" PRIx16, alg); ++ return NULL; ++ } + } + + int tpm2_asym_alg_from_string(const char *alg) { diff --git a/SOURCES/0581-tpm2-lowercase-TPM2_PCR_VALUE-S-_VALID-functions.patch b/SOURCES/0581-tpm2-lowercase-TPM2_PCR_VALUE-S-_VALID-functions.patch new file mode 100644 index 0000000..1929258 --- /dev/null +++ b/SOURCES/0581-tpm2-lowercase-TPM2_PCR_VALUE-S-_VALID-functions.patch @@ -0,0 +1,121 @@ +From 8408676704f6e0c79445fdb1224e60ce31617c43 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 10:38:48 -0400 +Subject: [PATCH] tpm2: lowercase TPM2_PCR_VALUE[S]_VALID functions + +As these are not macros, they should be lowercase. + +(cherry picked from commit cc1a78d5c48a0100169a847da0e252f2b01b05e6) + +Related: RHEL-16182 +--- + src/cryptenroll/cryptenroll-tpm2.c | 2 +- + src/shared/tpm2-util.c | 16 ++++++++-------- + src/shared/tpm2-util.h | 4 ++-- + 3 files changed, 11 insertions(+), 11 deletions(-) + +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index 8a26ec692c..7c1946c3b1 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -163,7 +163,7 @@ int enroll_tpm2(struct crypt_device *cd, + assert(cd); + assert(volume_key); + assert(volume_key_size > 0); +- assert(TPM2_PCR_VALUES_VALID(hash_pcr_values, n_hash_pcr_values)); ++ assert(tpm2_pcr_values_valid(hash_pcr_values, n_hash_pcr_values)); + assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); + + assert_se(node = crypt_get_device_name(cd)); +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 491e8885d3..2dbd4eae8b 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1516,7 +1516,7 @@ size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l) { + return weight; + } + +-bool TPM2_PCR_VALUE_VALID(const Tpm2PCRValue *pcr_value) { ++bool tpm2_pcr_value_valid(const Tpm2PCRValue *pcr_value) { + int r; + + assert(pcr_value); +@@ -1547,13 +1547,13 @@ bool TPM2_PCR_VALUE_VALID(const Tpm2PCRValue *pcr_value) { + * 1) all entries must be sorted in ascending order (e.g. using tpm2_sort_pcr_values()) + * 2) all entries must be unique, i.e. there cannot be 2 entries with the same hash and index + */ +-bool TPM2_PCR_VALUES_VALID(const Tpm2PCRValue *pcr_values, size_t n_pcr_values) { ++bool tpm2_pcr_values_valid(const Tpm2PCRValue *pcr_values, size_t n_pcr_values) { + assert(pcr_values || n_pcr_values == 0); + + for (size_t i = 0; i < n_pcr_values; i++) { + const Tpm2PCRValue *v = &pcr_values[i]; + +- if (!TPM2_PCR_VALUE_VALID(v)) ++ if (!tpm2_pcr_value_valid(v)) + return false; + + if (i == 0) +@@ -1628,7 +1628,7 @@ int tpm2_pcr_values_to_mask(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, + assert(pcr_values || n_pcr_values == 0); + assert(ret_mask); + +- if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) ++ if (!tpm2_pcr_values_valid(pcr_values, n_pcr_values)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PCR values."); + + for (size_t i = 0; i < n_pcr_values; i++) +@@ -1653,7 +1653,7 @@ int tpm2_tpml_pcr_selection_from_pcr_values( + + assert(pcr_values || n_pcr_values == 0); + +- if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) ++ if (!tpm2_pcr_values_valid(pcr_values, n_pcr_values)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "PCR values are not valid."); + + for (size_t i = 0; i < n_pcr_values; i++) { +@@ -2310,7 +2310,7 @@ int tpm2_pcr_read( + + tpm2_sort_pcr_values(pcr_values, n_pcr_values); + +- if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) ++ if (!tpm2_pcr_values_valid(pcr_values, n_pcr_values)) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PCR values read from TPM are not valid."); + + *ret_pcr_values = TAKE_PTR(pcr_values); +@@ -4723,7 +4723,7 @@ int tpm2_parse_pcr_argument(const char *arg, Tpm2PCRValue **ret_pcr_values, size + + tpm2_sort_pcr_values(pcr_values, n_pcr_values); + +- if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) ++ if (!tpm2_pcr_values_valid(pcr_values, n_pcr_values)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parsed PCR values are not valid."); + + *ret_pcr_values = TAKE_PTR(pcr_values); +@@ -4765,7 +4765,7 @@ int tpm2_parse_pcr_argument_append(const char *arg, Tpm2PCRValue **ret_pcr_value + + tpm2_sort_pcr_values(pcr_values, n_pcr_values); + +- if (!TPM2_PCR_VALUES_VALID(pcr_values, n_pcr_values)) ++ if (!tpm2_pcr_values_valid(pcr_values, n_pcr_values)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parsed PCR values are not valid."); + + SWAP_TWO(*ret_pcr_values, pcr_values); +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 2e03eb85ab..ff90f034fe 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -76,11 +76,11 @@ typedef struct { + } Tpm2PCRValue; + + #define TPM2_PCR_VALUE_MAKE(i, h, v) (Tpm2PCRValue) { .index = (i), .hash = (h), .value = ((TPM2B_DIGEST) v), } +-bool TPM2_PCR_VALUE_VALID(const Tpm2PCRValue *pcr_value); ++bool tpm2_pcr_value_valid(const Tpm2PCRValue *pcr_value); + int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value); + char *tpm2_pcr_value_to_string(const Tpm2PCRValue *pcr_value); + +-bool TPM2_PCR_VALUES_VALID(const Tpm2PCRValue *pcr_values, size_t n_pcr_values); ++bool tpm2_pcr_values_valid(const Tpm2PCRValue *pcr_values, size_t n_pcr_values); + void tpm2_sort_pcr_values(Tpm2PCRValue *pcr_values, size_t n_pcr_values); + int tpm2_pcr_values_from_mask(uint32_t mask, TPMI_ALG_HASH hash, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); + int tpm2_pcr_values_to_mask(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPMI_ALG_HASH hash, uint32_t *ret_mask); diff --git a/SOURCES/0582-tpm2-move-cast-from-lhs-to-rhs-in-uint16_t-int-compa.patch b/SOURCES/0582-tpm2-move-cast-from-lhs-to-rhs-in-uint16_t-int-compa.patch new file mode 100644 index 0000000..1088b00 --- /dev/null +++ b/SOURCES/0582-tpm2-move-cast-from-lhs-to-rhs-in-uint16_t-int-compa.patch @@ -0,0 +1,25 @@ +From 02dd9a1dfcb52b0e078945b15a87469f20762610 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 10:42:35 -0400 +Subject: [PATCH] tpm2: move cast from lhs to rhs in uint16_t/int comparison + +(cherry picked from commit 3cd4145f347461018fe79263534f59df6d3cde7a) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 2dbd4eae8b..2c5d1741e7 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1532,7 +1532,7 @@ bool tpm2_pcr_value_valid(const Tpm2PCRValue *pcr_value) { + if (r < 0) + return false; + +- if ((int) pcr_value->value.size != r) { ++ if (pcr_value->value.size != (size_t) r) { + log_debug("PCR hash 0x%" PRIx16 " expected size %d does not match actual size %" PRIu16 ".", + pcr_value->hash, r, pcr_value->value.size); + return false; diff --git a/SOURCES/0583-tpm2-in-validator-functions-return-false-instead-of-.patch b/SOURCES/0583-tpm2-in-validator-functions-return-false-instead-of-.patch new file mode 100644 index 0000000..dcbca13 --- /dev/null +++ b/SOURCES/0583-tpm2-in-validator-functions-return-false-instead-of-.patch @@ -0,0 +1,41 @@ +From e498b6a8d2f75e56e725faa1b57b9c5552efc812 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 10:48:20 -0400 +Subject: [PATCH] tpm2: in validator functions, return false instead of assert + failure + +(cherry picked from commit 064ac95d81b9ab6a6eb8849cacce928015d44e5b) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 8 ++++++-- + 1 file changed, 6 insertions(+), 2 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 2c5d1741e7..4288d1b897 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1519,7 +1519,8 @@ size_t tpm2_tpml_pcr_selection_weight(const TPML_PCR_SELECTION *l) { + bool tpm2_pcr_value_valid(const Tpm2PCRValue *pcr_value) { + int r; + +- assert(pcr_value); ++ if (!pcr_value) ++ return false; + + if (!TPM2_PCR_INDEX_VALID(pcr_value->index)) { + log_debug("PCR index %u invalid.", pcr_value->index); +@@ -1546,9 +1547,12 @@ bool tpm2_pcr_value_valid(const Tpm2PCRValue *pcr_value) { + * + * 1) all entries must be sorted in ascending order (e.g. using tpm2_sort_pcr_values()) + * 2) all entries must be unique, i.e. there cannot be 2 entries with the same hash and index ++ * ++ * Returns true if all entries are valid (or if no entries are provided), false otherwise. + */ + bool tpm2_pcr_values_valid(const Tpm2PCRValue *pcr_values, size_t n_pcr_values) { +- assert(pcr_values || n_pcr_values == 0); ++ if (!pcr_values && n_pcr_values > 0) ++ return false; + + for (size_t i = 0; i < n_pcr_values; i++) { + const Tpm2PCRValue *v = &pcr_values[i]; diff --git a/SOURCES/0584-tpm2-in-tpm2_pcr_values_valid-use-FOREACH_ARRAY.patch b/SOURCES/0584-tpm2-in-tpm2_pcr_values_valid-use-FOREACH_ARRAY.patch new file mode 100644 index 0000000..911410e --- /dev/null +++ b/SOURCES/0584-tpm2-in-tpm2_pcr_values_valid-use-FOREACH_ARRAY.patch @@ -0,0 +1,266 @@ +From e3d00443ab82b7faafc57dd9bacd59479d565ac3 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 11:02:55 -0400 +Subject: [PATCH] tpm2: in tpm2_pcr_values_valid() use FOREACH_ARRAY() + +(cherry picked from commit 193fd5730e980db57cb092bd90cbc1966d9b404e) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 117 ++++++++++++++++++----------------------- + 1 file changed, 52 insertions(+), 65 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 4288d1b897..a91e805e06 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1278,22 +1278,15 @@ void tpm2_tpms_pcr_selection_move(TPMS_PCR_SELECTION *a, TPMS_PCR_SELECTION *b) + tpm2_tpms_pcr_selection_from_mask(0, b->hash, b); + } + ++#define FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml) \ ++ _FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml, UNIQ_T(l, UNIQ)) ++#define _FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml, l) \ ++ for (typeof(tpml) (l) = (tpml); (l); (l) = NULL) \ ++ FOREACH_ARRAY(tpms, (l)->pcrSelections, (l)->count) ++ + #define FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms) \ +- _FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms, UNIQ) +-#define _FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms, uniq) \ + FOREACH_PCR_IN_MASK(pcr, tpm2_tpms_pcr_selection_to_mask(tpms)) + +-#define FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml) \ +- UNIQ_FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml, UNIQ) +-#define UNIQ_FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml, uniq) \ +- for (TPML_PCR_SELECTION *UNIQ_T(_tpml, uniq) = (TPML_PCR_SELECTION*)(tpml); \ +- UNIQ_T(_tpml, uniq); UNIQ_T(_tpml, uniq) = NULL) \ +- _FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, UNIQ_T(_tpml, uniq)) +-#define _FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml) \ +- for (TPMS_PCR_SELECTION *tpms = tpml->pcrSelections; \ +- (uint32_t)(tpms - tpml->pcrSelections) < tpml->count; \ +- tpms++) +- + #define FOREACH_PCR_IN_TPML_PCR_SELECTION(pcr, tpms, tpml) \ + FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(tpms, tpml) \ + FOREACH_PCR_IN_TPMS_PCR_SELECTION(pcr, tpms) +@@ -1364,6 +1357,14 @@ static TPMS_PCR_SELECTION *tpm2_tpml_pcr_selection_get_tpms_pcr_selection( + return selection; + } + ++/* Combine all duplicate (same hash alg) TPMS_PCR_SELECTION entries in 'l'. */ ++static void tpm2_tpml_pcr_selection_cleanup(TPML_PCR_SELECTION *l) { ++ /* Can't use FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION() because we might modify l->count */ ++ for (uint32_t i = 0; i < l->count; i++) ++ /* This removes all duplicate TPMS_PCR_SELECTION entries for this hash. */ ++ (void) tpm2_tpml_pcr_selection_get_tpms_pcr_selection(l, l->pcrSelections[i].hash); ++} ++ + /* Convert a TPML_PCR_SELECTION object to a mask. Returns empty mask (i.e. 0) if 'hash_alg' is not in the object. */ + uint32_t tpm2_tpml_pcr_selection_to_mask(const TPML_PCR_SELECTION *l, TPMI_ALG_HASH hash_alg) { + assert(l); +@@ -1393,13 +1394,6 @@ void tpm2_tpml_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash_alg, TP + }; + } + +-/* Combine all duplicate (same hash alg) TPMS_PCR_SELECTION entries in 'l'. */ +-static void tpm2_tpml_pcr_selection_cleanup(TPML_PCR_SELECTION *l) { +- FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(s, l) +- /* This removes all duplicates for s->hash. */ +- (void) tpm2_tpml_pcr_selection_get_tpms_pcr_selection(l, s->hash); +-} +- + /* Add the PCR selections in 's' to the corresponding hash alg TPMS_PCR_SELECTION entry in 'l'. Adds a new + * TPMS_PCR_SELECTION entry for the hash alg if needed. This may modify the TPML_PCR_SELECTION by combining + * entries with the same hash alg. */ +@@ -1473,7 +1467,7 @@ void tpm2_tpml_pcr_selection_add(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION + assert(a); + assert(b); + +- FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection_b, (TPML_PCR_SELECTION*) b) ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection_b, b) + tpm2_tpml_pcr_selection_add_tpms_pcr_selection(a, selection_b); + } + +@@ -1482,7 +1476,7 @@ void tpm2_tpml_pcr_selection_sub(TPML_PCR_SELECTION *a, const TPML_PCR_SELECTION + assert(a); + assert(b); + +- FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection_b, (TPML_PCR_SELECTION*) b) ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(selection_b, b) + tpm2_tpml_pcr_selection_sub_tpms_pcr_selection(a, selection_b); + } + +@@ -1490,7 +1484,7 @@ char *tpm2_tpml_pcr_selection_to_string(const TPML_PCR_SELECTION *l) { + assert(l); + + _cleanup_free_ char *banks = NULL; +- FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(s, (TPML_PCR_SELECTION*) l) { ++ FOREACH_TPMS_PCR_SELECTION_IN_TPML_PCR_SELECTION(s, l) { + if (tpm2_tpms_pcr_selection_is_empty(s)) + continue; + +@@ -1554,36 +1548,35 @@ bool tpm2_pcr_values_valid(const Tpm2PCRValue *pcr_values, size_t n_pcr_values) + if (!pcr_values && n_pcr_values > 0) + return false; + +- for (size_t i = 0; i < n_pcr_values; i++) { +- const Tpm2PCRValue *v = &pcr_values[i]; +- +- if (!tpm2_pcr_value_valid(v)) ++ const Tpm2PCRValue *previous = NULL; ++ FOREACH_ARRAY(current, pcr_values, n_pcr_values) { ++ if (!tpm2_pcr_value_valid(current)) + return false; + +- if (i == 0) ++ if (!previous) { ++ previous = current; + continue; +- +- const Tpm2PCRValue *l = &pcr_values[i - 1]; ++ } + + /* Hashes must be sorted in ascending order */ +- if (v->hash < l->hash) { ++ if (current->hash < previous->hash) { + log_debug("PCR values not in ascending order, hash %" PRIu16 " is after %" PRIu16 ".", +- v->hash, l->hash); ++ current->hash, previous->hash); + return false; + } + +- if (v->hash == l->hash) { ++ if (current->hash == previous->hash) { + /* Indexes (for the same hash) must be sorted in ascending order */ +- if (v->index < l->index) { ++ if (current->index < previous->index) { + log_debug("PCR values not in ascending order, hash %" PRIu16 " index %u is after %u.", +- v->hash, v->index, l->index); ++ current->hash, current->index, previous->index); + return false; + } + + /* Indexes (for the same hash) must not be duplicates */ +- if (v->index == l->index) { ++ if (current->index == previous->index) { + log_debug("PCR values contain duplicates for hash %" PRIu16 " index %u.", +- v->hash, v->index); ++ current->hash, previous->index); + return false; + } + } +@@ -1635,9 +1628,9 @@ int tpm2_pcr_values_to_mask(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, + if (!tpm2_pcr_values_valid(pcr_values, n_pcr_values)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid PCR values."); + +- for (size_t i = 0; i < n_pcr_values; i++) +- if (pcr_values[i].hash == hash) +- SET_BIT(mask, pcr_values[i].index); ++ FOREACH_ARRAY(v, pcr_values, n_pcr_values) ++ if (v->hash == hash) ++ SET_BIT(mask, v->index); + + *ret_mask = mask; + +@@ -1660,14 +1653,10 @@ int tpm2_tpml_pcr_selection_from_pcr_values( + if (!tpm2_pcr_values_valid(pcr_values, n_pcr_values)) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "PCR values are not valid."); + +- for (size_t i = 0; i < n_pcr_values; i++) { +- unsigned index = pcr_values[i].index; +- TPMI_ALG_HASH hash = pcr_values[i].hash; +- const TPM2B_DIGEST *digest = &pcr_values[i].value; +- +- tpm2_tpml_pcr_selection_add_mask(&selection, hash, INDEX_TO_MASK(uint32_t, index)); ++ FOREACH_ARRAY(v, pcr_values, n_pcr_values) { ++ tpm2_tpml_pcr_selection_add_mask(&selection, v->hash, INDEX_TO_MASK(uint32_t, v->index)); + +- if (!GREEDY_REALLOC_APPEND(values, n_values, digest, 1)) ++ if (!GREEDY_REALLOC_APPEND(values, n_values, &v->value, 1)) + return log_oom_debug(); + } + +@@ -1825,8 +1814,8 @@ int tpm2_pcr_values_from_string(const char *arg, Tpm2PCRValue **ret_pcr_values, + char *tpm2_pcr_values_to_string(const Tpm2PCRValue *pcr_values, size_t n_pcr_values) { + _cleanup_free_ char *s = NULL; + +- for (size_t i = 0; i < n_pcr_values; i++) { +- _cleanup_free_ char *pcrstr = tpm2_pcr_value_to_string(&pcr_values[i]); ++ FOREACH_ARRAY(v, pcr_values, n_pcr_values) { ++ _cleanup_free_ char *pcrstr = tpm2_pcr_value_to_string(v); + if (!pcrstr || !strextend_with_separator(&s, "+", pcrstr)) + return NULL; + } +@@ -2352,9 +2341,7 @@ int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_ + } + } + +- for (size_t i = 0; i < n_pcr_values; i++) { +- Tpm2PCRValue *v = &pcr_values[i]; +- ++ FOREACH_ARRAY(v, pcr_values, n_pcr_values) { + if (v->hash == 0) + v->hash = pcr_bank; + +@@ -2410,9 +2397,9 @@ static int tpm2_pcr_mask_good( + return r; + + /* If at least one of the selected PCR values is something other than all 0x00 or all 0xFF we are happy. */ +- for (unsigned i = 0; i < n_pcr_values; i++) +- if (!memeqbyte(0x00, pcr_values[i].value.buffer, pcr_values[i].value.size) && +- !memeqbyte(0xFF, pcr_values[i].value.buffer, pcr_values[i].value.size)) ++ FOREACH_ARRAY(v, pcr_values, n_pcr_values) ++ if (!memeqbyte(0x00, v->value.buffer, v->value.size) && ++ !memeqbyte(0xFF, v->value.buffer, v->value.size)) + return true; + + return false; +@@ -2605,12 +2592,12 @@ int tpm2_get_good_pcr_banks_strv( + if (n_algs < 0) + return n_algs; + +- for (int i = 0; i < n_algs; i++) { ++ FOREACH_ARRAY(a, algs, n_algs) { + _cleanup_free_ char *n = NULL; + const EVP_MD *implementation; + const char *salg; + +- salg = tpm2_hash_alg_to_string(algs[i]); ++ salg = tpm2_hash_alg_to_string(*a); + if (!salg) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure."); + +@@ -2679,8 +2666,8 @@ int tpm2_digest_many( + return 0; + } + +- for (size_t i = 0; i < n_data; i++) +- sha256_process_bytes(data[i].iov_base, data[i].iov_len, &ctx); ++ FOREACH_ARRAY(d, data, n_data) ++ sha256_process_bytes(d->iov_base, d->iov_len, &ctx); + + sha256_finish_ctx(&ctx, digest->buffer); + +@@ -4688,16 +4675,16 @@ Tpm2Support tpm2_support(void) { + #if HAVE_TPM2 + static void tpm2_pcr_values_apply_default_hash_alg(Tpm2PCRValue *pcr_values, size_t n_pcr_values) { + TPMI_ALG_HASH default_hash = 0; +- for (size_t i = 0; i < n_pcr_values; i++) +- if (pcr_values[i].hash != 0) { +- default_hash = pcr_values[i].hash; ++ FOREACH_ARRAY(v, pcr_values, n_pcr_values) ++ if (v->hash != 0) { ++ default_hash = v->hash; + break; + } + + if (default_hash != 0) +- for (size_t i = 0; i < n_pcr_values; i++) +- if (pcr_values[i].hash == 0) +- pcr_values[i].hash = default_hash; ++ FOREACH_ARRAY(v, pcr_values, n_pcr_values) ++ if (v->hash == 0) ++ v->hash = default_hash; + } + #endif + diff --git a/SOURCES/0585-tpm2-use-SIZE_MAX-instead-of-strlen-for-unhexmem.patch b/SOURCES/0585-tpm2-use-SIZE_MAX-instead-of-strlen-for-unhexmem.patch new file mode 100644 index 0000000..e4e8343 --- /dev/null +++ b/SOURCES/0585-tpm2-use-SIZE_MAX-instead-of-strlen-for-unhexmem.patch @@ -0,0 +1,25 @@ +From 58681f650a4ccd8a138eb0d5e45665d7973f4d43 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 16:29:44 -0400 +Subject: [PATCH] tpm2: use SIZE_MAX instead of strlen() for unhexmem() + +(cherry picked from commit 7001a7daf7b2cf67991acc8383a58ff71432188c) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index a91e805e06..4da2143360 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1733,7 +1733,7 @@ int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value) { + + _cleanup_free_ void *buf = NULL; + size_t buf_size = 0; +- r = unhexmem(p, strlen(p), &buf, &buf_size); ++ r = unhexmem(p, SIZE_MAX, &buf, &buf_size); + if (r < 0) + return log_error_errno(r, "Invalid pcr hash value '%s': %m", p); + diff --git a/SOURCES/0586-tpm2-put-isempty-check-inside-previous-isempty-check.patch b/SOURCES/0586-tpm2-put-isempty-check-inside-previous-isempty-check.patch new file mode 100644 index 0000000..40450d2 --- /dev/null +++ b/SOURCES/0586-tpm2-put-isempty-check-inside-previous-isempty-check.patch @@ -0,0 +1,53 @@ +From 3ffb4462e0f3db78db5c1cf0cf8b14781c72d8e6 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 17:29:28 -0400 +Subject: [PATCH] tpm2: put !isempty() check inside previous !isempty() check + +(cherry picked from commit 2b2ee3f265957e4dda9f82b1b6e8138117a90502) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 26 +++++++++++++------------- + 1 file changed, 13 insertions(+), 13 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 4da2143360..d5dde21b05 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1725,23 +1725,23 @@ int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value) { + if (r < 0) + return log_error_errno(r, "Invalid pcr hash algorithm '%s': %m", hash); + pcr_value.hash = (TPMI_ALG_HASH) r; +- } + +- if (!isempty(p)) { +- /* Remove leading 0x if present */ +- p = startswith_no_case(p, "0x") ?: p; ++ if (!isempty(p)) { ++ /* Remove leading 0x if present */ ++ p = startswith_no_case(p, "0x") ?: p; + +- _cleanup_free_ void *buf = NULL; +- size_t buf_size = 0; +- r = unhexmem(p, SIZE_MAX, &buf, &buf_size); +- if (r < 0) +- return log_error_errno(r, "Invalid pcr hash value '%s': %m", p); ++ _cleanup_free_ void *buf = NULL; ++ size_t buf_size = 0; ++ r = unhexmem(p, SIZE_MAX, &buf, &buf_size); ++ if (r < 0) ++ return log_error_errno(r, "Invalid pcr hash value '%s': %m", p); + +- r = TPM2B_DIGEST_CHECK_SIZE(buf_size); +- if (r < 0) +- return log_error_errno(r, "PCR hash value size %zu too large.", buf_size); ++ r = TPM2B_DIGEST_CHECK_SIZE(buf_size); ++ if (r < 0) ++ return log_error_errno(r, "PCR hash value size %zu too large.", buf_size); + +- pcr_value.value = TPM2B_DIGEST_MAKE(buf, buf_size); ++ pcr_value.value = TPM2B_DIGEST_MAKE(buf, buf_size); ++ } + } + + *ret_pcr_value = pcr_value; diff --git a/SOURCES/0587-tpm2-simplify-call-to-asprintf.patch b/SOURCES/0587-tpm2-simplify-call-to-asprintf.patch new file mode 100644 index 0000000..ff186e0 --- /dev/null +++ b/SOURCES/0587-tpm2-simplify-call-to-asprintf.patch @@ -0,0 +1,28 @@ +From cb7d8d4b49a221dac222c417ec0fdd067ba9c21f Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 17:31:56 -0400 +Subject: [PATCH] tpm2: simplify call to asprintf() + +(cherry picked from commit 495f2bf57ccd74c5e63040f05430e1148942b2c7) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 4 +--- + 1 file changed, 1 insertion(+), 3 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index d5dde21b05..02ea6a61f9 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1754,10 +1754,8 @@ int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value) { + * string. This does not check for validity. */ + char *tpm2_pcr_value_to_string(const Tpm2PCRValue *pcr_value) { + _cleanup_free_ char *index = NULL, *value = NULL; +- int r; + +- r = asprintf(&index, "%u", pcr_value->index); +- if (r < 0) ++ if (asprintf(&index, "%u", pcr_value->index) < 0) + return NULL; + + const char *hash = tpm2_hash_alg_to_string(pcr_value->hash); diff --git a/SOURCES/0588-tpm2-check-pcr-value-hash-0-before-looking-up-hash-a.patch b/SOURCES/0588-tpm2-check-pcr-value-hash-0-before-looking-up-hash-a.patch new file mode 100644 index 0000000..43e0a44 --- /dev/null +++ b/SOURCES/0588-tpm2-check-pcr-value-hash-0-before-looking-up-hash-a.patch @@ -0,0 +1,26 @@ +From 4f87f34d6e8cf2fbbb5899bfd6ae459b5f8d2f09 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 17:37:40 -0400 +Subject: [PATCH] tpm2: check pcr value hash != 0 before looking up hash + algorithm name + +(cherry picked from commit 8e757259c3f491841a6c5aa853f2cfce41e95b2e) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 02ea6a61f9..d0217e4655 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1758,7 +1758,7 @@ char *tpm2_pcr_value_to_string(const Tpm2PCRValue *pcr_value) { + if (asprintf(&index, "%u", pcr_value->index) < 0) + return NULL; + +- const char *hash = tpm2_hash_alg_to_string(pcr_value->hash); ++ const char *hash = pcr_value->hash > 0 ? tpm2_hash_alg_to_string(pcr_value->hash) : NULL; + + if (hash && pcr_value->value.size > 0) { + value = hexmem(pcr_value->value.buffer, pcr_value->value.size); diff --git a/SOURCES/0589-tpm2-use-strempty.patch b/SOURCES/0589-tpm2-use-strempty.patch new file mode 100644 index 0000000..348c741 --- /dev/null +++ b/SOURCES/0589-tpm2-use-strempty.patch @@ -0,0 +1,25 @@ +From cc0d6f06461c16835a0d01e6a62a5e14dd566823 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 17:39:20 -0400 +Subject: [PATCH] tpm2: use strempty() + +(cherry picked from commit 85b6f299b2ae11ea589d373559d43cfe88c8d30f) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index d0217e4655..5508763a38 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1766,7 +1766,7 @@ char *tpm2_pcr_value_to_string(const Tpm2PCRValue *pcr_value) { + return NULL; + } + +- return strjoin(index, hash ? ":" : "", hash ?: "", value ? "=" : "", value ?: ""); ++ return strjoin(index, hash ? ":" : "", strempty(hash), value ? "=" : "", strempty(value)); + } + + /* Parse a string argument into an array of Tpm2PCRValue objects. diff --git a/SOURCES/0590-tpm2-split-TPM2_PCR_VALUE_MAKE-over-multiple-lines.patch b/SOURCES/0590-tpm2-split-TPM2_PCR_VALUE_MAKE-over-multiple-lines.patch new file mode 100644 index 0000000..5b004c9 --- /dev/null +++ b/SOURCES/0590-tpm2-split-TPM2_PCR_VALUE_MAKE-over-multiple-lines.patch @@ -0,0 +1,31 @@ +From e2358ec64ed4d204a5c62617da494d5928a0dbc9 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 17:42:39 -0400 +Subject: [PATCH] tpm2: split TPM2_PCR_VALUE_MAKE() over multiple lines + +(cherry picked from commit c6e5178220237fb10187c573a8ac4384993c0206) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.h | 8 +++++++- + 1 file changed, 7 insertions(+), 1 deletion(-) + +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index ff90f034fe..6c836180c6 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -75,7 +75,13 @@ typedef struct { + TPM2B_DIGEST value; + } Tpm2PCRValue; + +-#define TPM2_PCR_VALUE_MAKE(i, h, v) (Tpm2PCRValue) { .index = (i), .hash = (h), .value = ((TPM2B_DIGEST) v), } ++#define TPM2_PCR_VALUE_MAKE(i, h, v) \ ++ (Tpm2PCRValue) { \ ++ .index = (i), \ ++ .hash = (h), \ ++ .value = ((TPM2B_DIGEST) v), \ ++ } ++ + bool tpm2_pcr_value_valid(const Tpm2PCRValue *pcr_value); + int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value); + char *tpm2_pcr_value_to_string(const Tpm2PCRValue *pcr_value); diff --git a/SOURCES/0591-tpm2-remove-ret_-prefix-from-input-output-params.patch b/SOURCES/0591-tpm2-remove-ret_-prefix-from-input-output-params.patch new file mode 100644 index 0000000..0447769 --- /dev/null +++ b/SOURCES/0591-tpm2-remove-ret_-prefix-from-input-output-params.patch @@ -0,0 +1,62 @@ +From bd156a667759a5fa4ee3bec7d1eac280adb4a2ad Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 18:09:52 -0400 +Subject: [PATCH] tpm2: remove ret_ prefix from input/output params + +(cherry picked from commit ae2b38e4630b76103367869b5ace87ae0dd34f99) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 24 ++++++++++++------------ + 1 file changed, 12 insertions(+), 12 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 5508763a38..f91ef02a4e 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -4732,33 +4732,33 @@ int tpm2_parse_pcr_argument(const char *arg, Tpm2PCRValue **ret_pcr_values, size + * including application of the default hash algorithm. Then the two arrays are combined, the default hash + * algorithm check applied again (in case either the previous or current array had no default hash + * algorithm), and then the resulting array is sorted and rechecked for validity. */ +-int tpm2_parse_pcr_argument_append(const char *arg, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values) { ++int tpm2_parse_pcr_argument_append(const char *arg, Tpm2PCRValue **pcr_values, size_t *n_pcr_values) { + #if HAVE_TPM2 + int r; + + assert(arg); +- assert(ret_pcr_values); +- assert(ret_n_pcr_values); ++ assert(pcr_values); ++ assert(n_pcr_values); + +- _cleanup_free_ Tpm2PCRValue *pcr_values = NULL; +- size_t n_pcr_values; +- r = tpm2_parse_pcr_argument(arg, &pcr_values, &n_pcr_values); ++ _cleanup_free_ Tpm2PCRValue *more_pcr_values = NULL; ++ size_t n_more_pcr_values; ++ r = tpm2_parse_pcr_argument(arg, &more_pcr_values, &n_more_pcr_values); + if (r < 0) + return r; + + /* If we got previous values, append them. */ +- if (*ret_pcr_values && !GREEDY_REALLOC_APPEND(pcr_values, n_pcr_values, *ret_pcr_values, *ret_n_pcr_values)) ++ if (*pcr_values && !GREEDY_REALLOC_APPEND(more_pcr_values, n_more_pcr_values, *pcr_values, *n_pcr_values)) + return log_oom(); + +- tpm2_pcr_values_apply_default_hash_alg(pcr_values, n_pcr_values); ++ tpm2_pcr_values_apply_default_hash_alg(more_pcr_values, n_more_pcr_values); + +- tpm2_sort_pcr_values(pcr_values, n_pcr_values); ++ tpm2_sort_pcr_values(more_pcr_values, n_more_pcr_values); + +- if (!tpm2_pcr_values_valid(pcr_values, n_pcr_values)) ++ if (!tpm2_pcr_values_valid(more_pcr_values, n_more_pcr_values)) + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Parsed PCR values are not valid."); + +- SWAP_TWO(*ret_pcr_values, pcr_values); +- *ret_n_pcr_values = n_pcr_values; ++ SWAP_TWO(*pcr_values, more_pcr_values); ++ *n_pcr_values = n_more_pcr_values; + + return 0; + #else diff --git a/SOURCES/0592-tpm2-use-memcpy_safe-instead-of-memcpy.patch b/SOURCES/0592-tpm2-use-memcpy_safe-instead-of-memcpy.patch new file mode 100644 index 0000000..de9dac8 --- /dev/null +++ b/SOURCES/0592-tpm2-use-memcpy_safe-instead-of-memcpy.patch @@ -0,0 +1,25 @@ +From 7f1c570cc09a1bd03bf4d253997f40414643e9b1 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 18:10:56 -0400 +Subject: [PATCH] tpm2: use memcpy_safe() instead of memcpy() + +(cherry picked from commit 65fd657e0a13c0fdd4221cf6f22d51462a0bbc10) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 6c836180c6..4f6f795fbb 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -199,7 +199,7 @@ int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fing + struct_type UNIQ_T(STRUCT, uniq) = { .size_field = UNIQ_T(SIZE, uniq), }; \ + assert(sizeof(UNIQ_T(STRUCT, uniq).buffer_field) >= (size_t) UNIQ_T(SIZE, uniq)); \ + if (UNIQ_T(BUF, uniq)) \ +- memcpy(UNIQ_T(STRUCT, uniq).buffer_field, UNIQ_T(BUF, uniq), UNIQ_T(SIZE, uniq)); \ ++ memcpy_safe(UNIQ_T(STRUCT, uniq).buffer_field, UNIQ_T(BUF, uniq), UNIQ_T(SIZE, uniq)); \ + UNIQ_T(STRUCT, uniq); \ + }) + diff --git a/SOURCES/0593-openssl-use-new-char-size-instead-of-malloc-size.patch b/SOURCES/0593-openssl-use-new-char-size-instead-of-malloc-size.patch new file mode 100644 index 0000000..0da2af4 --- /dev/null +++ b/SOURCES/0593-openssl-use-new-char-size-instead-of-malloc-size.patch @@ -0,0 +1,25 @@ +From bdd07d469eb24b566be3cf8e4dd972c01c9a878f Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 18:14:07 -0400 +Subject: [PATCH] openssl: use new(char, size) instead of malloc(size) + +(cherry picked from commit b0307102951f57becf733f740710bc826e1609ba) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index c3f8f91eb3..313c1fc4ea 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -377,7 +377,7 @@ int ecc_pkey_to_curve_x_y( + if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, NULL, 0, &name_size)) + return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC group name size."); + +- _cleanup_free_ char *name = malloc(name_size + 1); ++ _cleanup_free_ char *name = new(char, name_size + 1); + if (!name) + return log_oom_debug(); + diff --git a/SOURCES/0594-tpm2-use-table-for-openssl-tpm2-ecc-curve-id-mapping.patch b/SOURCES/0594-tpm2-use-table-for-openssl-tpm2-ecc-curve-id-mapping.patch new file mode 100644 index 0000000..c60a02c --- /dev/null +++ b/SOURCES/0594-tpm2-use-table-for-openssl-tpm2-ecc-curve-id-mapping.patch @@ -0,0 +1,79 @@ +From 48a11405b9025c3bcb2927913c40a8b4d8a7b2c3 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 18:40:13 -0400 +Subject: [PATCH] tpm2: use table for openssl<->tpm2 ecc curve id mappings + +(cherry picked from commit 6761e1355edae6aa133f49325c8a03100694ba81) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 46 ++++++++++++++++++++++++------------------ + 1 file changed, 26 insertions(+), 20 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index f91ef02a4e..8a53c0ae59 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -3446,36 +3446,42 @@ static int tpm2_build_sealing_policy( + } + + #if HAVE_OPENSSL +-static int tpm2_ecc_curve_from_openssl_curve_id(int curve_id, TPM2_ECC_CURVE *ret) { ++static const struct { ++ TPM2_ECC_CURVE tpm2_ecc_curve_id; ++ int openssl_ecc_curve_id; ++} tpm2_openssl_ecc_curve_table[] = { ++ { TPM2_ECC_NIST_P192, NID_X9_62_prime192v1, }, ++ { TPM2_ECC_NIST_P224, NID_secp224r1, }, ++ { TPM2_ECC_NIST_P256, NID_X9_62_prime256v1, }, ++ { TPM2_ECC_NIST_P384, NID_secp384r1, }, ++ { TPM2_ECC_NIST_P521, NID_secp521r1, }, ++ { TPM2_ECC_SM2_P256, NID_sm2, }, ++}; ++ ++static int tpm2_ecc_curve_from_openssl_curve_id(int openssl_ecc_curve_id, TPM2_ECC_CURVE *ret) { + assert(ret); + +- switch (curve_id) { +- case NID_X9_62_prime192v1: *ret = TPM2_ECC_NIST_P192; return 0; +- case NID_secp224r1: *ret = TPM2_ECC_NIST_P192; return 0; +- case NID_X9_62_prime256v1: *ret = TPM2_ECC_NIST_P256; return 0; +- case NID_secp384r1: *ret = TPM2_ECC_NIST_P384; return 0; +- case NID_secp521r1: *ret = TPM2_ECC_NIST_P521; return 0; +- case NID_sm2: *ret = TPM2_ECC_SM2_P256; return 0; +- } ++ FOREACH_ARRAY(t, tpm2_openssl_ecc_curve_table, ELEMENTSOF(tpm2_openssl_ecc_curve_table)) ++ if (t->openssl_ecc_curve_id == openssl_ecc_curve_id) { ++ *ret = t->tpm2_ecc_curve_id; ++ return 0; ++ } + + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), +- "Openssl ECC curve id %d not supported.", curve_id); ++ "Openssl ECC curve id %d not supported.", openssl_ecc_curve_id); + } + +-static int tpm2_ecc_curve_to_openssl_curve_id(TPM2_ECC_CURVE curve, int *ret) { ++static int tpm2_ecc_curve_to_openssl_curve_id(TPM2_ECC_CURVE tpm2_ecc_curve_id, int *ret) { + assert(ret); + +- switch (curve) { +- case TPM2_ECC_NIST_P192: *ret = NID_X9_62_prime192v1; return 0; +- case TPM2_ECC_NIST_P224: *ret = NID_secp224r1; return 0; +- case TPM2_ECC_NIST_P256: *ret = NID_X9_62_prime256v1; return 0; +- case TPM2_ECC_NIST_P384: *ret = NID_secp384r1; return 0; +- case TPM2_ECC_NIST_P521: *ret = NID_secp521r1; return 0; +- case TPM2_ECC_SM2_P256: *ret = NID_sm2; return 0; +- } ++ FOREACH_ARRAY(t, tpm2_openssl_ecc_curve_table, ELEMENTSOF(tpm2_openssl_ecc_curve_table)) ++ if (t->tpm2_ecc_curve_id == tpm2_ecc_curve_id) { ++ *ret = t->openssl_ecc_curve_id; ++ return 0; ++ } + + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), +- "TPM2 ECC curve %u not supported.", curve); ++ "TPM2 ECC curve %u not supported.", tpm2_ecc_curve_id); + } + + #define TPM2_RSA_DEFAULT_EXPONENT UINT32_C(0x10001) diff --git a/SOURCES/0595-tpm2-use-switch-instead-of-if-else.patch b/SOURCES/0595-tpm2-use-switch-instead-of-if-else.patch new file mode 100644 index 0000000..1d767d0 --- /dev/null +++ b/SOURCES/0595-tpm2-use-switch-instead-of-if-else.patch @@ -0,0 +1,87 @@ +From b0ac1d4d5f7bf1b7f3b5afe6b966a2eec5ebdca6 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 19:02:18 -0400 +Subject: [PATCH] tpm2: use switch() instead of if-else + +(cherry picked from commit 3f4d5dfd651864adf94e43ffdc6303a41f96fcd4) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 27 ++++++++++++++++++--------- + 1 file changed, 18 insertions(+), 9 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 8a53c0ae59..f29515be14 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -3493,7 +3493,8 @@ int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret + assert(ret); + + const TPMT_PUBLIC *p = &public->publicArea; +- if (p->type == TPM2_ALG_ECC) { ++ switch (p->type) { ++ case TPM2_ALG_ECC: { + int curve_id; + r = tpm2_ecc_curve_to_openssl_curve_id(p->parameters.eccDetail.curveID, &curve_id); + if (r < 0) +@@ -3508,8 +3509,7 @@ int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret + point->y.size, + ret); + } +- +- if (p->type == TPM2_ALG_RSA) { ++ case TPM2_ALG_RSA: { + /* TPM specification Part 2 ("Structures") section for TPMS_RSA_PARAMS states "An exponent of + * zero indicates that the exponent is the default of 2^16 + 1". */ + uint32_t exponent = htobe32(p->parameters.rsaDetail.exponent ?: TPM2_RSA_DEFAULT_EXPONENT); +@@ -3520,9 +3520,10 @@ int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret + sizeof(exponent), + ret); + } +- +- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), +- "TPM2 asymmetric algorithm 0x%" PRIx16 " not supported.", p->type); ++ default: ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "TPM2 asymmetric algorithm 0x%" PRIx16 " not supported.", p->type); ++ } + } + + int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) { +@@ -3546,7 +3547,8 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) + key_id = EVP_PKEY_id(pkey); + #endif + +- if (key_id == EVP_PKEY_EC) { ++ switch (key_id) { ++ case EVP_PKEY_EC: { + public.type = TPM2_ALG_ECC; + + int curve_id; +@@ -3576,7 +3578,10 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) + return log_error_errno(r, "ECC key y size %zu too large.", y_size); + + public.unique.ecc.y = TPM2B_ECC_PARAMETER_MAKE(y, y_size); +- } else if (key_id == EVP_PKEY_RSA) { ++ ++ break; ++ } ++ case EVP_PKEY_RSA: { + public.type = TPM2_ALG_RSA; + + _cleanup_free_ void *n = NULL, *e = NULL; +@@ -3602,9 +3607,13 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) + if (exponent == TPM2_RSA_DEFAULT_EXPONENT) + exponent = 0; + public.parameters.rsaDetail.exponent = exponent; +- } else ++ ++ break; ++ } ++ default: + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "EVP_PKEY type %d not supported.", key_id); ++ } + + *ret = (TPM2B_PUBLIC) { + .size = sizeof(public), diff --git a/SOURCES/0596-tpm2-make-logging-level-consistent-at-debug-for-some.patch b/SOURCES/0596-tpm2-make-logging-level-consistent-at-debug-for-some.patch new file mode 100644 index 0000000..c25a67a --- /dev/null +++ b/SOURCES/0596-tpm2-make-logging-level-consistent-at-debug-for-some.patch @@ -0,0 +1,63 @@ +From 5819204528f5086b3772ca6caccfb23f55e01339 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 19:06:38 -0400 +Subject: [PATCH] tpm2: make logging level consistent at debug for some + functions + +(cherry picked from commit ed35ac31557722530e174e474b0be1b21bac53a4) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 12 ++++++------ + 1 file changed, 6 insertions(+), 6 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index f29515be14..48bb25a417 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -3556,7 +3556,7 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) + size_t x_size, y_size; + r = ecc_pkey_to_curve_x_y(pkey, &curve_id, &x, &x_size, &y, &y_size); + if (r < 0) +- return log_error_errno(r, "Could not get ECC key curve/x/y: %m"); ++ return log_debug_errno(r, "Could not get ECC key curve/x/y: %m"); + + TPM2_ECC_CURVE curve; + r = tpm2_ecc_curve_from_openssl_curve_id(curve_id, &curve); +@@ -3569,13 +3569,13 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) + + r = TPM2B_ECC_PARAMETER_CHECK_SIZE(x_size); + if (r < 0) +- return log_error_errno(r, "ECC key x size %zu too large.", x_size); ++ return log_debug_errno(r, "ECC key x size %zu too large.", x_size); + + public.unique.ecc.x = TPM2B_ECC_PARAMETER_MAKE(x, x_size); + + r = TPM2B_ECC_PARAMETER_CHECK_SIZE(y_size); + if (r < 0) +- return log_error_errno(r, "ECC key y size %zu too large.", y_size); ++ return log_debug_errno(r, "ECC key y size %zu too large.", y_size); + + public.unique.ecc.y = TPM2B_ECC_PARAMETER_MAKE(y, y_size); + +@@ -3588,17 +3588,17 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) + size_t n_size, e_size; + r = rsa_pkey_to_n_e(pkey, &n, &n_size, &e, &e_size); + if (r < 0) +- return log_error_errno(r, "Could not get RSA key n/e: %m"); ++ return log_debug_errno(r, "Could not get RSA key n/e: %m"); + + r = TPM2B_PUBLIC_KEY_RSA_CHECK_SIZE(n_size); + if (r < 0) +- return log_error_errno(r, "RSA key n size %zu too large.", n_size); ++ return log_debug_errno(r, "RSA key n size %zu too large.", n_size); + + public.unique.rsa = TPM2B_PUBLIC_KEY_RSA_MAKE(n, n_size); + public.parameters.rsaDetail.keyBits = n_size * 8; + + if (sizeof(uint32_t) < e_size) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "RSA key e size %zu too large.", e_size); + + uint32_t exponent = 0; diff --git a/SOURCES/0597-tpm2-remove-unnecessary-void-cast.patch b/SOURCES/0597-tpm2-remove-unnecessary-void-cast.patch new file mode 100644 index 0000000..049d586 --- /dev/null +++ b/SOURCES/0597-tpm2-remove-unnecessary-void-cast.patch @@ -0,0 +1,25 @@ +From c7c182f3b04e2a40733a72164cb950157e92e35f Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 19:07:38 -0400 +Subject: [PATCH] tpm2: remove unnecessary void* cast + +(cherry picked from commit 70cb382d368518d69e99b5e384e2456eb2d9916e) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 48bb25a417..9ef69fb7d8 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -3602,7 +3602,7 @@ int tpm2_tpm2b_public_from_openssl_pkey(const EVP_PKEY *pkey, TPM2B_PUBLIC *ret) + "RSA key e size %zu too large.", e_size); + + uint32_t exponent = 0; +- memcpy((void*) &exponent, e, e_size); ++ memcpy(&exponent, e, e_size); + exponent = be32toh(exponent) >> (32 - e_size * 8); + if (exponent == TPM2_RSA_DEFAULT_EXPONENT) + exponent = 0; diff --git a/SOURCES/0598-tpm2-add-tpm2_pcr_values_has_-any-all-_values-functi.patch b/SOURCES/0598-tpm2-add-tpm2_pcr_values_has_-any-all-_values-functi.patch new file mode 100644 index 0000000..8f125d4 --- /dev/null +++ b/SOURCES/0598-tpm2-add-tpm2_pcr_values_has_-any-all-_values-functi.patch @@ -0,0 +1,78 @@ +From 889bffecee285203761d0d9f052122f2ef46dfce Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 19:18:40 -0400 +Subject: [PATCH] tpm2: add tpm2_pcr_values_has_(any|all)_values() functions + +(cherry picked from commit 26d8d71fa5fc9d620899e3940ad246485991e632) + +Related: RHEL-16182 +--- + src/cryptenroll/cryptenroll-tpm2.c | 7 +------ + src/shared/tpm2-util.c | 22 ++++++++++++++++++++++ + src/shared/tpm2-util.h | 2 ++ + 3 files changed, 25 insertions(+), 6 deletions(-) + +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index 7c1946c3b1..e16039590a 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -214,12 +214,7 @@ int enroll_tpm2(struct crypt_device *cd, + if (r < 0) + return r; + +- bool pcr_value_specified = false; +- for (size_t i = 0; i < n_hash_pcr_values; i++) +- if (hash_pcr_values[i].value.size > 0) { +- pcr_value_specified = true; +- break; +- } ++ bool pcr_value_specified = tpm2_pcr_values_has_any_values(hash_pcr_values, n_hash_pcr_values); + + r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values); + if (r < 0) +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 9ef69fb7d8..05189d9dfe 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1585,6 +1585,28 @@ bool tpm2_pcr_values_valid(const Tpm2PCRValue *pcr_values, size_t n_pcr_values) + return true; + } + ++/* Returns true if any of the provided PCR values has an actual hash value included, false otherwise. */ ++bool tpm2_pcr_values_has_any_values(const Tpm2PCRValue *pcr_values, size_t n_pcr_values) { ++ assert(pcr_values || n_pcr_values == 0); ++ ++ FOREACH_ARRAY(v, pcr_values, n_pcr_values) ++ if (v->value.size > 0) ++ return true; ++ ++ return false; ++} ++ ++/* Returns true if all of the provided PCR values has an actual hash value included, false otherwise. */ ++bool tpm2_pcr_values_has_all_values(const Tpm2PCRValue *pcr_values, size_t n_pcr_values) { ++ assert(pcr_values || n_pcr_values == 0); ++ ++ FOREACH_ARRAY(v, pcr_values, n_pcr_values) ++ if (v->value.size == 0) ++ return false; ++ ++ return true; ++} ++ + static int cmp_pcr_values(const Tpm2PCRValue *a, const Tpm2PCRValue *b) { + assert(a); + assert(b); +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 4f6f795fbb..f74efbd223 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -83,6 +83,8 @@ typedef struct { + } + + bool tpm2_pcr_value_valid(const Tpm2PCRValue *pcr_value); ++bool tpm2_pcr_values_has_any_values(const Tpm2PCRValue *pcr_values, size_t n_pcr_values); ++bool tpm2_pcr_values_has_all_values(const Tpm2PCRValue *pcr_values, size_t n_pcr_values); + int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value); + char *tpm2_pcr_value_to_string(const Tpm2PCRValue *pcr_value); + diff --git a/SOURCES/0599-tpm2-wrap-7-in-UINT32_C.patch b/SOURCES/0599-tpm2-wrap-7-in-UINT32_C.patch new file mode 100644 index 0000000..3458427 --- /dev/null +++ b/SOURCES/0599-tpm2-wrap-7-in-UINT32_C.patch @@ -0,0 +1,25 @@ +From 70ab69fd242a719fb3ce0e8d5fdeb18025ab6f96 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 19:21:21 -0400 +Subject: [PATCH] tpm2: wrap (7) in UINT32_C() + +(cherry picked from commit 81e3d37211362a4a0549f57abb56ba030337a135) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.h | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index f74efbd223..f9efca9311 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -249,7 +249,7 @@ int tpm2_make_luks2_json(int keyslot, uint32_t hash_pcr_mask, uint16_t pcr_bank, + int tpm2_parse_luks2_json(JsonVariant *v, int *ret_keyslot, uint32_t *ret_hash_pcr_mask, uint16_t *ret_pcr_bank, void **ret_pubkey, size_t *ret_pubkey_size, uint32_t *ret_pubkey_pcr_mask, uint16_t *ret_primary_alg, void **ret_blob, size_t *ret_blob_size, void **ret_policy_hash, size_t *ret_policy_hash_size, void **ret_salt, size_t *ret_salt_size, void **ret_srk_buf, size_t *ret_srk_buf_size, TPM2Flags *ret_flags); + + /* Default to PCR 7 only */ +-#define TPM2_PCR_INDEX_DEFAULT (7) ++#define TPM2_PCR_INDEX_DEFAULT UINT32_C(7) + #define TPM2_PCR_MASK_DEFAULT INDEX_TO_MASK(uint32_t, TPM2_PCR_INDEX_DEFAULT) + + /* We want the helpers below to work also if TPM2 libs are not available, hence define these four defines if diff --git a/SOURCES/0600-cryptenroll-change-man-page-example-to-remove-leadin.patch b/SOURCES/0600-cryptenroll-change-man-page-example-to-remove-leadin.patch new file mode 100644 index 0000000..2932d6a --- /dev/null +++ b/SOURCES/0600-cryptenroll-change-man-page-example-to-remove-leadin.patch @@ -0,0 +1,29 @@ +From d3e2f335036aa4f899f01d4dd0199685893a1837 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 21 Aug 2023 19:25:49 -0400 +Subject: [PATCH] cryptenroll: change man page example to remove leading 0x and + lowercase hex + +(cherry picked from commit a11a2e059cd8e7d488985c23ceee0d6a2d5c8fb0) + +Related: RHEL-16182 +--- + man/systemd-cryptenroll.xml | 4 ++-- + 1 file changed, 2 insertions(+), 2 deletions(-) + +diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml +index 3f36f320b6..28e315bd1c 100644 +--- a/man/systemd-cryptenroll.xml ++++ b/man/systemd-cryptenroll.xml +@@ -391,9 +391,9 @@ + specifies that PCR registers 4, 1, and 5 should be used. + Example: specifies that PCR register 7 from the SHA256 + bank should be used. +- Example: ++ Example: + specifies that PCR register 4 from the SHA1 bank should be used, and a hash digest value of +- 0x3A3F780F11A4B49969FCAA80CD6E3957C33B2275 will be used instead of reading the current PCR ++ 3a3f780f11a4b49969fcaa80cd6e3957c33b2275 will be used instead of reading the current PCR + value. + + diff --git a/SOURCES/0601-openssl-add-log_openssl_errors.patch b/SOURCES/0601-openssl-add-log_openssl_errors.patch new file mode 100644 index 0000000..77c2df3 --- /dev/null +++ b/SOURCES/0601-openssl-add-log_openssl_errors.patch @@ -0,0 +1,460 @@ +From 7ea93aa35e8a521d68f7fab44d46590210f637b2 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 4 Aug 2023 18:51:55 -0400 +Subject: [PATCH] openssl: add log_openssl_errors() + +Add a macro to log all errors in the openssl 'thread error queue'. + +This consolidates all the openssl-generated errors to return -EIO and log at +debug level. + +Also add a 'invalid' test in test-openssl, to allow manual verification that +the openssl error(s) are logged. + +(cherry picked from commit 60696b22d96d0c27680400818672e16f8bb2d53b) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 175 +++++++++++++++++++++----------------- + src/test/test-openssl.c | 8 ++ + 2 files changed, 107 insertions(+), 76 deletions(-) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index 313c1fc4ea..3d3d8090f8 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -6,6 +6,32 @@ + #include "hexdecoct.h" + + #if HAVE_OPENSSL ++/* For each error in the the Openssl thread error queue, log the provided message and the Openssl error ++ * string. If there are no errors in the Openssl thread queue, this logs the message with "No openssl ++ * errors." This logs at level debug. Returns -EIO (or -ENOMEM). */ ++#define log_openssl_errors(fmt, ...) _log_openssl_errors(UNIQ, fmt, ##__VA_ARGS__) ++#define _log_openssl_errors(u, fmt, ...) \ ++ ({ \ ++ size_t UNIQ_T(MAX, u) = 512 /* arbitrary, but openssl doc states it must be >= 256 */; \ ++ _cleanup_free_ char *UNIQ_T(BUF, u) = malloc(UNIQ_T(MAX, u)); \ ++ !UNIQ_T(BUF, u) \ ++ ? log_oom_debug() \ ++ : __log_openssl_errors(u, UNIQ_T(BUF, u), UNIQ_T(MAX, u), fmt, ##__VA_ARGS__) \ ++ ?: log_debug_errno(SYNTHETIC_ERRNO(EIO), fmt ": No openssl errors.", ##__VA_ARGS__); \ ++ }) ++#define __log_openssl_errors(u, buf, max, fmt, ...) \ ++ ({ \ ++ int UNIQ_T(R, u) = 0; \ ++ for (;;) { \ ++ unsigned long UNIQ_T(E, u) = ERR_get_error(); \ ++ if (UNIQ_T(E, u) == 0) \ ++ break; \ ++ ERR_error_string_n(UNIQ_T(E, u), buf, max); \ ++ UNIQ_T(R, u) = log_debug_errno(SYNTHETIC_ERRNO(EIO), fmt ": %s", ##__VA_ARGS__, buf); \ ++ } \ ++ UNIQ_T(R, u); \ ++ }) ++ + int openssl_pkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) { + assert(pem); + assert(ret); +@@ -17,7 +43,7 @@ int openssl_pkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) { + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = PEM_read_PUBKEY(f, NULL, NULL, NULL); + if (!pkey) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to parse PEM."); ++ return log_openssl_errors("Failed to parse PEM"); + + *ret = TAKE_PTR(pkey); + +@@ -74,23 +100,23 @@ int rsa_encrypt_bytes( + + ctx = EVP_PKEY_CTX_new(pkey, NULL); + if (!ctx) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to allocate public key context"); ++ return log_openssl_errors("Failed to allocate public key context"); + + if (EVP_PKEY_encrypt_init(ctx) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize public key context"); ++ return log_openssl_errors("Failed to initialize public key context"); + + if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_PADDING) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to configure PKCS#1 padding"); ++ return log_openssl_errors("Failed to configure PKCS#1 padding"); + + if (EVP_PKEY_encrypt(ctx, NULL, &l, decrypted_key, decrypted_key_size) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size"); ++ return log_openssl_errors("Failed to determine encrypted key size"); + + b = malloc(l); + if (!b) + return -ENOMEM; + + if (EVP_PKEY_encrypt(ctx, b, &l, decrypted_key, decrypted_key_size) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to determine encrypted key size"); ++ return log_openssl_errors("Failed to determine encrypted key size"); + + *ret_encrypt_key = TAKE_PTR(b); + *ret_encrypt_key_size = l; +@@ -139,53 +165,53 @@ int rsa_pkey_from_n_e(const void *n, size_t n_size, const void *e, size_t e_size + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!ctx) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); + + _cleanup_(BN_freep) BIGNUM *bn_n = BN_bin2bn(n, n_size, NULL); + if (!bn_n) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create BIGNUM for RSA n."); ++ return log_openssl_errors("Failed to create BIGNUM for RSA n"); + + _cleanup_(BN_freep) BIGNUM *bn_e = BN_bin2bn(e, e_size, NULL); + if (!bn_e) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create BIGNUM for RSA e."); ++ return log_openssl_errors("Failed to create BIGNUM for RSA e"); + + #if OPENSSL_VERSION_MAJOR >= 3 + if (EVP_PKEY_fromdata_init(ctx) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize EVP_PKEY_CTX."); ++ return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); + + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + if (!bld) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_N, bn_n)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set RSA OSSL_PKEY_PARAM_RSA_N."); ++ return log_openssl_errors("Failed to set RSA OSSL_PKEY_PARAM_RSA_N"); + + if (!OSSL_PARAM_BLD_push_BN(bld, OSSL_PKEY_PARAM_RSA_E, bn_e)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set RSA OSSL_PKEY_PARAM_RSA_E."); ++ return log_openssl_errors("Failed to set RSA OSSL_PKEY_PARAM_RSA_E"); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + if (!params) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to build RSA OSSL_PARAM."); ++ return log_openssl_errors("Failed to build RSA OSSL_PARAM"); + + if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to create RSA EVP_PKEY."); ++ return log_openssl_errors("Failed to create RSA EVP_PKEY"); + #else + _cleanup_(RSA_freep) RSA *rsa_key = RSA_new(); + if (!rsa_key) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new RSA"); + + if (!RSA_set0_key(rsa_key, bn_n, bn_e, NULL)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set RSA n/e."); ++ return log_openssl_errors("Failed to set RSA n/e"); + /* rsa_key owns these now, don't free */ + TAKE_PTR(bn_n); + TAKE_PTR(bn_e); + + pkey = EVP_PKEY_new(); + if (!pkey) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new EVP_PKEY"); + + if (!EVP_PKEY_assign_RSA(pkey, rsa_key)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to assign RSA key."); ++ return log_openssl_errors("Failed to assign RSA key"); + /* pkey owns this now, don't free */ + TAKE_PTR(rsa_key); + #endif +@@ -212,24 +238,23 @@ int rsa_pkey_to_n_e( + #if OPENSSL_VERSION_MAJOR >= 3 + _cleanup_(BN_freep) BIGNUM *bn_n = NULL; + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_N, &bn_n)) +- return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get RSA n."); ++ return log_openssl_errors("Failed to get RSA n"); + + _cleanup_(BN_freep) BIGNUM *bn_e = NULL; + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_RSA_E, &bn_e)) +- return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get RSA e."); ++ return log_openssl_errors("Failed to get RSA e"); + #else + const RSA *rsa = EVP_PKEY_get0_RSA((EVP_PKEY*) pkey); + if (!rsa) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), +- "Failed to get RSA key from public key."); ++ return log_openssl_errors("Failed to get RSA key from public key"); + + const BIGNUM *bn_n = RSA_get0_n(rsa); + if (!bn_n) +- return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get RSA n."); ++ return log_openssl_errors("Failed to get RSA n"); + + const BIGNUM *bn_e = RSA_get0_e(rsa); + if (!bn_e) +- return log_error_errno(SYNTHETIC_ERRNO(EIO), "Failed to get RSA e."); ++ return log_openssl_errors("Failed to get RSA e"); + #endif + + size_t n_size = BN_num_bytes(bn_n), e_size = BN_num_bytes(bn_e); +@@ -254,17 +279,17 @@ int rsa_pkey_new(size_t bits, EVP_PKEY **ret) { + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_RSA, NULL); + if (!ctx) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); + + if (EVP_PKEY_keygen_init(ctx) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize EVP_PKEY_CTX."); ++ return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); + + if (EVP_PKEY_CTX_set_rsa_keygen_bits(ctx, (int) bits) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set RSA bits to %zu.", bits); ++ return log_openssl_errors("Failed to set RSA bits to %zu", bits); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate ECC key."); ++ return log_openssl_errors("Failed to generate ECC key"); + + *ret = TAKE_PTR(pkey); + +@@ -286,70 +311,71 @@ int ecc_pkey_from_curve_x_y( + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + if (!ctx) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); + +- _cleanup_(BN_freep) BIGNUM *bn_x = BN_bin2bn(x, x_size, NULL), *bn_y = BN_bin2bn(y, y_size, NULL); +- if (!bn_x || !bn_y) +- return log_oom_debug(); ++ _cleanup_(BN_freep) BIGNUM *bn_x = BN_bin2bn(x, x_size, NULL); ++ if (!bn_x) ++ return log_openssl_errors("Failed to create BIGNUM x"); ++ ++ _cleanup_(BN_freep) BIGNUM *bn_y = BN_bin2bn(y, y_size, NULL); ++ if (!bn_y) ++ return log_openssl_errors("Failed to create BIGNUM y"); + + _cleanup_(EC_GROUP_freep) EC_GROUP *group = EC_GROUP_new_by_curve_name(curve_id); + if (!group) +- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), +- "ECC curve id %d not supported.", curve_id); ++ return log_openssl_errors("ECC curve id %d not supported", curve_id); + + _cleanup_(EC_POINT_freep) EC_POINT *point = EC_POINT_new(group); + if (!point) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new EC_POINT"); + + if (!EC_POINT_set_affine_coordinates(group, point, bn_x, bn_y, NULL)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ECC coordinates."); ++ return log_openssl_errors("Failed to set ECC coordinates"); + + #if OPENSSL_VERSION_MAJOR >= 3 + if (EVP_PKEY_fromdata_init(ctx) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to initialize EVP_PKEY_CTX."); ++ return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); + + _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); + if (!bld) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); + + if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_PKEY_PARAM_GROUP_NAME, (char*) OSSL_EC_curve_nid2name(curve_id), 0)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to add ECC OSSL_PKEY_PARAM_GROUP_NAME."); ++ return log_openssl_errors("Failed to add ECC OSSL_PKEY_PARAM_GROUP_NAME"); + + _cleanup_(OPENSSL_freep) void *pbuf = NULL; + size_t pbuf_len = 0; + pbuf_len = EC_POINT_point2buf(group, point, POINT_CONVERSION_UNCOMPRESSED, (unsigned char**) &pbuf, NULL); + if (pbuf_len == 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to convert ECC point to buffer."); ++ return log_openssl_errors("Failed to convert ECC point to buffer"); + + if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_PKEY_PARAM_PUB_KEY, pbuf, pbuf_len)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to add ECC OSSL_PKEY_PARAM_PUB_KEY."); ++ return log_openssl_errors("Failed to add ECC OSSL_PKEY_PARAM_PUB_KEY"); + + _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); + if (!params) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to build ECC OSSL_PARAM."); ++ return log_openssl_errors("Failed to build ECC OSSL_PARAM"); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + if (EVP_PKEY_fromdata(ctx, &pkey, EVP_PKEY_PUBLIC_KEY, params) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), +- "Failed to create ECC EVP_PKEY."); ++ return log_openssl_errors("Failed to create ECC EVP_PKEY"); + #else + _cleanup_(EC_KEY_freep) EC_KEY *eckey = EC_KEY_new(); + if (!eckey) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new EC_KEY"); + + if (!EC_KEY_set_group(eckey, group)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ECC group."); ++ return log_openssl_errors("Failed to set ECC group"); + + if (!EC_KEY_set_public_key(eckey, point)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ECC point."); ++ return log_openssl_errors("Failed to set ECC point"); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = EVP_PKEY_new(); + if (!pkey) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new EVP_PKEY"); + + if (!EVP_PKEY_assign_EC_KEY(pkey, eckey)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to assign ECC key."); ++ return log_openssl_errors("Failed to assign ECC key"); + /* pkey owns this now, don't free */ + TAKE_PTR(eckey); + #endif +@@ -375,48 +401,48 @@ int ecc_pkey_to_curve_x_y( + #if OPENSSL_VERSION_MAJOR >= 3 + size_t name_size; + if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, NULL, 0, &name_size)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC group name size."); ++ return log_openssl_errors("Failed to get ECC group name size"); + + _cleanup_free_ char *name = new(char, name_size + 1); + if (!name) + return log_oom_debug(); + + if (!EVP_PKEY_get_utf8_string_param(pkey, OSSL_PKEY_PARAM_GROUP_NAME, name, name_size + 1, NULL)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC group name."); ++ return log_openssl_errors("Failed to get ECC group name"); + + curve_id = OBJ_sn2nid(name); + if (curve_id == NID_undef) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC curve id."); ++ return log_openssl_errors("Failed to get ECC curve id"); + + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_X, &bn_x)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC point x."); ++ return log_openssl_errors("Failed to get ECC point x"); + + if (!EVP_PKEY_get_bn_param(pkey, OSSL_PKEY_PARAM_EC_PUB_Y, &bn_y)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC point y."); ++ return log_openssl_errors("Failed to get ECC point y"); + #else + const EC_KEY *eckey = EVP_PKEY_get0_EC_KEY((EVP_PKEY*) pkey); + if (!eckey) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get EC_KEY."); ++ return log_openssl_errors("Failed to get EC_KEY"); + + const EC_GROUP *group = EC_KEY_get0_group(eckey); + if (!group) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get EC_GROUP."); ++ return log_openssl_errors("Failed to get EC_GROUP"); + + curve_id = EC_GROUP_get_curve_name(group); + if (curve_id == NID_undef) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC curve id."); ++ return log_openssl_errors("Failed to get ECC curve id"); + + const EC_POINT *point = EC_KEY_get0_public_key(eckey); + if (!point) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get EC_POINT."); ++ return log_openssl_errors("Failed to get EC_POINT"); + + bn_x = BN_new(); + bn_y = BN_new(); + if (!bn_x || !bn_y) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new BIGNUM"); + + if (!EC_POINT_get_affine_coordinates(group, point, bn_x, bn_y, NULL)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to get ECC x/y."); ++ return log_openssl_errors("Failed to get ECC x/y."); + #endif + + size_t x_size = BN_num_bytes(bn_x), y_size = BN_num_bytes(bn_y); +@@ -447,17 +473,17 @@ int ecc_pkey_new(int curve_id, EVP_PKEY **ret) { + + _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new_id(EVP_PKEY_EC, NULL); + if (!ctx) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); + + if (EVP_PKEY_keygen_init(ctx) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to initialize EVP_PKEY_CTX."); ++ return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); + + if (EVP_PKEY_CTX_set_ec_paramgen_curve_nid(ctx, curve_id) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to set ECC curve %d.", curve_id); ++ return log_openssl_errors("Failed to set ECC curve %d", curve_id); + + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; + if (EVP_PKEY_keygen(ctx, &pkey) <= 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Failed to generate ECC key."); ++ return log_openssl_errors("Failed to generate ECC key"); + + *ret = TAKE_PTR(pkey); + +@@ -480,8 +506,7 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s + + sz = i2d_PublicKey(pk, NULL); + if (sz < 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to convert public key to DER format: %s", +- ERR_error_string(ERR_get_error(), NULL)); ++ return log_openssl_errors("Unable to convert public key to DER format"); + + dd = d = malloc(sz); + if (!d) +@@ -489,18 +514,17 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s + + lsz = i2d_PublicKey(pk, &dd); + if (lsz < 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to convert public key to DER format: %s", +- ERR_error_string(ERR_get_error(), NULL)); ++ return log_openssl_errors("Unable to convert public key to DER format"); + + m = EVP_MD_CTX_new(); + if (!m) +- return log_oom_debug(); ++ return log_openssl_errors("Failed to create new EVP_MD_CTX"); + + if (EVP_DigestInit_ex(m, md, NULL) != 1) +- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to initialize %s context.", EVP_MD_name(md)); ++ return log_openssl_errors("Failed to initialize %s context", EVP_MD_name(md)); + + if (EVP_DigestUpdate(m, d, lsz) != 1) +- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to run %s context.", EVP_MD_name(md)); ++ return log_openssl_errors("Failed to run %s context", EVP_MD_name(md)); + + msz = EVP_MD_size(md); + assert(msz > 0); +@@ -511,7 +535,7 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s + + umsz = msz; + if (EVP_DigestFinal_ex(m, h, &umsz) != 1) +- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Failed to finalize hash context."); ++ return log_openssl_errors("Failed to finalize hash context"); + + assert(umsz == (unsigned) msz); + +@@ -560,8 +584,7 @@ int x509_fingerprint(X509 *cert, uint8_t buffer[static SHA256_DIGEST_SIZE]) { + + dersz = i2d_X509(cert, &der); + if (dersz < 0) +- return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unable to convert PEM certificate to DER format: %s", +- ERR_error_string(ERR_get_error(), NULL)); ++ return log_openssl_errors("Unable to convert PEM certificate to DER format"); + + sha256_direct(der, dersz, buffer); + return 0; +diff --git a/src/test/test-openssl.c b/src/test/test-openssl.c +index 7672c8959d..c46ecdcda8 100644 +--- a/src/test/test-openssl.c ++++ b/src/test/test-openssl.c +@@ -94,4 +94,12 @@ TEST(ecc_pkey_curve_x_y) { + assert_se(memcmp_nn(y, y_len, y2, y2_size) == 0); + } + ++TEST(invalid) { ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ++ ++ DEFINE_HEX_PTR(key, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d466b7b"); ++ assert_se(openssl_pkey_from_pem(key, key_len, &pkey) == -EIO); ++ assert_se(pkey == NULL); ++} ++ + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0602-openssl-add-openssl_digest_size.patch b/SOURCES/0602-openssl-add-openssl_digest_size.patch new file mode 100644 index 0000000..4a8aa75 --- /dev/null +++ b/SOURCES/0602-openssl-add-openssl_digest_size.patch @@ -0,0 +1,147 @@ +From 50240c828da96724d76c14dd0e71b78bc2dce657 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 5 Jul 2023 12:59:47 -0400 +Subject: [PATCH] openssl: add openssl_digest_size() + +Add function to get digest hash size for provided digest name. + +(cherry picked from commit c52a003dc86bb91b2724a00449a50b26009fdfd0) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 31 +++++++++++++++++++++++ + src/shared/openssl-util.h | 3 +++ + src/test/test-openssl.c | 52 +++++++++++++++++++++++++++++++++++++++ + 3 files changed, 86 insertions(+) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index 3d3d8090f8..ecdb418402 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -87,6 +87,37 @@ int openssl_hash(const EVP_MD *alg, + return 0; + } + ++/* Returns the number of bytes generated by the specified digest algorithm. This can be used only for ++ * fixed-size algorithms, e.g. md5, sha1, sha256, etc. Do not use this for variable-sized digest algorithms, ++ * e.g. shake128. Returns 0 on success, -EOPNOTSUPP if the algorithm is not supported, or < 0 for any other ++ * error. */ ++int openssl_digest_size(const char *digest_alg, size_t *ret_digest_size) { ++ assert(digest_alg); ++ assert(ret_digest_size); ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL); ++#else ++ const EVP_MD *md = EVP_get_digestbyname(digest_alg); ++#endif ++ if (!md) ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "Digest algorithm '%s' not supported.", digest_alg); ++ ++ size_t digest_size; ++#if OPENSSL_VERSION_MAJOR >= 3 ++ digest_size = EVP_MD_get_size(md); ++#else ++ digest_size = EVP_MD_size(md); ++#endif ++ if (digest_size == 0) ++ return log_openssl_errors("Failed to get Digest size"); ++ ++ *ret_digest_size = digest_size; ++ ++ return 0; ++} ++ + int rsa_encrypt_bytes( + EVP_PKEY *pkey, + const void *decrypted_key, +diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h +index 90158f589b..309dc16805 100644 +--- a/src/shared/openssl-util.h ++++ b/src/shared/openssl-util.h +@@ -39,6 +39,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL); + #if OPENSSL_VERSION_MAJOR >= 3 ++DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD*, EVP_MD_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM*, OSSL_PARAM_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM_BLD*, OSSL_PARAM_BLD_free, NULL); + #else +@@ -57,6 +58,8 @@ int openssl_pkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret); + + int openssl_hash(const EVP_MD *alg, const void *msg, size_t msg_len, uint8_t *ret_hash, size_t *ret_hash_len); + ++int openssl_digest_size(const char *digest_alg, size_t *ret_digest_size); ++ + int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); + + int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size); +diff --git a/src/test/test-openssl.c b/src/test/test-openssl.c +index c46ecdcda8..a8a2b534a4 100644 +--- a/src/test/test-openssl.c ++++ b/src/test/test-openssl.c +@@ -2,6 +2,7 @@ + + #include "hexdecoct.h" + #include "openssl-util.h" ++#include "string-util.h" + #include "tests.h" + + TEST(openssl_pkey_from_pem) { +@@ -102,4 +103,55 @@ TEST(invalid) { + assert_se(pkey == NULL); + } + ++static const struct { ++ const char *alg; ++ size_t size; ++} digest_size_table[] = { ++ /* SHA1 "family" */ ++ { "sha1", 20, }, ++#if OPENSSL_VERSION_MAJOR >= 3 ++ { "sha-1", 20, }, ++#endif ++ /* SHA2 family */ ++ { "sha224", 28, }, ++ { "sha256", 32, }, ++ { "sha384", 48, }, ++ { "sha512", 64, }, ++#if OPENSSL_VERSION_MAJOR >= 3 ++ { "sha-224", 28, }, ++ { "sha2-224", 28, }, ++ { "sha-256", 32, }, ++ { "sha2-256", 32, }, ++ { "sha-384", 48, }, ++ { "sha2-384", 48, }, ++ { "sha-512", 64, }, ++ { "sha2-512", 64, }, ++#endif ++ /* SHA3 family */ ++ { "sha3-224", 28, }, ++ { "sha3-256", 32, }, ++ { "sha3-384", 48, }, ++ { "sha3-512", 64, }, ++ /* SM3 family */ ++ { "sm3", 32, }, ++ /* MD5 family */ ++ { "md5", 16, }, ++}; ++ ++TEST(digest_size) { ++ size_t size; ++ ++ FOREACH_ARRAY(t, digest_size_table, ELEMENTSOF(digest_size_table)) { ++ assert(openssl_digest_size(t->alg, &size) >= 0); ++ assert_se(size == t->size); ++ ++ _cleanup_free_ char *uppercase_alg = strdup(t->alg); ++ assert_se(uppercase_alg); ++ assert_se(openssl_digest_size(ascii_strupper(uppercase_alg), &size) >= 0); ++ assert_se(size == t->size); ++ } ++ ++ assert_se(openssl_digest_size("invalid.alg", &size) == -EOPNOTSUPP); ++} ++ + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0603-openssl-add-openssl_digest_many.patch b/SOURCES/0603-openssl-add-openssl_digest_many.patch new file mode 100644 index 0000000..f29de72 --- /dev/null +++ b/SOURCES/0603-openssl-add-openssl_digest_many.patch @@ -0,0 +1,179 @@ +From fe351e0c111c98e1ad317df05b87705a233c46d6 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 5 Jul 2023 12:28:39 -0400 +Subject: [PATCH] openssl: add openssl_digest_many() + +Add function to perform openssl digest calculation on multiple buffers. + +(cherry picked from commit bed4831ce227a59d40b3712a3b1deee9fe0440f5) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 58 +++++++++++++++++++++++++++++++++++++ + src/shared/openssl-util.h | 7 +++++ + src/test/test-openssl.c | 60 +++++++++++++++++++++++++++++++++++++++ + 3 files changed, 125 insertions(+) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index ecdb418402..0aef979e8c 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -118,6 +118,64 @@ int openssl_digest_size(const char *digest_alg, size_t *ret_digest_size) { + return 0; + } + ++/* Calculate the digest hash value for the provided data, using the specified digest algorithm. Returns 0 on ++ * success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any other error. */ ++int openssl_digest_many( ++ const char *digest_alg, ++ const struct iovec data[], ++ size_t n_data, ++ void **ret_digest, ++ size_t *ret_digest_size) { ++ ++ int r; ++ ++ assert(digest_alg); ++ assert(data || n_data == 0); ++ assert(ret_digest); ++ /* ret_digest_size is optional, as caller may already know the digest size */ ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL); ++#else ++ const EVP_MD *md = EVP_get_digestbyname(digest_alg); ++#endif ++ if (!md) ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "Digest algorithm '%s' not supported.", digest_alg); ++ ++ _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = EVP_MD_CTX_new(); ++ if (!ctx) ++ return log_openssl_errors("Failed to create new EVP_MD_CTX"); ++ ++ if (!EVP_DigestInit_ex(ctx, md, NULL)) ++ return log_openssl_errors("Failed to initializate EVP_MD_CTX"); ++ ++ for (size_t i = 0; i < n_data; i++) ++ if (!EVP_DigestUpdate(ctx, data[i].iov_base, data[i].iov_len)) ++ return log_openssl_errors("Failed to update Digest"); ++ ++ size_t digest_size; ++ r = openssl_digest_size(digest_alg, &digest_size); ++ if (r < 0) ++ return r; ++ ++ _cleanup_free_ void *buf = malloc(digest_size); ++ if (!buf) ++ return log_oom_debug(); ++ ++ unsigned int size; ++ if (!EVP_DigestFinal_ex(ctx, buf, &size)) ++ return log_openssl_errors("Failed to finalize Digest"); ++ ++ assert(size == digest_size); ++ ++ *ret_digest = TAKE_PTR(buf); ++ if (ret_digest_size) ++ *ret_digest_size = size; ++ ++ return 0; ++} ++ + int rsa_encrypt_bytes( + EVP_PKEY *pkey, + const void *decrypted_key, +diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h +index 309dc16805..f1c84c102e 100644 +--- a/src/shared/openssl-util.h ++++ b/src/shared/openssl-util.h +@@ -1,6 +1,7 @@ + /* SPDX-License-Identifier: LGPL-2.1-or-later */ + #pragma once + ++#include "io-util.h" + #include "macro.h" + #include "sha256.h" + +@@ -60,6 +61,12 @@ int openssl_hash(const EVP_MD *alg, const void *msg, size_t msg_len, uint8_t *re + + int openssl_digest_size(const char *digest_alg, size_t *ret_digest_size); + ++int openssl_digest_many(const char *digest_alg, const struct iovec data[], size_t n_data, void **ret_digest, size_t *ret_digest_size); ++ ++static inline int openssl_digest(const char *digest_alg, const void *buf, size_t len, void **ret_digest, size_t *ret_digest_size) { ++ return openssl_digest_many(digest_alg, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); ++} ++ + int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); + + int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size); +diff --git a/src/test/test-openssl.c b/src/test/test-openssl.c +index a8a2b534a4..35ac980d25 100644 +--- a/src/test/test-openssl.c ++++ b/src/test/test-openssl.c +@@ -154,4 +154,64 @@ TEST(digest_size) { + assert_se(openssl_digest_size("invalid.alg", &size) == -EOPNOTSUPP); + } + ++static void verify_digest(const char *digest_alg, const struct iovec *data, size_t n_data, const char *expect) { ++ _cleanup_free_ void *digest = NULL; ++ size_t digest_size; ++ int r; ++ ++ r = openssl_digest_many(digest_alg, data, n_data, &digest, &digest_size); ++ if (r == -EOPNOTSUPP) ++ return; ++ assert_se(r >= 0); ++ ++ DEFINE_HEX_PTR(e, expect); ++ assert_se(memcmp_nn(e, e_len, digest, digest_size) == 0); ++} ++ ++#define _DEFINE_DIGEST_TEST(uniq, alg, expect, ...) \ ++ const struct iovec UNIQ_T(i, uniq)[] = { __VA_ARGS__ }; \ ++ verify_digest(alg, \ ++ UNIQ_T(i, uniq), \ ++ ELEMENTSOF(UNIQ_T(i, uniq)), \ ++ expect); ++#define DEFINE_DIGEST_TEST(alg, expect, ...) _DEFINE_DIGEST_TEST(UNIQ, alg, expect, __VA_ARGS__) ++#define DEFINE_SHA1_TEST(expect, ...) DEFINE_DIGEST_TEST("SHA1", expect, __VA_ARGS__) ++#define DEFINE_SHA256_TEST(expect, ...) DEFINE_DIGEST_TEST("SHA256", expect, __VA_ARGS__) ++#define DEFINE_SHA384_TEST(expect, ...) DEFINE_DIGEST_TEST("SHA384", expect, __VA_ARGS__) ++#define DEFINE_SHA512_TEST(expect, ...) DEFINE_DIGEST_TEST("SHA512", expect, __VA_ARGS__) ++ ++TEST(digest_many) { ++ const struct iovec test = IOVEC_MAKE_STRING("test"); ++ ++ /* Empty digests */ ++ DEFINE_SHA1_TEST("da39a3ee5e6b4b0d3255bfef95601890afd80709"); ++ DEFINE_SHA256_TEST("e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855"); ++ DEFINE_SHA384_TEST("38b060a751ac96384cd9327eb1b1e36a21fdb71114be07434c0cc7bf63f6e1da274edebfe76f65fbd51ad2f14898b95b"); ++ DEFINE_SHA512_TEST("cf83e1357eefb8bdf1542850d66d8007d620e4050b5715dc83f4a921d36ce9ce47d0d13c5d85f2b0ff8318d2877eec2f63b931bd47417a81a538327af927da3e"); ++ ++ DEFINE_SHA1_TEST("a94a8fe5ccb19ba61c4c0873d391e987982fbbd3", test); ++ DEFINE_SHA256_TEST("9f86d081884c7d659a2feaa0c55ad015a3bf4f1b2b0b822cd15d6c15b0f00a08", test); ++ DEFINE_SHA384_TEST("768412320f7b0aa5812fce428dc4706b3cae50e02a64caa16a782249bfe8efc4b7ef1ccb126255d196047dfedf17a0a9", test); ++ DEFINE_SHA512_TEST("ee26b0dd4af7e749aa1a8ee3c10ae9923f618980772e473f8819a5d4940e0db27ac185f8a0e1d5f84f88bc887fd67b143732c304cc5fa9ad8e6f57f50028a8ff", test); ++ ++ DEFINE_HEX_PTR(h1, "e9ff2b6dfbc03b8dd0471a0f23840334e3ef51c64a325945524563c0375284a092751eca8d084fae22f74a104559a0ee8339d1845538481e674e6d31d4f63089"); ++ DEFINE_HEX_PTR(h2, "5b6e809933a1b8d5a4a6bb62e20b36ae82d9408141e7479d0aa067273bd2d04007fb1977bad549d54330a49ed98f82b495ba"); ++ DEFINE_HEX_PTR(h3, "d2aeef94d7ba2a"); ++ DEFINE_HEX_PTR(h4, "1557db45ded3e38c79b5bb25c83ade42fa7d13047ef1b9a0b21a3c2ab2d4eee5c75e2927ce643163addbda65331035850a436c0acffc723f419e1d1cbf04c9064e6d850580c0732a12600f9feb"); ++ ++ const struct iovec i1 = IOVEC_MAKE(h1, h1_len); ++ const struct iovec i2 = IOVEC_MAKE(h2, h2_len); ++ const struct iovec i3 = IOVEC_MAKE(h3, h3_len); ++ const struct iovec i4 = IOVEC_MAKE(h4, h4_len); ++ ++ DEFINE_SHA1_TEST("8e7c659a6331508b06adf98b430759dafb92fc43", i1, i2, i3, i4); ++ DEFINE_SHA256_TEST("4d6be38798786a5500651c1a02d96aa010e9d7b2bece1695294cd396d456cde8", i1, i2, i3, i4); ++ DEFINE_SHA384_TEST("82e6ec14f8d90f1ae1fd4fb7f415ea6fdb674515b13092e3e548a8d37a8faed30cda8ea613ec2a015a51bc578dacc995", i1, i2, i3, i4); ++ DEFINE_SHA512_TEST("21fe5beb15927257a9143ff59010e51d4c65c7c5237b0cd9a8db3c3fabe429be3a0759f9ace3cdd70f6ea543f998bec9bc3308833d70aa1bd380364de872a62c", i1, i2, i3, i4); ++ ++ DEFINE_SHA256_TEST("0e0ed67d6717dc08dd6f472f6c35107a92b8c2695dcba344b884436f97a9eb4d", i1, i1, i1, i4); ++ ++ DEFINE_SHA256_TEST("8fe8b8d1899c44bfb82e1edc4ff92642db5b2cb25c4210ea06c3846c757525a8", i1, i1, i1, i4, i4, i4, i4, i3, i3, i2); ++} ++ + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0604-openssl-replace-openssl_hash-with-openssl_digest.patch b/SOURCES/0604-openssl-replace-openssl_hash-with-openssl_digest.patch new file mode 100644 index 0000000..2b9b0c8 --- /dev/null +++ b/SOURCES/0604-openssl-replace-openssl_hash-with-openssl_digest.patch @@ -0,0 +1,166 @@ +From aa2a2c0bd0d89624e1a03c1f602f026f13ac4073 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 5 Jul 2023 16:53:01 -0400 +Subject: [PATCH] openssl: replace openssl_hash() with openssl_digest() + +The openssl_hash() function was used only by string_hashnum(); change it to use +openssl_digest() instead. + +(cherry picked from commit 11f7bc5e9c6f3bffcefd08076e493f3159e23bc1) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 53 ++++++--------------------------------- + src/shared/openssl-util.h | 8 +++--- + src/test/test-cryptolib.c | 8 +++--- + 3 files changed, 15 insertions(+), 54 deletions(-) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index 0aef979e8c..7a69db4195 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -50,43 +50,6 @@ int openssl_pkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret) { + return 0; + } + +-int openssl_hash(const EVP_MD *alg, +- const void *msg, +- size_t msg_len, +- uint8_t *ret_hash, +- size_t *ret_hash_len) { +- +- _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX *ctx = NULL; +- unsigned len; +- int r; +- +- ctx = EVP_MD_CTX_new(); +- if (!ctx) +- /* This function just calls OPENSSL_zalloc, so failure +- * here is almost certainly a failed allocation. */ +- return -ENOMEM; +- +- /* The documentation claims EVP_DigestInit behaves just like +- * EVP_DigestInit_ex if passed NULL, except it also calls +- * EVP_MD_CTX_reset, which deinitializes the context. */ +- r = EVP_DigestInit_ex(ctx, alg, NULL); +- if (r == 0) +- return -EIO; +- +- r = EVP_DigestUpdate(ctx, msg, msg_len); +- if (r == 0) +- return -EIO; +- +- r = EVP_DigestFinal_ex(ctx, ret_hash, &len); +- if (r == 0) +- return -EIO; +- +- if (ret_hash_len) +- *ret_hash_len = len; +- +- return 0; +-} +- + /* Returns the number of bytes generated by the specified digest algorithm. This can be used only for + * fixed-size algorithms, e.g. md5, sha1, sha256, etc. Do not use this for variable-sized digest algorithms, + * e.g. shake128. Returns 0 on success, -EOPNOTSUPP if the algorithm is not supported, or < 0 for any other +@@ -638,18 +601,19 @@ int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_s + int string_hashsum( + const char *s, + size_t len, +- const EVP_MD *md_algorithm, ++ const char *md_algorithm, + char **ret) { + +- uint8_t hash[EVP_MAX_MD_SIZE]; ++ _cleanup_free_ void *hash = NULL; + size_t hash_size; +- char *enc; ++ _cleanup_free_ char *enc; + int r; + +- hash_size = EVP_MD_size(md_algorithm); +- assert(hash_size > 0); ++ assert(s || len == 0); ++ assert(md_algorithm); ++ assert(ret); + +- r = openssl_hash(md_algorithm, s, len, hash, NULL); ++ r = openssl_digest(md_algorithm, s, len, &hash, &hash_size); + if (r < 0) + return r; + +@@ -657,9 +621,8 @@ int string_hashsum( + if (!enc) + return -ENOMEM; + +- *ret = enc; ++ *ret = TAKE_PTR(enc); + return 0; +- + } + # endif + #endif +diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h +index f1c84c102e..a37c6e3a50 100644 +--- a/src/shared/openssl-util.h ++++ b/src/shared/openssl-util.h +@@ -57,8 +57,6 @@ static inline void sk_X509_free_allp(STACK_OF(X509) **sk) { + + int openssl_pkey_from_pem(const void *pem, size_t pem_size, EVP_PKEY **ret); + +-int openssl_hash(const EVP_MD *alg, const void *msg, size_t msg_len, uint8_t *ret_hash, size_t *ret_hash_len); +- + int openssl_digest_size(const char *digest_alg, size_t *ret_digest_size); + + int openssl_digest_many(const char *digest_alg, const struct iovec data[], size_t n_data, void **ret_digest, size_t *ret_digest_size); +@@ -128,13 +126,13 @@ typedef gcry_md_hd_t hash_context_t; + #endif + + #if PREFER_OPENSSL +-int string_hashsum(const char *s, size_t len, hash_algorithm_t md_algorithm, char **ret); ++int string_hashsum(const char *s, size_t len, const char *md_algorithm, char **ret); + + static inline int string_hashsum_sha224(const char *s, size_t len, char **ret) { +- return string_hashsum(s, len, EVP_sha224(), ret); ++ return string_hashsum(s, len, "SHA224", ret); + } + + static inline int string_hashsum_sha256(const char *s, size_t len, char **ret) { +- return string_hashsum(s, len, EVP_sha256(), ret); ++ return string_hashsum(s, len, "SHA256", ret); + } + #endif +diff --git a/src/test/test-cryptolib.c b/src/test/test-cryptolib.c +index ef39bda653..6202a5d6d4 100644 +--- a/src/test/test-cryptolib.c ++++ b/src/test/test-cryptolib.c +@@ -11,25 +11,25 @@ TEST(string_hashsum) { + _cleanup_free_ char *out1 = NULL, *out2 = NULL, *out3 = NULL, *out4 = NULL; + + assert_se(string_hashsum("asdf", 4, +- OPENSSL_OR_GCRYPT(EVP_sha224(), GCRY_MD_SHA224), ++ OPENSSL_OR_GCRYPT("SHA224", GCRY_MD_SHA224), + &out1) == 0); + /* echo -n 'asdf' | sha224sum - */ + assert_se(streq(out1, "7872a74bcbf298a1e77d507cd95d4f8d96131cbbd4cdfc571e776c8a")); + + assert_se(string_hashsum("asdf", 4, +- OPENSSL_OR_GCRYPT(EVP_sha256(), GCRY_MD_SHA256), ++ OPENSSL_OR_GCRYPT("SHA256", GCRY_MD_SHA256), + &out2) == 0); + /* echo -n 'asdf' | sha256sum - */ + assert_se(streq(out2, "f0e4c2f76c58916ec258f246851bea091d14d4247a2fc3e18694461b1816e13b")); + + assert_se(string_hashsum("", 0, +- OPENSSL_OR_GCRYPT(EVP_sha224(), GCRY_MD_SHA224), ++ OPENSSL_OR_GCRYPT("SHA224", GCRY_MD_SHA224), + &out3) == 0); + /* echo -n '' | sha224sum - */ + assert_se(streq(out3, "d14a028c2a3a2bc9476102bb288234c415a2b01f828ea62ac5b3e42f")); + + assert_se(string_hashsum("", 0, +- OPENSSL_OR_GCRYPT(EVP_sha256(), GCRY_MD_SHA256), ++ OPENSSL_OR_GCRYPT("SHA256", GCRY_MD_SHA256), + &out4) == 0); + /* echo -n '' | sha256sum - */ + assert_se(streq(out4, "e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855")); diff --git a/SOURCES/0605-openssl-add-openssl_hmac_many.patch b/SOURCES/0605-openssl-add-openssl_hmac_many.patch new file mode 100644 index 0000000..a235aef --- /dev/null +++ b/SOURCES/0605-openssl-add-openssl_hmac_many.patch @@ -0,0 +1,285 @@ +From 95440b3e8b6729252acd5e0940aed3dd42b96b0e Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 26 Jun 2023 17:40:18 -0400 +Subject: [PATCH] openssl: add openssl_hmac_many() + +Add function to perform HMAC on multiple buffers. + +Also update test-openssl with associated testing, and replace some memcmp() +with memcmp_nn(). + +(cherry picked from commit a95e8fa2a39cf8f2992fab9abc31b3df95c1ca00) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 97 ++++++++++++++++++++++++++++++++++++++ + src/shared/openssl-util.h | 9 ++++ + src/test/test-openssl.c | 99 +++++++++++++++++++++++++++++++++++---- + 3 files changed, 197 insertions(+), 8 deletions(-) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index 7a69db4195..10664e362c 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -139,6 +139,103 @@ int openssl_digest_many( + return 0; + } + ++/* Calculate the HMAC digest hash value for the provided data, using the provided key and specified digest ++ * algorithm. Returns 0 on success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any ++ * other error. */ ++int openssl_hmac_many( ++ const char *digest_alg, ++ const void *key, ++ size_t key_size, ++ const struct iovec data[], ++ size_t n_data, ++ void **ret_digest, ++ size_t *ret_digest_size) { ++ ++ assert(digest_alg); ++ assert(key); ++ assert(data || n_data == 0); ++ assert(ret_digest); ++ /* ret_digest_size is optional, as caller may already know the digest size */ ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL); ++#else ++ const EVP_MD *md = EVP_get_digestbyname(digest_alg); ++#endif ++ if (!md) ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "Digest algorithm '%s' not supported.", digest_alg); ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ _cleanup_(EVP_MAC_freep) EVP_MAC *mac = EVP_MAC_fetch(NULL, "HMAC", NULL); ++ if (!mac) ++ return log_openssl_errors("Failed to create new EVP_MAC"); ++ ++ _cleanup_(EVP_MAC_CTX_freep) EVP_MAC_CTX *ctx = EVP_MAC_CTX_new(mac); ++ if (!ctx) ++ return log_openssl_errors("Failed to create new EVP_MAC_CTX"); ++ ++ _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); ++ if (!bld) ++ return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); ++ ++ if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_MAC_PARAM_DIGEST, (char*) digest_alg, 0)) ++ return log_openssl_errors("Failed to set HMAC OSSL_MAC_PARAM_DIGEST"); ++ ++ _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); ++ if (!params) ++ return log_openssl_errors("Failed to build HMAC OSSL_PARAM"); ++ ++ if (!EVP_MAC_init(ctx, key, key_size, params)) ++ return log_openssl_errors("Failed to initializate EVP_MAC_CTX"); ++#else ++ _cleanup_(HMAC_CTX_freep) HMAC_CTX *ctx = HMAC_CTX_new(); ++ if (!ctx) ++ return log_openssl_errors("Failed to create new HMAC_CTX"); ++ ++ if (!HMAC_Init_ex(ctx, key, key_size, md, NULL)) ++ return log_openssl_errors("Failed to initialize HMAC_CTX"); ++#endif ++ ++ for (size_t i = 0; i < n_data; i++) ++#if OPENSSL_VERSION_MAJOR >= 3 ++ if (!EVP_MAC_update(ctx, data[i].iov_base, data[i].iov_len)) ++#else ++ if (!HMAC_Update(ctx, data[i].iov_base, data[i].iov_len)) ++#endif ++ return log_openssl_errors("Failed to update HMAC"); ++ ++ size_t digest_size; ++#if OPENSSL_VERSION_MAJOR >= 3 ++ digest_size = EVP_MAC_CTX_get_mac_size(ctx); ++#else ++ digest_size = HMAC_size(ctx); ++#endif ++ if (digest_size == 0) ++ return log_openssl_errors("Failed to get HMAC digest size"); ++ ++ _cleanup_free_ void *buf = malloc(digest_size); ++ if (!buf) ++ return log_oom_debug(); ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ size_t size; ++ if (!EVP_MAC_final(ctx, buf, &size, digest_size)) ++#else ++ unsigned int size; ++ if (!HMAC_Final(ctx, buf, &size)) ++#endif ++ return log_openssl_errors("Failed to finalize HMAC"); ++ ++ assert(size == digest_size); ++ ++ *ret_digest = TAKE_PTR(buf); ++ if (ret_digest_size) ++ *ret_digest_size = size; ++ ++ return 0; ++} ++ + int rsa_encrypt_bytes( + EVP_PKEY *pkey, + const void *decrypted_key, +diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h +index a37c6e3a50..8079946ab5 100644 +--- a/src/shared/openssl-util.h ++++ b/src/shared/openssl-util.h +@@ -40,11 +40,14 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL); + #if OPENSSL_VERSION_MAJOR >= 3 ++DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC*, EVP_MAC_free, NULL); ++DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC_CTX*, EVP_MAC_CTX_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD*, EVP_MD_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM*, OSSL_PARAM_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(OSSL_PARAM_BLD*, OSSL_PARAM_BLD_free, NULL); + #else + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EC_KEY*, EC_KEY_free, NULL); ++DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(HMAC_CTX*, HMAC_CTX_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(RSA*, RSA_free, NULL); + #endif + +@@ -65,6 +68,12 @@ static inline int openssl_digest(const char *digest_alg, const void *buf, size_t + return openssl_digest_many(digest_alg, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); + } + ++int openssl_hmac_many(const char *digest_alg, const void *key, size_t key_size, const struct iovec data[], size_t n_data, void **ret_digest, size_t *ret_digest_size); ++ ++static inline int openssl_hmac(const char *digest_alg, const void *key, size_t key_size, const void *buf, size_t len, void **ret_digest, size_t *ret_digest_size) { ++ return openssl_hmac_many(digest_alg, key, key_size, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); ++} ++ + int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); + + int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size); +diff --git a/src/test/test-openssl.c b/src/test/test-openssl.c +index 35ac980d25..676438f76d 100644 +--- a/src/test/test-openssl.c ++++ b/src/test/test-openssl.c +@@ -17,12 +17,10 @@ TEST(openssl_pkey_from_pem) { + assert_se(curve_id == NID_X9_62_prime256v1); + + DEFINE_HEX_PTR(expected_x, "ae39c4b812ec225f6b869870caf5cd3e18f88c19cf0d79f22742bd532acd81de"); +- assert_se(x_len == expected_x_len); +- assert_se(memcmp(x, expected_x, x_len) == 0); ++ assert_se(memcmp_nn(x, x_len, expected_x, expected_x_len) == 0); + + DEFINE_HEX_PTR(expected_y, "92e40e764fea12bed9028fa66b9788571b7c004145e9a01952fad1eab51a8be5"); +- assert_se(y_len == expected_y_len); +- assert_se(memcmp(y, expected_y, y_len) == 0); ++ assert_se(memcmp_nn(y, y_len, expected_y, expected_y_len) == 0); + + DEFINE_HEX_PTR(key_rsa, "2d2d2d2d2d424547494e205055424c4943204b45592d2d2d2d2d0a4d494942496a414e42676b71686b6947397730424151454641414f43415138414d49494243674b4341514541795639434950652f505852337a436f63787045300a6a575262546c3568585844436b472f584b79374b6d2f4439584942334b734f5a31436a5937375571372f674359363170697838697552756a73413464503165380a593445336c68556d374a332b6473766b626f4b64553243626d52494c2f6675627771694c4d587a41673342575278747234547545443533527a373634554650640a307a70304b68775231496230444c67772f344e67566f314146763378784b4d6478774d45683567676b73733038326332706c354a504e32587677426f744e6b4d0a5471526c745a4a35355244436170696e7153334577376675646c4e735851357746766c7432377a7637344b585165616d704c59433037584f6761304c676c536b0a79754774586b6a50542f735542544a705374615769674d5a6f714b7479563463515a58436b4a52684459614c47587673504233687a766d5671636e6b47654e540a65774944415141420a2d2d2d2d2d454e44205055424c4943204b45592d2d2d2d2d0a"); + _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey_rsa = NULL; +@@ -33,12 +31,10 @@ TEST(openssl_pkey_from_pem) { + assert_se(rsa_pkey_to_n_e(pkey_rsa, &n, &n_len, &e, &e_len) >= 0); + + DEFINE_HEX_PTR(expected_n, "c95f4220f7bf3d7477cc2a1cc691348d645b4e5e615d70c2906fd72b2eca9bf0fd5c80772ac399d428d8efb52aeff80263ad698b1f22b91ba3b00e1d3f57bc638137961526ec9dfe76cbe46e829d53609b99120bfdfb9bc2a88b317cc0837056471b6be13b840f9dd1cfbeb85053ddd33a742a1c11d486f40cb830ff8360568d4016fdf1c4a31dc7030487982092cb34f36736a65e493cdd97bf0068b4d90c4ea465b59279e510c26a98a7a92dc4c3b7ee76536c5d0e7016f96ddbbcefef829741e6a6a4b602d3b5ce81ad0b8254a4cae1ad5e48cf4ffb140532694ad6968a0319a2a2adc95e1c4195c29094610d868b197bec3c1de1cef995a9c9e419e3537b"); +- assert_se(n_len == expected_n_len); +- assert_se(memcmp(n, expected_n, n_len) == 0); ++ assert_se(memcmp_nn(n, n_len, expected_n, expected_n_len) == 0); + + DEFINE_HEX_PTR(expected_e, "010001"); +- assert_se(e_len == expected_e_len); +- assert_se(memcmp(e, expected_e, e_len) == 0); ++ assert_se(memcmp_nn(e, e_len, expected_e, expected_e_len) == 0); + } + + TEST(rsa_pkey_n_e) { +@@ -214,4 +210,91 @@ TEST(digest_many) { + DEFINE_SHA256_TEST("8fe8b8d1899c44bfb82e1edc4ff92642db5b2cb25c4210ea06c3846c757525a8", i1, i1, i1, i4, i4, i4, i4, i3, i3, i2); + } + ++static void verify_hmac( ++ const char *digest_alg, ++ const char *key, ++ const struct iovec *data, ++ size_t n_data, ++ const char *expect) { ++ ++ DEFINE_HEX_PTR(k, key); ++ DEFINE_HEX_PTR(e, expect); ++ _cleanup_free_ void *digest = NULL; ++ size_t digest_size; ++ ++ if (n_data == 0) { ++ assert_se(openssl_hmac(digest_alg, k, k_len, NULL, 0, &digest, &digest_size) == 0); ++ assert_se(memcmp_nn(e, e_len, digest, digest_size) == 0); ++ digest = mfree(digest); ++ } else if(n_data == 1) { ++ assert_se(openssl_hmac(digest_alg, k, k_len, data[0].iov_base, data[0].iov_len, &digest, &digest_size) == 0); ++ assert_se(memcmp_nn(e, e_len, digest, digest_size) == 0); ++ digest = mfree(digest); ++ } ++ ++ assert_se(openssl_hmac_many(digest_alg, k, k_len, data, n_data, &digest, &digest_size) == 0); ++ assert_se(memcmp_nn(e, e_len, digest, digest_size) == 0); ++} ++ ++#define _DEFINE_HMAC_TEST(uniq, alg, key, expect, ...) \ ++ const struct iovec UNIQ_T(i, uniq)[] = { __VA_ARGS__ }; \ ++ verify_hmac(alg, \ ++ key, \ ++ UNIQ_T(i, uniq), \ ++ ELEMENTSOF(UNIQ_T(i, uniq)), \ ++ expect); ++#define DEFINE_HMAC_TEST(alg, key, expect, ...) _DEFINE_HMAC_TEST(UNIQ, alg, key, expect, __VA_ARGS__) ++#define DEFINE_HMAC_SHA1_TEST(key, expect, ...) DEFINE_HMAC_TEST("SHA1", key, expect, __VA_ARGS__) ++#define DEFINE_HMAC_SHA256_TEST(key, expect, ...) DEFINE_HMAC_TEST("SHA256", key, expect, __VA_ARGS__) ++#define DEFINE_HMAC_SHA384_TEST(key, expect, ...) DEFINE_HMAC_TEST("SHA384", key, expect, __VA_ARGS__) ++#define DEFINE_HMAC_SHA512_TEST(key, expect, ...) DEFINE_HMAC_TEST("SHA512", key, expect, __VA_ARGS__) ++ ++TEST(hmac_many) { ++ const char *key1 = "760eb6845073862c1914c6d188bf8214", ++ *key2 = "0628d1a5f83fce99779e12e2336d87046d42d74b755f00d9f72350668860fd00", ++ *key3 = "b61158912b76348c54f104629924be4178b8a9c9459c3a6e9daa1885445a61fccc1aa0f749c31f3ade4e227f64dd0e86a94b25c2e181f044af22d0a8c07074c3"; ++ const struct iovec test = IOVEC_MAKE_STRING("test"); ++ ++ /* Empty digests */ ++ DEFINE_HMAC_SHA1_TEST(key1, "EB9725FC9A99A652C3171E0863984AC42461F88B"); ++ DEFINE_HMAC_SHA256_TEST(key1, "82A15D4DD5F583CF8F06D3E447DF0FDFF95A24E29229934B48BD0A5B4E0ADC85"); ++ DEFINE_HMAC_SHA384_TEST(key1, "C60F15C4E18736750D91095ADA148C4179825A487CCA3AE047A2FB94F85A5587AB6AF57678AA79715FEF848129C108C3"); ++ DEFINE_HMAC_SHA512_TEST(key1, "2B10DC9BFC0349400F8965482EA149C1C51C865BB7B16097623F41C14CF6C8A678724BFAE0CE842EED899C12CC17B5D8C4287F72BE788532FE7CF0BE2EBCD447"); ++ ++ DEFINE_HMAC_SHA1_TEST(key2, "F9AA74F129681E91807EB264EA6E1B5C5F9B4CFD"); ++ DEFINE_HMAC_SHA256_TEST(key2, "B4ADEBF8B3044A5B0668B742C0A49B61D8380F89938C84794C92567F5A33CC7D"); ++ DEFINE_HMAC_SHA384_TEST(key2, "E5EACAB7A13CF5BE60FA228D771E183CD6E57536BB9EAFC34A6BB52B1B1324BD6FB8A1713F91EC040790AE97F5672D53"); ++ DEFINE_HMAC_SHA512_TEST(key2, "75A597D83A6270FC3204DE741E76DEFCF42D3E1812C71E41EEA8C0F23C07315822E83BE8B54705CB00FEF4CE1BAF80E3975414925C83BF3719CEBC27DD133F7D"); ++ ++ DEFINE_HMAC_SHA1_TEST(key3, "4B8EACB3C3935ACC8C58995C89F16020FC993569"); ++ DEFINE_HMAC_SHA256_TEST(key3, "520E8C0323A1994D58EF5456611BCB6CD701399B24F8FBA0B5A3CD3186780E8E"); ++ DEFINE_HMAC_SHA384_TEST(key3, "52ADAF691EFDC377B7349EAA45EE1BFAFA27CAC1FFE08B942C80426D1CA9F3464E3A71D611DA0B415435E82D6EE9F34A"); ++ DEFINE_HMAC_SHA512_TEST(key3, "22D8C17BAF591E07CD2BD58A1B3D76D5904EC45C9099F0171A243F07611E25208A395833BC3F9BBD425636FD8D574BE1A1A367DCB6C40AD3C06E2B57E8FD2729"); ++ ++ /* test message */ ++ DEFINE_HMAC_SHA1_TEST(key2, "DEE6313BE6391523D0B2B326890F13A65F3965B2", test); ++ DEFINE_HMAC_SHA256_TEST(key2, "496FF3E9DA52B2B490CD5EAE23457F8A33E61AB7B42F6E6374B7629CFBE1FCED", test); ++ DEFINE_HMAC_SHA384_TEST(key2, "F5223F750D671453CA6159C1354242DB13E0189CB79AC73E4964F623181B00C811A596F7CE3408DDE06B96C6D792F41E", test); ++ DEFINE_HMAC_SHA512_TEST(key2, "8755A8B0D85D89AFFE7A15702BBA0F835CDE454334EC952ED777A30035D6BD9407EA5DF8DCB89814C1DF7EE215022EA68D9D2BC4E4B299CD6F55CD60C269A706", test); ++ ++ DEFINE_HEX_PTR(h1, "e9ff2b6dfbc03b8dd0471a0f23840334e3ef51c64a325945524563c0375284a092751eca8d084fae22f74a104559a0ee8339d1845538481e674e6d31d4f63089"); ++ DEFINE_HEX_PTR(h2, "5b6e809933a1b8d5a4a6bb62e20b36ae82d9408141e7479d0aa067273bd2d04007fb1977bad549d54330a49ed98f82b495ba"); ++ DEFINE_HEX_PTR(h3, "d2aeef94d7ba2a"); ++ DEFINE_HEX_PTR(h4, "1557db45ded3e38c79b5bb25c83ade42fa7d13047ef1b9a0b21a3c2ab2d4eee5c75e2927ce643163addbda65331035850a436c0acffc723f419e1d1cbf04c9064e6d850580c0732a12600f9feb"); ++ ++ const struct iovec i1 = IOVEC_MAKE(h1, h1_len); ++ const struct iovec i2 = IOVEC_MAKE(h2, h2_len); ++ const struct iovec i3 = IOVEC_MAKE(h3, h3_len); ++ const struct iovec i4 = IOVEC_MAKE(h4, h4_len); ++ ++ DEFINE_HMAC_SHA1_TEST(key2, "28C041532012BFF1B7C87B2A15A8C43EB8037D27", i1, i2, i3, i4); ++ DEFINE_HMAC_SHA256_TEST(key2, "F8A1FBDEE3CD383EA2B4940A3C8E72F443DB5B247016C9F84E2D2FEF3C5A0A23", i1, i2, i3, i4); ++ DEFINE_HMAC_SHA384_TEST(key2, "4D2AB0516F1F5C73BD0761407E0AF42361C1CAE761685FC65D1199598315EE3DCA4DB88E4D96FB06C2DA215A33FA9CE9", i1, i2, i3, i4); ++ DEFINE_HMAC_SHA512_TEST(key2, "E9BF8FC6FDE75FD5E4EF2DF399EE675C57B60C59A7B331F30535FDE68D8072185552E9A8BFA2008C52437F1BCC1472D16FBCF2A77C37339752938E42D2642150", i1, i2, i3, i4); ++ ++ DEFINE_HMAC_SHA256_TEST(key3, "94D4E4B55368A533F6A7FDCC3B93E1F283BB1CA387BB5D14FAFF44A009EDF040", i1, i1, i1, i4); ++ ++ DEFINE_HMAC_SHA256_TEST(key3, "5BE1F4D9C2AFAA2BB3F58FCE967BC7D3084BB8F512659875BDA634991145B0F0", i1, i1, i1, i4, i4, i4, i4, i3, i3, i2); ++} ++ + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0606-openssl-add-rsa_oaep_encrypt_bytes.patch b/SOURCES/0606-openssl-add-rsa_oaep_encrypt_bytes.patch new file mode 100644 index 0000000..6e60da4 --- /dev/null +++ b/SOURCES/0606-openssl-add-rsa_oaep_encrypt_bytes.patch @@ -0,0 +1,107 @@ +From 4abb94f091fb2bf934b5157d8f6a9ae25a4145a2 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 15 Jun 2023 08:19:51 -0400 +Subject: [PATCH] openssl: add rsa_oaep_encrypt_bytes() + +Add function to encrypt bytes, similar to rsa_encrypt_bytes() but using OAEP +(Optimal Asymmetric Encryption Padding). + +(cherry picked from commit 816b1dc4eb27eef8c4a6f5e4380e68589b58c9bf) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 67 +++++++++++++++++++++++++++++++++++++++ + src/shared/openssl-util.h | 2 ++ + 2 files changed, 69 insertions(+) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index 10664e362c..20c1885efb 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -273,6 +273,73 @@ int rsa_encrypt_bytes( + return 0; + } + ++/* Encrypt the key data using RSA-OAEP with the provided label and specified digest algorithm. Returns 0 on ++ * success, -EOPNOTSUPP if the digest algorithm is not supported, or < 0 for any other error. */ ++int rsa_oaep_encrypt_bytes( ++ const EVP_PKEY *pkey, ++ const char *digest_alg, ++ const char *label, ++ const void *decrypted_key, ++ size_t decrypted_key_size, ++ void **ret_encrypt_key, ++ size_t *ret_encrypt_key_size) { ++ ++ assert(pkey); ++ assert(digest_alg); ++ assert(label); ++ assert(decrypted_key); ++ assert(decrypted_key_size > 0); ++ assert(ret_encrypt_key); ++ assert(ret_encrypt_key_size); ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ _cleanup_(EVP_MD_freep) EVP_MD *md = EVP_MD_fetch(NULL, digest_alg, NULL); ++#else ++ const EVP_MD *md = EVP_get_digestbyname(digest_alg); ++#endif ++ if (!md) ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "Digest algorithm '%s' not supported.", digest_alg); ++ ++ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) pkey, NULL); ++ if (!ctx) ++ return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); ++ ++ if (EVP_PKEY_encrypt_init(ctx) <= 0) ++ return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); ++ ++ if (EVP_PKEY_CTX_set_rsa_padding(ctx, RSA_PKCS1_OAEP_PADDING) <= 0) ++ return log_openssl_errors("Failed to configure RSA-OAEP padding"); ++ ++ if (EVP_PKEY_CTX_set_rsa_oaep_md(ctx, md) <= 0) ++ return log_openssl_errors("Failed to configure RSA-OAEP MD"); ++ ++ _cleanup_free_ char *duplabel = strdup(label); ++ if (!duplabel) ++ return log_oom_debug(); ++ ++ if (EVP_PKEY_CTX_set0_rsa_oaep_label(ctx, duplabel, strlen(duplabel) + 1) <= 0) ++ return log_openssl_errors("Failed to configure RSA-OAEP label"); ++ /* ctx owns this now, don't free */ ++ TAKE_PTR(duplabel); ++ ++ size_t size = 0; ++ if (EVP_PKEY_encrypt(ctx, NULL, &size, decrypted_key, decrypted_key_size) <= 0) ++ return log_openssl_errors("Failed to determine RSA-OAEP encrypted key size"); ++ ++ _cleanup_free_ void *buf = malloc(size); ++ if (!buf) ++ return log_oom_debug(); ++ ++ if (EVP_PKEY_encrypt(ctx, buf, &size, decrypted_key, decrypted_key_size) <= 0) ++ return log_openssl_errors("Failed to RSA-OAEP encrypt"); ++ ++ *ret_encrypt_key = TAKE_PTR(buf); ++ *ret_encrypt_key_size = size; ++ ++ return 0; ++} ++ + int rsa_pkey_to_suitable_key_size( + EVP_PKEY *pkey, + size_t *ret_suitable_key_size) { +diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h +index 8079946ab5..5fe7ca341f 100644 +--- a/src/shared/openssl-util.h ++++ b/src/shared/openssl-util.h +@@ -76,6 +76,8 @@ static inline int openssl_hmac(const char *digest_alg, const void *key, size_t k + + int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); + ++int rsa_oaep_encrypt_bytes(const EVP_PKEY *pkey, const char *digest_alg, const char *label, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); ++ + int rsa_pkey_to_suitable_key_size(EVP_PKEY *pkey, size_t *ret_suitable_key_size); + + int rsa_pkey_new(size_t bits, EVP_PKEY **ret); diff --git a/SOURCES/0607-openssl-add-kdf_kb_hmac_derive.patch b/SOURCES/0607-openssl-add-kdf_kb_hmac_derive.patch new file mode 100644 index 0000000..83cd612 --- /dev/null +++ b/SOURCES/0607-openssl-add-kdf_kb_hmac_derive.patch @@ -0,0 +1,184 @@ +From 1b6c4b7d68582bb7865405a143b6217ce9616b8d Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 27 Jun 2023 15:04:59 -0400 +Subject: [PATCH] openssl: add kdf_kb_hmac_derive() + +Add function to perform key-based (KB) key derivation function (KDF) using +hash-based message authentication code (HMAC). + +Also alphabetize openssl-util.c header list, and include string-util.h. + +(cherry picked from commit a65a25bec74d893881f0c452ece5111b1ab4e01b) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 91 ++++++++++++++++++++++++++++++++++++++- + src/shared/openssl-util.h | 5 +++ + src/test/test-openssl.c | 16 +++++++ + 3 files changed, 110 insertions(+), 2 deletions(-) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index 20c1885efb..9107f198cb 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -1,9 +1,10 @@ + /* SPDX-License-Identifier: LGPL-2.1-or-later */ + +-#include "fd-util.h" +-#include "openssl-util.h" + #include "alloc-util.h" ++#include "fd-util.h" + #include "hexdecoct.h" ++#include "openssl-util.h" ++#include "string-util.h" + + #if HAVE_OPENSSL + /* For each error in the the Openssl thread error queue, log the provided message and the Openssl error +@@ -236,6 +237,92 @@ int openssl_hmac_many( + return 0; + } + ++/* Perform Key-Based HMAC KDF. The mode must be "COUNTER" or "FEEDBACK". The parameter naming is from the ++ * Openssl api, and maps to SP800-108 naming as "...key, salt, info, and seed correspond to KI, Label, ++ * Context, and IV (respectively)...". The derive_size parameter specifies how many bytes are derived. ++ * ++ * For more details see: https://www.openssl.org/docs/manmaster/man7/EVP_KDF-KB.html */ ++int kdf_kb_hmac_derive( ++ const char *mode, ++ const char *digest, ++ const void *key, ++ size_t key_size, ++ const void *salt, ++ size_t salt_size, ++ const void *info, ++ size_t info_size, ++ const void *seed, ++ size_t seed_size, ++ size_t derive_size, ++ void **ret) { ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ assert(mode); ++ assert(strcaseeq(mode, "COUNTER") || strcaseeq(mode, "FEEDBACK")); ++ assert(digest); ++ assert(key || key_size == 0); ++ assert(salt || salt_size == 0); ++ assert(info || info_size == 0); ++ assert(seed || seed_size == 0); ++ assert(derive_size > 0); ++ assert(ret); ++ ++ _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = EVP_KDF_fetch(NULL, "KBKDF", NULL); ++ if (!kdf) ++ return log_openssl_errors("Failed to create new EVP_KDF"); ++ ++ _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf); ++ if (!ctx) ++ return log_openssl_errors("Failed to create new EVP_KDF_CTX"); ++ ++ _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); ++ if (!bld) ++ return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); ++ ++ if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MAC, (char*) "HMAC", 0)) ++ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_MAC"); ++ ++ if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_MODE, (char*) mode, 0)) ++ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_MODE"); ++ ++ if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0)) ++ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_DIGEST"); ++ ++ if (key) ++ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size)) ++ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_KEY"); ++ ++ if (salt) ++ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size)) ++ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_SALT"); ++ ++ if (info) ++ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size)) ++ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_INFO"); ++ ++ if (seed) ++ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SEED, (char*) seed, seed_size)) ++ return log_openssl_errors("Failed to add KDF-KB OSSL_KDF_PARAM_SEED"); ++ ++ _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); ++ if (!params) ++ return log_openssl_errors("Failed to build KDF-KB OSSL_PARAM"); ++ ++ _cleanup_free_ void *buf = malloc(derive_size); ++ if (!buf) ++ return log_oom_debug(); ++ ++ if (EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) ++ return log_openssl_errors("Openssl KDF-KB derive failed"); ++ ++ *ret = TAKE_PTR(buf); ++ ++ return 0; ++#else ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "KDF-KB requires openssl >= 3."); ++#endif ++} ++ + int rsa_encrypt_bytes( + EVP_PKEY *pkey, + const void *decrypted_key, +diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h +index 5fe7ca341f..a927819932 100644 +--- a/src/shared/openssl-util.h ++++ b/src/shared/openssl-util.h +@@ -23,6 +23,7 @@ + # endif + # if OPENSSL_VERSION_MAJOR >= 3 + # include ++# include + # include + # endif + +@@ -40,6 +41,8 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL); + #if OPENSSL_VERSION_MAJOR >= 3 ++DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF*, EVP_KDF_free, NULL); ++DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF_CTX*, EVP_KDF_CTX_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC*, EVP_MAC_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC_CTX*, EVP_MAC_CTX_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD*, EVP_MD_free, NULL); +@@ -74,6 +77,8 @@ static inline int openssl_hmac(const char *digest_alg, const void *key, size_t k + return openssl_hmac_many(digest_alg, key, key_size, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); + } + ++int kdf_kb_hmac_derive(const char *mode, const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, const void *seed, size_t seed_size, size_t derive_size, void **ret); ++ + int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); + + int rsa_oaep_encrypt_bytes(const EVP_PKEY *pkey, const char *digest_alg, const char *label, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); +diff --git a/src/test/test-openssl.c b/src/test/test-openssl.c +index 676438f76d..a354b524f0 100644 +--- a/src/test/test-openssl.c ++++ b/src/test/test-openssl.c +@@ -297,4 +297,20 @@ TEST(hmac_many) { + DEFINE_HMAC_SHA256_TEST(key3, "5BE1F4D9C2AFAA2BB3F58FCE967BC7D3084BB8F512659875BDA634991145B0F0", i1, i1, i1, i4, i4, i4, i4, i3, i3, i2); + } + ++TEST(kdf_kb_hmac_derive) { ++#if OPENSSL_VERSION_MAJOR >= 3 ++ _cleanup_free_ void *derived_key = NULL; ++ ++ DEFINE_HEX_PTR(key, "d7ac57124f28371eacaec475b74869d26b4cd64586412a607ce0a9e0c63d468c"); ++ const char *salt = "salty chocolate"; ++ DEFINE_HEX_PTR(info, "6721a2012d9554f5a64593ed3eaa8fe15e6a21e1c8c8736ea4d234eb55b9e31a"); ++ DEFINE_HEX_PTR(expected_derived_key, "A9DA9CEEB9578DBE7DD2862F82898B086E85FF2D10C4E8EC5BD99D0D7F003A2DE1574EB4BD789C03EF5235259BCB3A009DA303EA4DB4CA6BF507DB7C5A063279"); ++ ++ assert_se(kdf_kb_hmac_derive("COUNTER", "SHA256", key, key_len, salt, strlen(salt), info, info_len, /* seed= */ NULL, /* seed_size= */ 0, 64, &derived_key) >= 0); ++ assert_se(memcmp_nn(derived_key, 64, expected_derived_key, expected_derived_key_len) == 0); ++#else ++ log_tests_skipped("KDF-KB requires Openssl >= 3"); ++#endif ++} ++ + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0608-openssl-add-openssl_cipher_many.patch b/SOURCES/0608-openssl-add-openssl_cipher_many.patch new file mode 100644 index 0000000..300a87f --- /dev/null +++ b/SOURCES/0608-openssl-add-openssl_cipher_many.patch @@ -0,0 +1,288 @@ +From d6134077134468d8b72dd0124d6d3470e5b143ac Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 27 Jun 2023 15:04:59 -0400 +Subject: [PATCH] openssl: add openssl_cipher_many() + +Add function to perform openssl cipher operations. + +(cherry picked from commit 58f215a0ac195dc0a7e0232d53789ccde736a08b) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 101 ++++++++++++++++++++++++++++++++++ + src/shared/openssl-util.h | 7 +++ + src/shared/tests.h | 2 +- + src/test/test-openssl.c | 112 ++++++++++++++++++++++++++++++++++++++ + 4 files changed, 221 insertions(+), 1 deletion(-) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index 9107f198cb..19ec385bf0 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -237,6 +237,107 @@ int openssl_hmac_many( + return 0; + } + ++/* Symmetric Cipher encryption using the alg-bits-mode cipher, e.g. AES-128-CFB. The key is required and must ++ * be at least the minimum required key length for the cipher. The IV is optional but, if provided, it must ++ * be at least the minimum iv length for the cipher. If no IV is provided and the cipher requires one, a ++ * buffer of zeroes is used. Returns 0 on success, -EOPNOTSUPP if the cipher algorithm is not supported, or < ++ * 0 on any other error. */ ++int openssl_cipher_many( ++ const char *alg, ++ size_t bits, ++ const char *mode, ++ const void *key, ++ size_t key_size, ++ const void *iv, ++ size_t iv_size, ++ const struct iovec data[], ++ size_t n_data, ++ void **ret, ++ size_t *ret_size) { ++ ++ assert(alg); ++ assert(bits > 0); ++ assert(mode); ++ assert(key); ++ assert(iv || iv_size == 0); ++ assert(data || n_data == 0); ++ assert(ret); ++ assert(ret_size); ++ ++ _cleanup_free_ char *cipher_alg = NULL; ++ if (asprintf(&cipher_alg, "%s-%zu-%s", alg, bits, mode) < 0) ++ return log_oom_debug(); ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ _cleanup_(EVP_CIPHER_freep) EVP_CIPHER *cipher = EVP_CIPHER_fetch(NULL, cipher_alg, NULL); ++#else ++ const EVP_CIPHER *cipher = EVP_get_cipherbyname(cipher_alg); ++#endif ++ if (!cipher) ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "Cipher algorithm '%s' not supported.", cipher_alg); ++ ++ _cleanup_(EVP_CIPHER_CTX_freep) EVP_CIPHER_CTX *ctx = EVP_CIPHER_CTX_new(); ++ if (!ctx) ++ return log_openssl_errors("Failed to create new EVP_CIPHER_CTX"); ++ ++ /* Verify enough key data was provided. */ ++ int cipher_key_length = EVP_CIPHER_key_length(cipher); ++ assert(cipher_key_length >= 0); ++ if ((size_t) cipher_key_length > key_size) ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Not enough key bytes provided, require %d", cipher_key_length); ++ ++ /* Verify enough IV data was provided or, if no IV was provided, use a zeroed buffer for IV data. */ ++ int cipher_iv_length = EVP_CIPHER_iv_length(cipher); ++ assert(cipher_iv_length >= 0); ++ _cleanup_free_ void *zero_iv = NULL; ++ if (iv_size == 0) { ++ zero_iv = malloc0(cipher_iv_length); ++ if (!zero_iv) ++ return log_oom_debug(); ++ ++ iv = zero_iv; ++ iv_size = (size_t) cipher_iv_length; ++ } ++ if ((size_t) cipher_iv_length > iv_size) ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Not enough IV bytes provided, require %d", cipher_iv_length); ++ ++ if (!EVP_EncryptInit(ctx, cipher, key, iv)) ++ return log_openssl_errors("Failed to initialize EVP_CIPHER_CTX."); ++ ++ int cipher_block_size = EVP_CIPHER_CTX_block_size(ctx); ++ assert(cipher_block_size > 0); ++ ++ _cleanup_free_ uint8_t *buf = NULL; ++ size_t size = 0; ++ ++ for (size_t i = 0; i < n_data; i++) { ++ /* Cipher may produce (up to) input length + cipher block size of output. */ ++ if (!GREEDY_REALLOC(buf, size + data[i].iov_len + cipher_block_size)) ++ return log_oom_debug(); ++ ++ int update_size; ++ if (!EVP_EncryptUpdate(ctx, &buf[size], &update_size, data[i].iov_base, data[i].iov_len)) ++ return log_openssl_errors("Failed to update Cipher."); ++ ++ size += update_size; ++ } ++ ++ if (!GREEDY_REALLOC(buf, size + cipher_block_size)) ++ return log_oom_debug(); ++ ++ int final_size; ++ if (!EVP_EncryptFinal_ex(ctx, &buf[size], &final_size)) ++ return log_openssl_errors("Failed to finalize Cipher."); ++ ++ *ret = TAKE_PTR(buf); ++ *ret_size = size + final_size; ++ ++ return 0; ++} ++ + /* Perform Key-Based HMAC KDF. The mode must be "COUNTER" or "FEEDBACK". The parameter naming is from the + * Openssl api, and maps to SP800-108 naming as "...key, salt, info, and seed correspond to KI, Label, + * Context, and IV (respectively)...". The derive_size parameter specifies how many bytes are derived. +diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h +index a927819932..2e894fba80 100644 +--- a/src/shared/openssl-util.h ++++ b/src/shared/openssl-util.h +@@ -41,6 +41,7 @@ DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(SSL*, SSL_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(BIO*, BIO_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MD_CTX*, EVP_MD_CTX_free, NULL); + #if OPENSSL_VERSION_MAJOR >= 3 ++DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_CIPHER*, EVP_CIPHER_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF*, EVP_KDF_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_KDF_CTX*, EVP_KDF_CTX_free, NULL); + DEFINE_TRIVIAL_CLEANUP_FUNC_FULL(EVP_MAC*, EVP_MAC_free, NULL); +@@ -77,6 +78,12 @@ static inline int openssl_hmac(const char *digest_alg, const void *key, size_t k + return openssl_hmac_many(digest_alg, key, key_size, &IOVEC_MAKE((void*) buf, len), 1, ret_digest, ret_digest_size); + } + ++int openssl_cipher_many(const char *alg, size_t bits, const char *mode, const void *key, size_t key_size, const void *iv, size_t iv_size, const struct iovec data[], size_t n_data, void **ret, size_t *ret_size); ++ ++static inline int openssl_cipher(const char *alg, size_t bits, const char *mode, const void *key, size_t key_size, const void *iv, size_t iv_size, const void *buf, size_t len, void **ret, size_t *ret_size) { ++ return openssl_cipher_many(alg, bits, mode, key, key_size, iv, iv_size, &IOVEC_MAKE((void*) buf, len), 1, ret, ret_size); ++} ++ + int kdf_kb_hmac_derive(const char *mode, const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, const void *seed, size_t seed_size, size_t derive_size, void **ret); + + int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); +diff --git a/src/shared/tests.h b/src/shared/tests.h +index 6c2a2f1df2..3cf34d9bcc 100644 +--- a/src/shared/tests.h ++++ b/src/shared/tests.h +@@ -42,7 +42,7 @@ bool can_memlock(void); + #define DEFINE_HEX_PTR(name, hex) \ + _cleanup_free_ void *name = NULL; \ + size_t name##_len = 0; \ +- assert_se(unhexmem(hex, strlen(hex), &name, &name##_len) >= 0); ++ assert_se(unhexmem(hex, strlen_ptr(hex), &name, &name##_len) >= 0); + + #define TEST_REQ_RUNNING_SYSTEMD(x) \ + if (sd_booted() > 0) { \ +diff --git a/src/test/test-openssl.c b/src/test/test-openssl.c +index a354b524f0..9d2a1ad0c2 100644 +--- a/src/test/test-openssl.c ++++ b/src/test/test-openssl.c +@@ -313,4 +313,116 @@ TEST(kdf_kb_hmac_derive) { + #endif + } + ++static void check_cipher( ++ const char *alg, ++ size_t bits, ++ const char *mode, ++ const char *hex_key, ++ const char *hex_iv, ++ const struct iovec data[], ++ size_t n_data, ++ const char *hex_expected) { ++ ++ _cleanup_free_ void *enc_buf = NULL; ++ size_t enc_buf_len; ++ ++ DEFINE_HEX_PTR(key, hex_key); ++ DEFINE_HEX_PTR(iv, hex_iv); ++ DEFINE_HEX_PTR(expected, hex_expected); ++ ++ if (n_data == 0) { ++ assert_se(openssl_cipher(alg, bits, mode, key, key_len, iv, iv_len, NULL, 0, &enc_buf, &enc_buf_len) >= 0); ++ assert_se(memcmp_nn(enc_buf, enc_buf_len, expected, expected_len) == 0); ++ enc_buf = mfree(enc_buf); ++ } else if (n_data == 1) { ++ assert_se(openssl_cipher(alg, bits, mode, key, key_len, iv, iv_len, data[0].iov_base, data[0].iov_len, &enc_buf, &enc_buf_len) >= 0); ++ assert_se(memcmp_nn(enc_buf, enc_buf_len, expected, expected_len) == 0); ++ enc_buf = mfree(enc_buf); ++ } ++ ++ assert_se(openssl_cipher_many(alg, bits, mode, key, key_len, iv, iv_len, data, n_data, &enc_buf, &enc_buf_len) >= 0); ++ assert_se(memcmp_nn(enc_buf, enc_buf_len, expected, expected_len) == 0); ++} ++ ++TEST(openssl_cipher) { ++ struct iovec data[] = { ++ IOVEC_MAKE_STRING("my"), ++ IOVEC_MAKE_STRING(" "), ++ IOVEC_MAKE_STRING("secret"), ++ IOVEC_MAKE_STRING(" "), ++ IOVEC_MAKE_STRING("text"), ++ IOVEC_MAKE_STRING("!"), ++ }; ++ ++ check_cipher( ++ "aes", 256, "cfb", ++ "32c62bbaeb0decc5c874b8e0148f86475b5bb10a36f7078a75a6f11704c2f06a", ++ /* hex_iv= */ NULL, ++ data, ELEMENTSOF(data), ++ "bd4a46f8762bf4bef4430514aaec5e"); ++ ++ check_cipher( ++ "aes", 256, "cfb", ++ "32c62bbaeb0decc5c874b8e0148f86475b5bb10a36f7078a75a6f11704c2f06a", ++ "00000000000000000000000000000000", ++ data, ELEMENTSOF(data), ++ "bd4a46f8762bf4bef4430514aaec5e"); ++ ++ check_cipher( ++ "aes", 256, "cfb", ++ "32c62bbaeb0decc5c874b8e0148f86475b5bb10a36f7078a75a6f11704c2f06a", ++ "9088fd5c4ad9b9419eced86283021a59", ++ data, ELEMENTSOF(data), ++ "6dfbf8dc972f9a462ad7427a1fa41a"); ++ ++ check_cipher( ++ "aes", 256, "cfb", ++ "32c62bbaeb0decc5c874b8e0148f86475b5bb10a36f7078a75a6f11704c2f06a", ++ /* hex_iv= */ NULL, ++ &data[2], 1, ++ "a35605f9763c"); ++ ++ check_cipher( ++ "aes", 256, "cfb", ++ "32c62bbaeb0decc5c874b8e0148f86475b5bb10a36f7078a75a6f11704c2f06a", ++ /* hex_iv= */ NULL, ++ /* data= */ NULL, /* n_data= */ 0, ++ /* expected= */ NULL); ++ ++ check_cipher( ++ "aes", 128, "cfb", ++ "b8fe4b89f6f25dd58cadceb68c99d508", ++ /* hex_iv= */ NULL, ++ data, ELEMENTSOF(data), ++ "9c0fe3abb904ab419d950ae00c93a1"); ++ ++ check_cipher( ++ "aes", 128, "cfb", ++ "b8fe4b89f6f25dd58cadceb68c99d508", ++ "00000000000000000000000000000000", ++ data, ELEMENTSOF(data), ++ "9c0fe3abb904ab419d950ae00c93a1"); ++ ++ check_cipher( ++ "aes", 128, "cfb", ++ "b8fe4b89f6f25dd58cadceb68c99d508", ++ "9088fd5c4ad9b9419eced86283021a59", ++ data, ELEMENTSOF(data), ++ "e765617aceb1326f5309008c14f4e1"); ++ ++ check_cipher( ++ "aes", 128, "cfb", ++ "b8fe4b89f6f25dd58cadceb68c99d508", ++ /* hex_iv= */ NULL, ++ /* data= */ NULL, /* n_data= */ 0, ++ /* expected= */ NULL); ++ ++ check_cipher( ++ "aes", 128, "cfb", ++ "b8fe4b89f6f25dd58cadceb68c99d508", ++ "00000000000000000000000000000000", ++ /* data= */ NULL, /* n_data= */ 0, ++ /* expected= */ NULL); ++} ++ + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0609-openssl-add-ecc_edch.patch b/SOURCES/0609-openssl-add-ecc_edch.patch new file mode 100644 index 0000000..5180009 --- /dev/null +++ b/SOURCES/0609-openssl-add-ecc_edch.patch @@ -0,0 +1,110 @@ +From 5d8a176526175a9d7bf762c46245492bf056bdc8 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 7 Jul 2023 10:11:07 -0400 +Subject: [PATCH] openssl: add ecc_edch() + +Add function to perform ECC EDCH. + +(cherry picked from commit 779b80d8039ae3925ce6a52f97f9d53586ae931e) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 42 +++++++++++++++++++++++++++++++++++++++ + src/shared/openssl-util.h | 2 ++ + src/test/test-openssl.c | 19 ++++++++++++++++++ + 3 files changed, 63 insertions(+) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index 19ec385bf0..c8eadd9c63 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -894,6 +894,48 @@ int ecc_pkey_new(int curve_id, EVP_PKEY **ret) { + return 0; + } + ++/* Perform ECDH to derive an ECC shared secret between the provided private key and public peer key. For two ++ * keys, this will result in the same shared secret in either direction; ECDH using Alice's private key and ++ * Bob's public (peer) key will result in the same shared secret as ECDH using Bob's private key and Alice's ++ * public (peer) key. On success, this returns 0 and provides the shared secret; otherwise this returns an ++ * error. */ ++int ecc_ecdh(const EVP_PKEY *private_pkey, ++ const EVP_PKEY *peer_pkey, ++ void **ret_shared_secret, ++ size_t *ret_shared_secret_size) { ++ ++ assert(private_pkey); ++ assert(peer_pkey); ++ assert(ret_shared_secret); ++ assert(ret_shared_secret_size); ++ ++ _cleanup_(EVP_PKEY_CTX_freep) EVP_PKEY_CTX *ctx = EVP_PKEY_CTX_new((EVP_PKEY*) private_pkey, NULL); ++ if (!ctx) ++ return log_openssl_errors("Failed to create new EVP_PKEY_CTX"); ++ ++ if (EVP_PKEY_derive_init(ctx) <= 0) ++ return log_openssl_errors("Failed to initialize EVP_PKEY_CTX"); ++ ++ if (EVP_PKEY_derive_set_peer(ctx, (EVP_PKEY*) peer_pkey) <= 0) ++ return log_openssl_errors("Failed to set ECC derive peer"); ++ ++ size_t shared_secret_size; ++ if (EVP_PKEY_derive(ctx, NULL, &shared_secret_size) <= 0) ++ return log_openssl_errors("Failed to get ECC shared secret size"); ++ ++ _cleanup_free_ void *shared_secret = malloc(shared_secret_size); ++ if (!shared_secret) ++ return log_oom_debug(); ++ ++ if (EVP_PKEY_derive(ctx, (unsigned char*) shared_secret, &shared_secret_size) <= 0) ++ return log_openssl_errors("Failed to derive ECC shared secret"); ++ ++ *ret_shared_secret = TAKE_PTR(shared_secret); ++ *ret_shared_secret_size = shared_secret_size; ++ ++ return 0; ++} ++ + int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size) { + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* m = NULL; + _cleanup_free_ void *d = NULL, *h = NULL; +diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h +index 2e894fba80..0fea0c5df0 100644 +--- a/src/shared/openssl-util.h ++++ b/src/shared/openssl-util.h +@@ -104,6 +104,8 @@ int ecc_pkey_to_curve_x_y(const EVP_PKEY *pkey, int *ret_curve_id, void **ret_x, + + int ecc_pkey_new(int curve_id, EVP_PKEY **ret); + ++int ecc_ecdh(const EVP_PKEY *private_pkey, const EVP_PKEY *peer_pkey, void **ret_shared_secret, size_t *ret_shared_secret_size); ++ + int pubkey_fingerprint(EVP_PKEY *pk, const EVP_MD *md, void **ret, size_t *ret_size); + + #else +diff --git a/src/test/test-openssl.c b/src/test/test-openssl.c +index 9d2a1ad0c2..1653c0dc9d 100644 +--- a/src/test/test-openssl.c ++++ b/src/test/test-openssl.c +@@ -425,4 +425,23 @@ TEST(openssl_cipher) { + /* expected= */ NULL); + } + ++TEST(ecc_ecdh) { ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkeyA = NULL, *pkeyB = NULL, *pkeyC = NULL; ++ _cleanup_free_ void *secretAB = NULL, *secretBA = NULL, *secretAC = NULL, *secretCA = NULL; ++ size_t secretAB_size, secretBA_size, secretAC_size, secretCA_size; ++ ++ assert_se(ecc_pkey_new(NID_X9_62_prime256v1, &pkeyA) >= 0); ++ assert_se(ecc_pkey_new(NID_X9_62_prime256v1, &pkeyB) >= 0); ++ assert_se(ecc_pkey_new(NID_X9_62_prime256v1, &pkeyC) >= 0); ++ ++ assert_se(ecc_ecdh(pkeyA, pkeyB, &secretAB, &secretAB_size) >= 0); ++ assert_se(ecc_ecdh(pkeyB, pkeyA, &secretBA, &secretBA_size) >= 0); ++ assert_se(ecc_ecdh(pkeyA, pkeyC, &secretAC, &secretAC_size) >= 0); ++ assert_se(ecc_ecdh(pkeyC, pkeyA, &secretCA, &secretCA_size) >= 0); ++ ++ assert_se(memcmp_nn(secretAB, secretAB_size, secretBA, secretBA_size) == 0); ++ assert_se(memcmp_nn(secretAC, secretAC_size, secretCA, secretCA_size) == 0); ++ assert_se(memcmp_nn(secretAC, secretAC_size, secretAB, secretAB_size) != 0); ++} ++ + DEFINE_TEST_MAIN(LOG_DEBUG); diff --git a/SOURCES/0610-openssl-add-kdf_ss_derive.patch b/SOURCES/0610-openssl-add-kdf_ss_derive.patch new file mode 100644 index 0000000..6ebbe49 --- /dev/null +++ b/SOURCES/0610-openssl-add-kdf_ss_derive.patch @@ -0,0 +1,157 @@ +From e632ee86bcf9af68af5775a772e954aabc4cfc4e Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 7 Jul 2023 10:13:27 -0400 +Subject: [PATCH] openssl: add kdf_ss_derive() + +Add function to perform KDF-SS ("concat" KDF). + +While Openssl allows a digest, HMAC, or KMAC for the auxiliary function H, this +currently only allows using a digest for H. + +(cherry picked from commit 8c2205bb1c4ac8024d9a51b4bdf73677b75b7a13) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 66 +++++++++++++++++++++++++++++++++++++++ + src/shared/openssl-util.h | 2 ++ + src/test/test-openssl.c | 37 ++++++++++++++++++++++ + 3 files changed, 105 insertions(+) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index c8eadd9c63..d863729708 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -338,6 +338,72 @@ int openssl_cipher_many( + return 0; + } + ++/* Perform Single-Step (aka "Concat") KDF. Currently, this only supports using the digest for the auxiliary ++ * function. The derive_size parameter specifies how many bytes are derived. ++ * ++ * For more details see: https://www.openssl.org/docs/manmaster/man7/EVP_KDF-SS.html */ ++int kdf_ss_derive( ++ const char *digest, ++ const void *key, ++ size_t key_size, ++ const void *salt, ++ size_t salt_size, ++ const void *info, ++ size_t info_size, ++ size_t derive_size, ++ void **ret) { ++ ++#if OPENSSL_VERSION_MAJOR >= 3 ++ assert(digest); ++ assert(key); ++ assert(derive_size > 0); ++ assert(ret); ++ ++ _cleanup_(EVP_KDF_freep) EVP_KDF *kdf = EVP_KDF_fetch(NULL, "SSKDF", NULL); ++ if (!kdf) ++ return log_openssl_errors("Failed to create new EVP_KDF"); ++ ++ _cleanup_(EVP_KDF_CTX_freep) EVP_KDF_CTX *ctx = EVP_KDF_CTX_new(kdf); ++ if (!ctx) ++ return log_openssl_errors("Failed to create new EVP_KDF_CTX"); ++ ++ _cleanup_(OSSL_PARAM_BLD_freep) OSSL_PARAM_BLD *bld = OSSL_PARAM_BLD_new(); ++ if (!bld) ++ return log_openssl_errors("Failed to create new OSSL_PARAM_BLD"); ++ ++ _cleanup_free_ void *buf = malloc(derive_size); ++ if (!buf) ++ return log_oom_debug(); ++ ++ if (!OSSL_PARAM_BLD_push_utf8_string(bld, OSSL_KDF_PARAM_DIGEST, (char*) digest, 0)) ++ return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_DIGEST"); ++ ++ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_KEY, (char*) key, key_size)) ++ return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_KEY"); ++ ++ if (salt) ++ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_SALT, (char*) salt, salt_size)) ++ return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_SALT"); ++ ++ if (info) ++ if (!OSSL_PARAM_BLD_push_octet_string(bld, OSSL_KDF_PARAM_INFO, (char*) info, info_size)) ++ return log_openssl_errors("Failed to add KDF-SS OSSL_KDF_PARAM_INFO"); ++ ++ _cleanup_(OSSL_PARAM_freep) OSSL_PARAM *params = OSSL_PARAM_BLD_to_param(bld); ++ if (!params) ++ return log_openssl_errors("Failed to build KDF-SS OSSL_PARAM"); ++ ++ if (EVP_KDF_derive(ctx, buf, derive_size, params) <= 0) ++ return log_openssl_errors("Openssl KDF-SS derive failed"); ++ ++ *ret = TAKE_PTR(buf); ++ ++ return 0; ++#else ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "KDF-SS requires openssl >= 3."); ++#endif ++} ++ + /* Perform Key-Based HMAC KDF. The mode must be "COUNTER" or "FEEDBACK". The parameter naming is from the + * Openssl api, and maps to SP800-108 naming as "...key, salt, info, and seed correspond to KI, Label, + * Context, and IV (respectively)...". The derive_size parameter specifies how many bytes are derived. +diff --git a/src/shared/openssl-util.h b/src/shared/openssl-util.h +index 0fea0c5df0..0715aebb42 100644 +--- a/src/shared/openssl-util.h ++++ b/src/shared/openssl-util.h +@@ -84,6 +84,8 @@ static inline int openssl_cipher(const char *alg, size_t bits, const char *mode, + return openssl_cipher_many(alg, bits, mode, key, key_size, iv, iv_size, &IOVEC_MAKE((void*) buf, len), 1, ret, ret_size); + } + ++int kdf_ss_derive(const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, size_t derive_size, void **ret); ++ + int kdf_kb_hmac_derive(const char *mode, const char *digest, const void *key, size_t key_size, const void *salt, size_t salt_size, const void *info, size_t info_size, const void *seed, size_t seed_size, size_t derive_size, void **ret); + + int rsa_encrypt_bytes(EVP_PKEY *pkey, const void *decrypted_key, size_t decrypted_key_size, void **ret_encrypt_key, size_t *ret_encrypt_key_size); +diff --git a/src/test/test-openssl.c b/src/test/test-openssl.c +index 1653c0dc9d..82d384bcb0 100644 +--- a/src/test/test-openssl.c ++++ b/src/test/test-openssl.c +@@ -313,6 +313,43 @@ TEST(kdf_kb_hmac_derive) { + #endif + } + ++#if OPENSSL_VERSION_MAJOR >= 3 ++static void check_ss_derive(const char *hex_key, const char *hex_salt, const char *hex_info, const char *hex_expected) { ++ DEFINE_HEX_PTR(key, hex_key); ++ DEFINE_HEX_PTR(salt, hex_salt); ++ DEFINE_HEX_PTR(info, hex_info); ++ DEFINE_HEX_PTR(expected, hex_expected); ++ ++ _cleanup_free_ void *derived_key = NULL; ++ assert_se(kdf_ss_derive("SHA256", key, key_len, salt, salt_len, info, info_len, expected_len, &derived_key) >= 0); ++ assert_se(memcmp_nn(derived_key, expected_len, expected, expected_len) == 0); ++} ++#endif ++ ++TEST(kdf_ss_derive) { ++#if OPENSSL_VERSION_MAJOR >= 3 ++ check_ss_derive( ++ "01166ad6b05d1fad8cdb50d1902170e9", ++ "feea805789dc8d0b57da5d4d61886b1a", ++ "af4cb6d1d0a996e21e3788584165e2ae", ++ "46CECAB4544E11EF986641BA6F843FAFFD111D3974C34E3B9592311E8579C6BD"); ++ ++ check_ss_derive( ++ "d1c39e37260d79d6e766f1d1412c4b61fc0801db469b97c897b0fbcaebea5178", ++ "b75e3b65d1bb845dee581c7e14cfebc6e882946e90273b77ebe289faaf7de248", ++ "ed25a0043d6c1eb28296da1f9ab138dafee18f4c937bfc43601d4ee6e7634199", ++ "30EB1A1E9DEA7DE4DDB8F3FDF50A01E3"); ++ /* Same inputs as above, but derive more bytes */ ++ check_ss_derive( ++ "d1c39e37260d79d6e766f1d1412c4b61fc0801db469b97c897b0fbcaebea5178", ++ "b75e3b65d1bb845dee581c7e14cfebc6e882946e90273b77ebe289faaf7de248", ++ "ed25a0043d6c1eb28296da1f9ab138dafee18f4c937bfc43601d4ee6e7634199", ++ "30EB1A1E9DEA7DE4DDB8F3FDF50A01E30581D606C1228D98AFF691DF743AC2EE9D99EFD2AE1946C079AA18C9524877FA65D5065F0DAED058AB3416AF80EB2B73"); ++#else ++ log_tests_skipped("KDF-SS requires Openssl >= 3"); ++#endif ++} ++ + static void check_cipher( + const char *alg, + size_t bits, diff --git a/SOURCES/0611-dlfcn-util-add-static-asserts-ensuring-our-sym_xyz-f.patch b/SOURCES/0611-dlfcn-util-add-static-asserts-ensuring-our-sym_xyz-f.patch new file mode 100644 index 0000000..b6eba63 --- /dev/null +++ b/SOURCES/0611-dlfcn-util-add-static-asserts-ensuring-our-sym_xyz-f.patch @@ -0,0 +1,244 @@ +From 6f074919107765848b9aaf14d9efcee88910cf36 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Wed, 16 Nov 2022 21:37:20 +0100 +Subject: [PATCH] dlfcn-util: add static asserts ensuring our sym_xyz() func + ptrs match the types from the official headers +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +Make sure that the sym_xyz function pointers have the types that the +functions we'll assign them have. + +And of course, this found a number of incompatibilities right-away, in +particular in the bpf hookup. + +(Doing this will trigger deprecation warnings from libbpf. I simply +turned them off locally now, since we are well aware of what we are +doing in that regard.) + +There's one return type fix (bool → int), that actually matters I think, +as it might have created an incompatibility on some archs. + +(cherry picked from commit 7736a71fd2c4f0704db2e0d110959f817829cb85) + +Related: RHEL-16182 +--- + src/shared/bpf-compat.h | 2 +- + src/shared/bpf-dlopen.c | 50 +++++++++++++++++++++++++++++++----- + src/shared/bpf-dlopen.h | 4 +-- + src/shared/cryptsetup-util.c | 10 ++++++++ + src/shared/dlfcn-util.h | 4 +++ + src/shared/idn-util.c | 4 +-- + src/shared/idn-util.h | 2 +- + 7 files changed, 64 insertions(+), 12 deletions(-) + +diff --git a/src/shared/bpf-compat.h b/src/shared/bpf-compat.h +index 04ade82fc1..9ccb7d8205 100644 +--- a/src/shared/bpf-compat.h ++++ b/src/shared/bpf-compat.h +@@ -25,7 +25,7 @@ struct bpf_map_create_opts; + * - before the compat static inline helpers that use them. + * When removing this file move these back to bpf-dlopen.h */ + extern int (*sym_bpf_map_create)(enum bpf_map_type, const char *, __u32, __u32, __u32, const struct bpf_map_create_opts *); +-extern bool (*sym_libbpf_probe_bpf_prog_type)(enum bpf_prog_type, const void *); ++extern int (*sym_libbpf_probe_bpf_prog_type)(enum bpf_prog_type, const void *); + + /* compat symbols removed in libbpf 1.0 */ + extern int (*sym_bpf_create_map)(enum bpf_map_type, int key_size, int value_size, int max_entries, __u32 map_flags); +diff --git a/src/shared/bpf-dlopen.c b/src/shared/bpf-dlopen.c +index 2556053cbb..15301aee60 100644 +--- a/src/shared/bpf-dlopen.c ++++ b/src/shared/bpf-dlopen.c +@@ -6,8 +6,20 @@ + #include "strv.h" + + #if HAVE_LIBBPF +-struct bpf_link* (*sym_bpf_program__attach_cgroup)(struct bpf_program *, int); +-struct bpf_link* (*sym_bpf_program__attach_lsm)(struct bpf_program *); ++ ++/* libbpf changed types of function prototypes around, so we need to disable some type checking for older ++ * libbpf. We consider everything older than 0.7 too old for accurate type checks. */ ++#if defined(__LIBBPF_CURRENT_VERSION_GEQ) ++#if __LIBBPF_CURRENT_VERSION_GEQ(0, 7) ++#define MODERN_LIBBPF 1 ++#endif ++#endif ++#if !defined(MODERN_LIBBPF) ++#define MODERN_LIBBPF 0 ++#endif ++ ++struct bpf_link* (*sym_bpf_program__attach_cgroup)(const struct bpf_program *, int); ++struct bpf_link* (*sym_bpf_program__attach_lsm)(const struct bpf_program *); + int (*sym_bpf_link__fd)(const struct bpf_link *); + int (*sym_bpf_link__destroy)(struct bpf_link *); + int (*sym_bpf_map__fd)(const struct bpf_map *); +@@ -22,7 +34,7 @@ int (*sym_bpf_object__load_skeleton)(struct bpf_object_skeleton *); + int (*sym_bpf_object__attach_skeleton)(struct bpf_object_skeleton *); + void (*sym_bpf_object__detach_skeleton)(struct bpf_object_skeleton *); + void (*sym_bpf_object__destroy_skeleton)(struct bpf_object_skeleton *); +-bool (*sym_libbpf_probe_bpf_prog_type)(enum bpf_prog_type, const void *); ++int (*sym_libbpf_probe_bpf_prog_type)(enum bpf_prog_type, const void *); + const char* (*sym_bpf_program__name)(const struct bpf_program *); + libbpf_print_fn_t (*sym_libbpf_set_print)(libbpf_print_fn_t); + long (*sym_libbpf_get_error)(const void *); +@@ -49,6 +61,8 @@ int dlopen_bpf(void) { + void *dl; + int r; + ++ DISABLE_WARNING_DEPRECATED_DECLARATIONS; ++ + dl = dlopen("libbpf.so.1", RTLD_LAZY); + if (!dl) { + /* libbpf < 1.0.0 (we rely on 0.1.0+) provide most symbols we care about, but +@@ -61,14 +75,29 @@ int dlopen_bpf(void) { + "neither libbpf.so.1 nor libbpf.so.0 are installed: %s", dlerror()); + + /* symbols deprecated in 1.0 we use as compat */ +- r = dlsym_many_or_warn(dl, LOG_DEBUG, ++ r = dlsym_many_or_warn( ++ dl, LOG_DEBUG, ++#if MODERN_LIBBPF ++ /* Don't exist anymore in new libbpf, hence cannot type check them */ ++ DLSYM_ARG_FORCE(bpf_create_map), ++ DLSYM_ARG_FORCE(bpf_probe_prog_type)); ++#else + DLSYM_ARG(bpf_create_map), + DLSYM_ARG(bpf_probe_prog_type)); ++#endif + } else { + /* symbols available from 0.7.0 */ +- r = dlsym_many_or_warn(dl, LOG_DEBUG, ++ r = dlsym_many_or_warn( ++ dl, LOG_DEBUG, ++#if MODERN_LIBBPF + DLSYM_ARG(bpf_map_create), +- DLSYM_ARG(libbpf_probe_bpf_prog_type)); ++ DLSYM_ARG(libbpf_probe_bpf_prog_type) ++#else ++ /* These symbols did not exist in old libbpf, hence we cannot type check them */ ++ DLSYM_ARG_FORCE(bpf_map_create), ++ DLSYM_ARG_FORCE(libbpf_probe_bpf_prog_type) ++#endif ++ ); + } + + r = dlsym_many_or_warn( +@@ -86,8 +115,14 @@ int dlopen_bpf(void) { + DLSYM_ARG(bpf_object__attach_skeleton), + DLSYM_ARG(bpf_object__detach_skeleton), + DLSYM_ARG(bpf_object__destroy_skeleton), ++#if MODERN_LIBBPF + DLSYM_ARG(bpf_program__attach_cgroup), + DLSYM_ARG(bpf_program__attach_lsm), ++#else ++ /* libbpf added a "const" to function parameters where it should not have, ignore this type incompatibility */ ++ DLSYM_ARG_FORCE(bpf_program__attach_cgroup), ++ DLSYM_ARG_FORCE(bpf_program__attach_lsm), ++#endif + DLSYM_ARG(bpf_program__name), + DLSYM_ARG(libbpf_set_print), + DLSYM_ARG(libbpf_get_error)); +@@ -96,6 +131,9 @@ int dlopen_bpf(void) { + + /* We set the print helper unconditionally. Otherwise libbpf will emit not useful log messages. */ + (void) sym_libbpf_set_print(bpf_print_func); ++ ++ REENABLE_WARNING; ++ + return r; + } + +diff --git a/src/shared/bpf-dlopen.h b/src/shared/bpf-dlopen.h +index 95951e63e0..0750abc56b 100644 +--- a/src/shared/bpf-dlopen.h ++++ b/src/shared/bpf-dlopen.h +@@ -8,8 +8,8 @@ + + #include "bpf-compat.h" + +-extern struct bpf_link* (*sym_bpf_program__attach_cgroup)(struct bpf_program *, int); +-extern struct bpf_link* (*sym_bpf_program__attach_lsm)(struct bpf_program *); ++extern struct bpf_link* (*sym_bpf_program__attach_cgroup)(const struct bpf_program *, int); ++extern struct bpf_link* (*sym_bpf_program__attach_lsm)(const struct bpf_program *); + extern int (*sym_bpf_link__fd)(const struct bpf_link *); + extern int (*sym_bpf_link__destroy)(struct bpf_link *); + extern int (*sym_bpf_map__fd)(const struct bpf_map *); +diff --git a/src/shared/cryptsetup-util.c b/src/shared/cryptsetup-util.c +index 401e7a3f9c..84358c2792 100644 +--- a/src/shared/cryptsetup-util.c ++++ b/src/shared/cryptsetup-util.c +@@ -193,6 +193,14 @@ int dlopen_cryptsetup(void) { + #if HAVE_LIBCRYPTSETUP + int r; + ++ /* libcryptsetup added crypt_reencrypt() in 2.2.0, and marked it obsolete in 2.4.0, replacing it with ++ * crypt_reencrypt_run(), which takes one extra argument but is otherwise identical. The old call is ++ * still available though, and given we want to support 2.2.0 for a while longer, we'll stick to the ++ * old symbol. Howerver, the old symbols now has a GCC deprecation decorator, hence let's turn off ++ * warnings about this for now. */ ++ ++ DISABLE_WARNING_DEPRECATED_DECLARATIONS; ++ + r = dlopen_many_sym_or_warn( + &cryptsetup_dl, "libcryptsetup.so.12", LOG_DEBUG, + DLSYM_ARG(crypt_activate_by_passphrase), +@@ -238,6 +246,8 @@ int dlopen_cryptsetup(void) { + if (r <= 0) + return r; + ++ REENABLE_WARNING; ++ + /* Redirect the default logging calls of libcryptsetup to our own logging infra. (Note that + * libcryptsetup also maintains per-"struct crypt_device" log functions, which we'll also set + * whenever allocating a "struct crypt_device" context. Why set both? To be defensive: maybe some +diff --git a/src/shared/dlfcn-util.h b/src/shared/dlfcn-util.h +index 7bd5ff4595..ca632f4e1f 100644 +--- a/src/shared/dlfcn-util.h ++++ b/src/shared/dlfcn-util.h +@@ -19,6 +19,10 @@ int dlopen_many_sym_or_warn_sentinel(void **dlp, const char *filename, int log_l + * that each library symbol to resolve will be placed in a variable with the "sym_" prefix, i.e. a symbol + * "foobar" is loaded into a variable "sym_foobar". */ + #define DLSYM_ARG(arg) \ ++ ({ assert_cc(__builtin_types_compatible_p(typeof(sym_##arg), typeof(&arg))); &sym_##arg; }), STRINGIFY(arg) ++ ++/* libbpf is a bit confused about type-safety and API compatibility. Provide a macro that can tape over that mess. Sad. */ ++#define DLSYM_ARG_FORCE(arg) \ + &sym_##arg, STRINGIFY(arg) + + static inline void *safe_dlclose(void *p) { +diff --git a/src/shared/idn-util.c b/src/shared/idn-util.c +index d4108d0c8e..6f36688dc0 100644 +--- a/src/shared/idn-util.c ++++ b/src/shared/idn-util.c +@@ -17,7 +17,7 @@ static void* idn_dl = NULL; + + #if HAVE_LIBIDN2 + int (*sym_idn2_lookup_u8)(const uint8_t* src, uint8_t** lookupname, int flags) = NULL; +-const char *(*sym_idn2_strerror)(int rc) = NULL; ++const char *(*sym_idn2_strerror)(int rc) _const_ = NULL; + int (*sym_idn2_to_unicode_8z8z)(const char * input, char ** output, int flags) = NULL; + + int dlopen_idn(void) { +@@ -31,7 +31,7 @@ int dlopen_idn(void) { + + #if HAVE_LIBIDN + int (*sym_idna_to_ascii_4i)(const uint32_t * in, size_t inlen, char *out, int flags); +-int (*sym_idna_to_unicode_44i)(const uint32_t * in, size_t inlen,uint32_t * out, size_t * outlen, int flags); ++int (*sym_idna_to_unicode_44i)(const uint32_t * in, size_t inlen, uint32_t * out, size_t * outlen, int flags); + char* (*sym_stringprep_ucs4_to_utf8)(const uint32_t * str, ssize_t len, size_t * items_read, size_t * items_written); + uint32_t* (*sym_stringprep_utf8_to_ucs4)(const char *str, ssize_t len, size_t *items_written); + +diff --git a/src/shared/idn-util.h b/src/shared/idn-util.h +index 4698eed3b8..e64bd99747 100644 +--- a/src/shared/idn-util.h ++++ b/src/shared/idn-util.h +@@ -20,7 +20,7 @@ static inline int dlopen_idn(void) { + + #if HAVE_LIBIDN2 + extern int (*sym_idn2_lookup_u8)(const uint8_t* src, uint8_t** lookupname, int flags); +-extern const char *(*sym_idn2_strerror)(int rc); ++extern const char *(*sym_idn2_strerror)(int rc) _const_; + extern int (*sym_idn2_to_unicode_8z8z)(const char * input, char ** output, int flags); + #endif + diff --git a/SOURCES/0612-tpm2-add-tpm2_marshal_blob-and-tpm2_unmarshal_blob.patch b/SOURCES/0612-tpm2-add-tpm2_marshal_blob-and-tpm2_unmarshal_blob.patch new file mode 100644 index 0000000..f4656c0 --- /dev/null +++ b/SOURCES/0612-tpm2-add-tpm2_marshal_blob-and-tpm2_unmarshal_blob.patch @@ -0,0 +1,167 @@ +From 10ea7ea6b2fbbb3b1b98b563fa6d7804ac9f33ec Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 28 Jun 2023 11:46:31 -0400 +Subject: [PATCH] tpm2: add tpm2_marshal_blob() and tpm2_unmarshal_blob() + +Add functions to marshal and unmarshal our 'blob' object. + +(cherry picked from commit 653c3fe9085fca81f0533bb8ee53ef5547ed513e) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 113 ++++++++++++++++++++++++++++++----------- + src/shared/tpm2-util.h | 3 ++ + 2 files changed, 85 insertions(+), 31 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 05189d9dfe..afa3db9c07 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -3688,6 +3688,79 @@ int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *r + #endif + } + ++/* Marshal the public and private objects into a single nonstandard 'blob'. This is not a (publicly) standard ++ * format, this is specific to how we currently store the sealed object. This 'blob' can be unmarshalled by ++ * tpm2_unmarshal_blob(). */ ++int tpm2_marshal_blob( ++ const TPM2B_PUBLIC *public, ++ const TPM2B_PRIVATE *private, ++ void **ret_blob, ++ size_t *ret_blob_size) { ++ ++ TSS2_RC rc; ++ ++ assert(public); ++ assert(private); ++ assert(ret_blob); ++ assert(ret_blob_size); ++ ++ size_t max_size = sizeof(*private) + sizeof(*public); ++ ++ _cleanup_free_ void *blob = malloc(max_size); ++ if (!blob) ++ return log_oom_debug(); ++ ++ size_t blob_size = 0; ++ rc = sym_Tss2_MU_TPM2B_PRIVATE_Marshal(private, blob, max_size, &blob_size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal private key: %s", sym_Tss2_RC_Decode(rc)); ++ ++ rc = sym_Tss2_MU_TPM2B_PUBLIC_Marshal(public, blob, max_size, &blob_size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal public key: %s", sym_Tss2_RC_Decode(rc)); ++ ++ *ret_blob = TAKE_PTR(blob); ++ *ret_blob_size = blob_size; ++ ++ return 0; ++} ++ ++/* Unmarshal the 'blob' into public and private objects. This is not a (publicly) standard format, this is ++ * specific to how we currently store the sealed object. This expects the 'blob' to have been created by ++ * tpm2_marshal_blob(). */ ++int tpm2_unmarshal_blob( ++ const void *blob, ++ size_t blob_size, ++ TPM2B_PUBLIC *ret_public, ++ TPM2B_PRIVATE *ret_private) { ++ ++ TSS2_RC rc; ++ ++ assert(blob); ++ assert(ret_public); ++ assert(ret_private); ++ ++ TPM2B_PRIVATE private = {}; ++ size_t offset = 0; ++ rc = sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal(blob, blob_size, &offset, &private); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to unmarshal private key: %s", sym_Tss2_RC_Decode(rc)); ++ ++ TPM2B_PUBLIC public = {}; ++ rc = sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal(blob, blob_size, &offset, &public); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to unmarshal public key: %s", sym_Tss2_RC_Decode(rc)); ++ ++ *ret_public = public; ++ *ret_private = private; ++ ++ return 0; ++} ++ + int tpm2_seal(Tpm2Context *c, + const TPM2B_DIGEST *policy, + const char *pin, +@@ -3825,21 +3898,10 @@ int tpm2_seal(Tpm2Context *c, + log_debug("Marshalling private and public part of HMAC key."); + + _cleanup_free_ void *blob = NULL; +- size_t max_size = sizeof(*private) + sizeof(*public), blob_size = 0; +- +- blob = malloc0(max_size); +- if (!blob) +- return log_oom(); +- +- rc = sym_Tss2_MU_TPM2B_PRIVATE_Marshal(private, blob, max_size, &blob_size); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to marshal private key: %s", sym_Tss2_RC_Decode(rc)); +- +- rc = sym_Tss2_MU_TPM2B_PUBLIC_Marshal(public, blob, max_size, &blob_size); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to marshal public key: %s", sym_Tss2_RC_Decode(rc)); ++ size_t blob_size; ++ r = tpm2_marshal_blob(public, private, &blob, &blob_size); ++ if (r < 0) ++ return log_error_errno(r, "Could not create sealed blob: %m"); + + /* serialize the key for storage in the LUKS header. A deserialized ESYS_TR provides both + * the raw TPM handle as well as the object name. The object name is used to verify that +@@ -3930,22 +3992,11 @@ int tpm2_unseal(const char *device, + + usec_t start = now(CLOCK_MONOTONIC); + +- log_debug("Unmarshalling private part of HMAC key."); +- +- TPM2B_PRIVATE private = {}; +- size_t offset = 0; +- rc = sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal(blob, blob_size, &offset, &private); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to unmarshal private key: %s", sym_Tss2_RC_Decode(rc)); +- +- log_debug("Unmarshalling public part of HMAC key."); +- +- TPM2B_PUBLIC public = {}; +- rc = sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal(blob, blob_size, &offset, &public); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to unmarshal public key: %s", sym_Tss2_RC_Decode(rc)); ++ TPM2B_PUBLIC public; ++ TPM2B_PRIVATE private; ++ r = tpm2_unmarshal_blob(blob, blob_size, &public, &private); ++ if (r < 0) ++ return log_error_errno(r, "Could not extract parts from blob: %m"); + + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new(device, &c); +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index f9efca9311..7b0750b03a 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -164,6 +164,9 @@ int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGE + int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); + int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, TPM2B_DIGEST *digest); + ++int tpm2_marshal_blob(const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, void **ret_blob, size_t *ret_blob_size); ++int tpm2_unmarshal_blob(const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_public, TPM2B_PRIVATE *ret_private); ++ + int tpm2_seal(Tpm2Context *c, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); + int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); + diff --git a/SOURCES/0613-tpm2-add-tpm2_serialize-and-tpm2_deserialize.patch b/SOURCES/0613-tpm2-add-tpm2_serialize-and-tpm2_deserialize.patch new file mode 100644 index 0000000..d07fdda --- /dev/null +++ b/SOURCES/0613-tpm2-add-tpm2_serialize-and-tpm2_deserialize.patch @@ -0,0 +1,160 @@ +From ea5fd9eabda73184a151d93520ed0ded9283b778 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 30 Jun 2023 13:42:25 -0400 +Subject: [PATCH] tpm2: add tpm2_serialize() and tpm2_deserialize() + +Add functions to perform serialization and deserialization of ESYS_TR objects. + +(cherry picked from commit 1eff4242958ca355e2cee459a7436450900b7941) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 101 ++++++++++++++++++++++++++++------------- + 1 file changed, 70 insertions(+), 31 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index afa3db9c07..131356538e 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -3761,6 +3761,65 @@ int tpm2_unmarshal_blob( + return 0; + } + ++/* Serialize a handle. This produces a binary object that can be later deserialized (by the same TPM), even ++ * across restarts of the TPM or reboots (assuming the handle is persistent). */ ++static int tpm2_serialize( ++ Tpm2Context *c, ++ const Tpm2Handle *handle, ++ void **ret_serialized, ++ size_t *ret_serialized_size) { ++ ++ TSS2_RC rc; ++ ++ assert(c); ++ assert(handle); ++ assert(ret_serialized); ++ assert(ret_serialized_size); ++ ++ _cleanup_(Esys_Freep) unsigned char *serialized = NULL; ++ size_t size = 0; ++ rc = sym_Esys_TR_Serialize(c->esys_context, handle->esys_handle, &serialized, &size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to serialize: %s", sym_Tss2_RC_Decode(rc)); ++ ++ *ret_serialized = TAKE_PTR(serialized); ++ *ret_serialized_size = size; ++ ++ return 0; ++} ++ ++static int tpm2_deserialize( ++ Tpm2Context *c, ++ const void *serialized, ++ size_t serialized_size, ++ Tpm2Handle **ret_handle) { ++ ++ TSS2_RC rc; ++ int r; ++ ++ assert(c); ++ assert(serialized); ++ assert(ret_handle); ++ ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; ++ r = tpm2_handle_new(c, &handle); ++ if (r < 0) ++ return r; ++ ++ /* Since this is an existing handle in the TPM we should not implicitly flush it. */ ++ handle->flush = false; ++ ++ rc = sym_Esys_TR_Deserialize(c->esys_context, serialized, serialized_size, &handle->esys_handle); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to deserialize: %s", sym_Tss2_RC_Decode(rc)); ++ ++ *ret_handle = TAKE_PTR(handle); ++ ++ return 0; ++} ++ + int tpm2_seal(Tpm2Context *c, + const TPM2B_DIGEST *policy, + const char *pin, +@@ -3773,7 +3832,6 @@ int tpm2_seal(Tpm2Context *c, + size_t *ret_srk_buf_size) { + + uint16_t primary_alg = 0; +- TSS2_RC rc; + int r; + + assert(ret_secret); +@@ -3903,34 +3961,27 @@ int tpm2_seal(Tpm2Context *c, + if (r < 0) + return log_error_errno(r, "Could not create sealed blob: %m"); + +- /* serialize the key for storage in the LUKS header. A deserialized ESYS_TR provides both +- * the raw TPM handle as well as the object name. The object name is used to verify that +- * the key we use later is the key we expect to establish the session with. +- */ +- _cleanup_(Esys_Freep) uint8_t *srk_buf = NULL; +- size_t srk_buf_size = 0; +- if (ret_srk_buf) { +- log_debug("Serializing SRK ESYS_TR reference"); +- rc = sym_Esys_TR_Serialize(c->esys_context, primary_handle->esys_handle, &srk_buf, &srk_buf_size); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to serialize primary key: %s", sym_Tss2_RC_Decode(rc)); +- } +- + if (DEBUG_LOGGING) + log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); + ++ _cleanup_free_ void *srk_buf = NULL; ++ size_t srk_buf_size = 0; + if (ret_srk_buf) { ++ _cleanup_(Esys_Freep) void *tmp = NULL; ++ r = tpm2_serialize(c, primary_handle, &tmp, &srk_buf_size); ++ if (r < 0) ++ return r; ++ + /* + * make a copy since we don't want the caller to understand that + * ESYS allocated the pointer. It would make tracking what deallocator + * to use for srk_buf in which context a PITA. + */ +- void *tmp = memdup(srk_buf, srk_buf_size); +- if (!tmp) ++ srk_buf = memdup(tmp, srk_buf_size); ++ if (!srk_buf) + return log_oom(); + +- *ret_srk_buf = TAKE_PTR(tmp); ++ *ret_srk_buf = TAKE_PTR(srk_buf); + *ret_srk_buf_size = srk_buf_size; + } + +@@ -4005,21 +4056,9 @@ int tpm2_unseal(const char *device, + + _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; + if (srk_buf) { +- r = tpm2_handle_new(c, &primary_handle); ++ r = tpm2_deserialize(c, srk_buf, srk_buf_size, &primary_handle); + if (r < 0) + return r; +- +- primary_handle->flush = false; +- +- log_debug("Found existing SRK key to use, deserializing ESYS_TR"); +- rc = sym_Esys_TR_Deserialize( +- c->esys_context, +- srk_buf, +- srk_buf_size, +- &primary_handle->esys_handle); +- if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to deserialize primary key: %s", sym_Tss2_RC_Decode(rc)); + } else if (primary_alg != 0) { + TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), }; + r = tpm2_get_legacy_template(primary_alg, &template.publicArea); diff --git a/SOURCES/0614-tpm2-add-tpm2_index_to_handle-and-tpm2_index_from_ha.patch b/SOURCES/0614-tpm2-add-tpm2_index_to_handle-and-tpm2_index_from_ha.patch new file mode 100644 index 0000000..cfece6f --- /dev/null +++ b/SOURCES/0614-tpm2-add-tpm2_index_to_handle-and-tpm2_index_from_ha.patch @@ -0,0 +1,284 @@ +From a721d6941e65589fc0a88091b64c9b6a10792ff4 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 30 Jun 2023 12:52:10 -0400 +Subject: [PATCH] tpm2: add tpm2_index_to_handle() and tpm2_index_from_handle() + +Adjust the tpm2_esys_handle_from_tpm_handle() function into better-named +tpm2_index_to_handle(), which operates like tpm2_get_srk() but allows using any +handle index. Also add matching tpm2_index_from_handle(). + +Also change the references to 'location' in tpm2_persist_handle() to more +appropriate 'handle index'. + +(cherry picked from commit 13cf98f3623866c18e77488e405862aedb1ec5f5) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 146 +++++++++++++++++++++++------------------ + src/shared/tpm2-util.h | 5 ++ + 2 files changed, 88 insertions(+), 63 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 131356538e..85ef4157ac 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -56,6 +56,7 @@ static TSS2_RC (*sym_Esys_TR_Close)(ESYS_CONTEXT *esys_context, ESYS_TR *rsrc_ha + static TSS2_RC (*sym_Esys_TR_Deserialize)(ESYS_CONTEXT *esys_context, uint8_t const *buffer, size_t buffer_size, ESYS_TR *esys_handle) = NULL; + static TSS2_RC (*sym_Esys_TR_FromTPMPublic)(ESYS_CONTEXT *esysContext, TPM2_HANDLE tpm_handle, ESYS_TR optionalSession1, ESYS_TR optionalSession2, ESYS_TR optionalSession3, ESYS_TR *object) = NULL; + static TSS2_RC (*sym_Esys_TR_GetName)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_NAME **name) = NULL; ++static TSS2_RC (*sym_Esys_TR_GetTpmHandle)(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle, TPM2_HANDLE *tpm_handle) = NULL; + static TSS2_RC (*sym_Esys_TR_Serialize)(ESYS_CONTEXT *esys_context, ESYS_TR object, uint8_t **buffer, size_t *buffer_size) = NULL; + static TSS2_RC (*sym_Esys_TR_SetAuth)(ESYS_CONTEXT *esysContext, ESYS_TR handle, TPM2B_AUTH const *authValue) = NULL; + static TSS2_RC (*sym_Esys_TRSess_GetAttributes)(ESYS_CONTEXT *esysContext, ESYS_TR session, TPMA_SESSION *flags) = NULL; +@@ -114,6 +115,12 @@ int dlopen_tpm2(void) { + if (r < 0) + return r; + ++ /* Esys_TR_GetTpmHandle was added to tpm2-tss in version 2.4.0. Once we can set a minimum tpm2-tss ++ * version of 2.4.0 this sym can be moved up to the normal list above. */ ++ r = dlsym_many_or_warn(libtss2_esys_dl, LOG_DEBUG, DLSYM_ARG_FORCE(Esys_TR_GetTpmHandle)); ++ if (r < 0) ++ log_debug("libtss2-esys too old, does not include Esys_TR_GetTpmHandle."); ++ + r = dlopen_many_sym_or_warn( + &libtss2_rc_dl, "libtss2-rc.so.0", LOG_DEBUG, + DLSYM_ARG(Tss2_RC_Decode)); +@@ -697,53 +704,59 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle) { + return 0; + } + +-/* Create a Tpm2Handle object that references a pre-existing handle in the TPM, at the TPM2_HANDLE address +- * provided. This should be used only for persistent, transient, or NV handles. Returns 1 on success, 0 if +- * the requested handle is not present in the TPM, or < 0 on error. */ +-static int tpm2_esys_handle_from_tpm_handle( ++/* Create a Tpm2Handle object that references a pre-existing handle in the TPM, at the handle index provided. ++ * This should be used only for persistent, transient, or NV handles; and the handle must already exist in ++ * the TPM at the specified handle index. The handle index should not be 0. Returns 1 if found, 0 if the ++ * index is empty, or < 0 on error. Also see tpm2_get_srk() below; the SRK is a commonly used persistent ++ * Tpm2Handle. */ ++int tpm2_index_to_handle( + Tpm2Context *c, ++ TPM2_HANDLE index, + const Tpm2Handle *session, +- TPM2_HANDLE tpm_handle, ++ TPM2B_PUBLIC **ret_public, ++ TPM2B_NAME **ret_name, ++ TPM2B_NAME **ret_qname, + Tpm2Handle **ret_handle) { + + TSS2_RC rc; + int r; + + assert(c); +- assert(tpm_handle > 0); +- assert(ret_handle); + + /* Let's restrict this, at least for now, to allow only some handle types. */ +- switch (TPM2_HANDLE_TYPE(tpm_handle)) { ++ switch (TPM2_HANDLE_TYPE(index)) { + case TPM2_HT_PERSISTENT: + case TPM2_HT_NV_INDEX: + case TPM2_HT_TRANSIENT: + break; + case TPM2_HT_PCR: + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), +- "Refusing to create ESYS handle for PCR handle 0x%08" PRIx32 ".", +- tpm_handle); ++ "Invalid handle 0x%08" PRIx32 " (in PCR range).", index); + case TPM2_HT_HMAC_SESSION: + case TPM2_HT_POLICY_SESSION: + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), +- "Refusing to create ESYS handle for session handle 0x%08" PRIx32 ".", +- tpm_handle); ++ "Invalid handle 0x%08" PRIx32 " (in session range).", index); + case TPM2_HT_PERMANENT: /* Permanent handles are defined, e.g. ESYS_TR_RH_OWNER. */ + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), +- "Refusing to create ESYS handle for permanent handle 0x%08" PRIx32 ".", +- tpm_handle); ++ "Invalid handle 0x%08" PRIx32 " (in permanent range).", index); + default: + return log_error_errno(SYNTHETIC_ERRNO(EINVAL), +- "Refusing to create ESYS handle for unknown handle 0x%08" PRIx32 ".", +- tpm_handle); ++ "Invalid handle 0x%08" PRIx32 " (in unknown range).", index); + } + +- r = tpm2_get_capability_handle(c, tpm_handle); ++ r = tpm2_get_capability_handle(c, index); + if (r < 0) + return r; + if (r == 0) { +- log_debug("TPM handle 0x%08" PRIx32 " not populated.", tpm_handle); +- *ret_handle = NULL; ++ log_debug("TPM handle 0x%08" PRIx32 " not populated.", index); ++ if (ret_public) ++ *ret_public = NULL; ++ if (ret_name) ++ *ret_name = NULL; ++ if (ret_qname) ++ *ret_qname = NULL; ++ if (ret_handle) ++ *ret_handle = NULL; + return 0; + } + +@@ -758,7 +771,7 @@ static int tpm2_esys_handle_from_tpm_handle( + + rc = sym_Esys_TR_FromTPMPublic( + c->esys_context, +- tpm_handle, ++ index, + session ? session->esys_handle : ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -767,25 +780,61 @@ static int tpm2_esys_handle_from_tpm_handle( + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to read public info: %s", sym_Tss2_RC_Decode(rc)); + +- *ret_handle = TAKE_PTR(handle); ++ if (ret_public || ret_name || ret_qname) { ++ r = tpm2_read_public(c, session, handle, ret_public, ret_name, ret_qname); ++ if (r < 0) ++ return r; ++ } ++ ++ if (ret_handle) ++ *ret_handle = TAKE_PTR(handle); + + return 1; + } + +-/* Copy an object in the TPM at a transient location to a persistent location. ++/* Get the handle index for the provided Tpm2Handle. */ ++int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE *ret_index) { ++ TSS2_RC rc; ++ ++ assert(c); ++ assert(handle); ++ assert(ret_index); ++ ++ /* Esys_TR_GetTpmHandle was added to tpm2-tss in version 2.4.0. Once we can set a minimum tpm2-tss ++ * version of 2.4.0 this check can be removed. */ ++ if (!sym_Esys_TR_GetTpmHandle) ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "libtss2-esys too old, does not include Esys_TR_GetTpmHandle."); ++ ++ rc = sym_Esys_TR_GetTpmHandle(c->esys_context, handle->esys_handle, ret_index); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to get handle index: %s", sym_Tss2_RC_Decode(rc)); ++ ++ return 0; ++} ++ ++/* Copy an object in the TPM at a transient handle to a persistent handle. + * +- * The provided transient handle must exist in the TPM in the transient range. The persistent location may be +- * 0 or any location in the persistent range. If 0, this will try each handle in the persistent range, in +- * ascending order, until an available one is found. If non-zero, only the requested persistent location will ++ * The provided transient handle must exist in the TPM in the transient range. The persistent handle may be 0 ++ * or any handle in the persistent range. If 0, this will try each handle in the persistent range, in ++ * ascending order, until an available one is found. If non-zero, only the requested persistent handle will + * be used. + * ++ * Note that the persistent handle parameter is an handle index (i.e. number), while the transient handle is ++ * a Tpm2Handle object. The returned persistent handle will be a Tpm2Handle object that is located in the TPM ++ * at the requested persistent handle index (or the first available if none was requested). ++ * + * Returns 1 if the object was successfully persisted, or 0 if there is already a key at the requested +- * location(s), or < 0 on error. The persistent handle is only provided when returning 1. */ ++ * handle, or < 0 on error. Theoretically, this would also return 0 if no specific persistent handle is ++ * requiested but all persistent handles are used, but it is extremely unlikely the TPM has enough internal ++ * memory to store the entire persistent range, in which case an error will be returned if the TPM is out of ++ * memory for persistent storage. The persistent handle is only provided when returning 1. */ + static int tpm2_persist_handle( + Tpm2Context *c, + const Tpm2Handle *transient_handle, + const Tpm2Handle *session, +- TPMI_DH_PERSISTENT persistent_location, ++ TPMI_DH_PERSISTENT persistent_handle_index, + Tpm2Handle **ret_persistent_handle) { + + /* We don't use TPM2_PERSISTENT_FIRST and TPM2_PERSISTENT_LAST here due to: +@@ -797,13 +846,13 @@ static int tpm2_persist_handle( + assert(c); + assert(transient_handle); + +- /* If persistent location specified, only try that. */ +- if (persistent_location != 0) { +- if (TPM2_HANDLE_TYPE(persistent_location) != TPM2_HT_PERSISTENT) ++ /* If persistent handle index specified, only try that. */ ++ if (persistent_handle_index != 0) { ++ if (TPM2_HANDLE_TYPE(persistent_handle_index) != TPM2_HT_PERSISTENT) + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), +- "Handle not in persistent range: 0x%x", persistent_location); ++ "Handle not in persistent range: 0x%x", persistent_handle_index); + +- first = last = persistent_location; ++ first = last = persistent_handle_index; + } + + for (TPMI_DH_PERSISTENT requested = first; requested <= last; requested++) { +@@ -901,7 +950,7 @@ static int tpm2_credit_random(Tpm2Context *c) { + return 0; + } + +-static int tpm2_read_public( ++int tpm2_read_public( + Tpm2Context *c, + const Tpm2Handle *session, + const Tpm2Handle *handle, +@@ -1113,36 +1162,7 @@ static int tpm2_get_srk( + TPM2B_NAME **ret_qname, + Tpm2Handle **ret_handle) { + +- int r; +- +- assert(c); +- +- _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; +- r = tpm2_esys_handle_from_tpm_handle(c, session, TPM2_SRK_HANDLE, &handle); +- if (r < 0) +- return r; +- if (r == 0) { /* SRK not found */ +- if (ret_public) +- *ret_public = NULL; +- if (ret_name) +- *ret_name = NULL; +- if (ret_qname) +- *ret_qname = NULL; +- if (ret_handle) +- *ret_handle = NULL; +- return 0; +- } +- +- if (ret_public || ret_name || ret_qname) { +- r = tpm2_read_public(c, session, handle, ret_public, ret_name, ret_qname); +- if (r < 0) +- return r; +- } +- +- if (ret_handle) +- *ret_handle = TAKE_PTR(handle); +- +- return 1; ++ return tpm2_index_to_handle(c, TPM2_SRK_HANDLE, session, ret_public, ret_name, ret_qname, ret_handle); + } + + /* Get the SRK, creating one if needed. Returns 0 on success, or < 0 on error. */ +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 7b0750b03a..c8da857eb4 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -155,6 +155,11 @@ void tpm2_log_debug_buffer(const void *buffer, size_t size, const char *msg); + void tpm2_log_debug_digest(const TPM2B_DIGEST *digest, const char *msg); + void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg); + ++int tpm2_index_to_handle(Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); ++int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE *ret_index); ++ ++int tpm2_read_public(Tpm2Context *c, const Tpm2Handle *session, const Tpm2Handle *handle, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname); ++ + int tpm2_pcr_read(Tpm2Context *c, const TPML_PCR_SELECTION *pcr_selection, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); + int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_t n_pcr_values); + diff --git a/SOURCES/0615-tpm2-fix-build-failure-without-openssl.patch b/SOURCES/0615-tpm2-fix-build-failure-without-openssl.patch new file mode 100644 index 0000000..36be43c --- /dev/null +++ b/SOURCES/0615-tpm2-fix-build-failure-without-openssl.patch @@ -0,0 +1,46 @@ +From f7b06cb36e436583979e7f4a1966297239ea1caa Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 20 Jan 2023 12:42:52 -0500 +Subject: [PATCH] tpm2: fix build failure without openssl + +(cherry picked from commit 0d7009d35df2fef494b1df67f5caa55e85dd8970) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 9 ++++++--- + 1 file changed, 6 insertions(+), 3 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 85ef4157ac..df1f7b8340 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -2621,6 +2621,7 @@ int tpm2_get_good_pcr_banks_strv( + uint32_t pcr_mask, + char ***ret) { + ++#if HAVE_OPENSSL + _cleanup_free_ TPMI_ALG_HASH *algs = NULL; + _cleanup_strv_free_ char **l = NULL; + int n_algs; +@@ -2657,6 +2658,9 @@ int tpm2_get_good_pcr_banks_strv( + + *ret = TAKE_PTR(l); + return 0; ++#else /* HAVE_OPENSSL */ ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); ++#endif + } + + /* Hash data into the digest. +@@ -4410,9 +4414,8 @@ int tpm2_extend_bytes( + sym_Tss2_RC_Decode(rc)); + + return 0; +-#else +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), +- "OpenSSL not supported on this build."); ++#else /* HAVE_OPENSSL */ ++ return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); + #endif + } + #endif diff --git a/SOURCES/0616-tpm2-util-look-for-tpm2-pcr-signature.json-directly-.patch b/SOURCES/0616-tpm2-util-look-for-tpm2-pcr-signature.json-directly-.patch new file mode 100644 index 0000000..5199ba5 --- /dev/null +++ b/SOURCES/0616-tpm2-util-look-for-tpm2-pcr-signature.json-directly-.patch @@ -0,0 +1,80 @@ +From 5b5e0f56ac9f9f2633735fc62d4b19b4105cea41 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Thu, 22 Jun 2023 16:26:15 +0200 +Subject: [PATCH] tpm2-util: look for tpm2-pcr-signature.json directly in + /.extra/ + +So far we relied on tmpfiles.d to copy tpm2-pcr-signature.json from +/.extra/ into /run/systemd/. This is racy however if cryptsetup runs too +early, and we cannot unconditionally run it after tmpfiles completed. + +hence, let's teach cryptsetup to directly look for the file in /.extra/, +in order to simplify this, and remove the race. But do so only in the +initrd (as only there /.extra/ is a concept). + +We generally prefer looking in /run/systemd/, since things are under +user control then. In the regular system we exclusively want that +userspace looks there. + +Fixes: #26490 +(cherry picked from commit 6270b2e67edbe5c9ac164fc3f2f1cd1d7832fcd8) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 20 ++++++++++++++++++-- + 1 file changed, 18 insertions(+), 2 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index df1f7b8340..944bf91019 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -15,6 +15,7 @@ + #include "hmac.h" + #include "lockfile-util.h" + #include "memory-util.h" ++#include "nulstr-util.h" + #include "parse-util.h" + #include "random-util.h" + #include "sha256.h" +@@ -23,6 +24,7 @@ + #include "string-table.h" + #include "time-util.h" + #include "tpm2-util.h" ++#include "util.h" + #include "virt.h" + + #if HAVE_TPM2 +@@ -4964,6 +4966,7 @@ int tpm2_parse_pcr_argument_to_mask(const char *arg, uint32_t *ret_mask) { + } + + int tpm2_load_pcr_signature(const char *path, JsonVariant **ret) { ++ _cleanup_strv_free_ char **search = NULL; + _cleanup_free_ char *discovered_path = NULL; + _cleanup_fclose_ FILE *f = NULL; + int r; +@@ -4971,10 +4974,23 @@ int tpm2_load_pcr_signature(const char *path, JsonVariant **ret) { + /* Tries to load a JSON PCR signature file. Takes an absolute path, a simple file name or NULL. In + * the latter two cases searches in /etc/, /usr/lib/, /run/, as usual. */ + +- if (!path) ++ search = strv_split_nulstr(CONF_PATHS_NULSTR("systemd")); ++ if (!search) ++ return log_oom(); ++ ++ if (!path) { ++ /* If no path is specified, then look for "tpm2-pcr-signature.json" automatically. Also, in ++ * this case include /.extra/ in the search path, but only in this case, and if we run in the ++ * initrd. We don't want to be too eager here, after all /.extra/ is untrusted territory. */ ++ + path = "tpm2-pcr-signature.json"; + +- r = search_and_fopen(path, "re", NULL, (const char**) CONF_PATHS_STRV("systemd"), &f, &discovered_path); ++ if (in_initrd()) ++ if (strv_extend(&search, "/.extra") < 0) ++ return log_oom(); ++ } ++ ++ r = search_and_fopen(path, "re", NULL, (const char**) search, &f, &discovered_path); + if (r < 0) + return log_debug_errno(r, "Failed to find TPM PCR signature file '%s': %m", path); + diff --git a/SOURCES/0617-tpm2-downgrade-most-log-functions-from-error-to-debu.patch b/SOURCES/0617-tpm2-downgrade-most-log-functions-from-error-to-debu.patch new file mode 100644 index 0000000..2c503a3 --- /dev/null +++ b/SOURCES/0617-tpm2-downgrade-most-log-functions-from-error-to-debu.patch @@ -0,0 +1,1534 @@ +From 80c19ca9c613a0909bca238b7e71165ed9d8f546 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 8 Sep 2023 12:39:49 -0400 +Subject: [PATCH] tpm2: downgrade most log functions from error to debug + +Because most TPM2 functions here are 'library-like' functions, they should be +at debug level, not error level. + +The only functions not reduced to logging at debug are tpm2_list_devices(), +since it is expected to print output, and the tpm2_parse_pcr_argument_*() +functions, since the system-wide parse_*_argument() functions generally log at +error level. + +(cherry picked from commit f9a0ee7554d18f8f1c1968811efed85a2f5e9013) + +Related: RHEL-16182 +--- + src/boot/measure.c | 2 +- + src/boot/pcrphase.c | 6 +- + src/cryptenroll/cryptenroll-tpm2.c | 8 +- + src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 13 +- + src/cryptsetup/cryptsetup-tpm2.c | 30 +- + src/cryptsetup/cryptsetup.c | 6 +- + src/partition/repart.c | 6 +- + src/shared/creds-util.c | 15 +- + src/shared/tpm2-util.c | 312 +++++++++--------- + src/shared/tpm2-util.h | 2 +- + 10 files changed, 203 insertions(+), 197 deletions(-) + +diff --git a/src/boot/measure.c b/src/boot/measure.c +index 76f7f3fbfa..8882850e13 100644 +--- a/src/boot/measure.c ++++ b/src/boot/measure.c +@@ -807,7 +807,7 @@ static int verb_sign(int argc, char *argv[], void *userdata) { + + r = tpm2_calculate_policy_pcr(&pcr_value, 1, &pcr_policy_digest); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not calculate PolicyPCR digest: %m"); + + _cleanup_(EVP_MD_CTX_freep) EVP_MD_CTX* mdctx = NULL; + mdctx = EVP_MD_CTX_new(); +diff --git a/src/boot/pcrphase.c b/src/boot/pcrphase.c +index 16d71e6a22..8b2b837222 100644 +--- a/src/boot/pcrphase.c ++++ b/src/boot/pcrphase.c +@@ -165,7 +165,7 @@ static int determine_banks(Tpm2Context *c, unsigned target_pcr_nr) { + + r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << target_pcr_nr, &l); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not verify pcr banks: %m"); + + strv_free_and_replace(arg_banks, l); + return 0; +@@ -343,7 +343,7 @@ static int run(int argc, char *argv[]) { + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new(arg_tpm2_device, &c); + if (r < 0) +- return r; ++ return log_error_errno(r, "Failed to create TPM2 context: %m"); + + r = determine_banks(c, target_pcr_nr); + if (r < 0) +@@ -359,7 +359,7 @@ static int run(int argc, char *argv[]) { + + r = tpm2_extend_bytes(c, arg_banks, target_pcr_nr, word, length, NULL, 0); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not extend PCR: %m"); + + log_struct(LOG_INFO, + "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR, +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index e16039590a..96e0183d4f 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -212,13 +212,13 @@ int enroll_tpm2(struct crypt_device *cd, + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; + r = tpm2_context_new(device, &tpm2_context); + if (r < 0) +- return r; ++ return log_error_errno(r, "Failed to create TPM2 context: %m"); + + bool pcr_value_specified = tpm2_pcr_values_has_any_values(hash_pcr_values, n_hash_pcr_values); + + r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not read pcr values: %m"); + + uint16_t hash_pcr_bank = 0; + uint32_t hash_pcr_mask = 0; +@@ -263,7 +263,7 @@ int enroll_tpm2(struct crypt_device *cd, + &srk_buf, + &srk_buf_size); + if (r < 0) +- return r; ++ return log_error_errno(r, "Failed to seal to TPM2: %m"); + + /* Let's see if we already have this specific PCR policy hash enrolled, if so, exit early. */ + r = search_policy_hash(cd, policy.buffer, policy.size); +@@ -295,7 +295,7 @@ int enroll_tpm2(struct crypt_device *cd, + srk_buf, srk_buf_size, + &secret2, &secret2_size); + if (r < 0) +- return r; ++ return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); + + if (memcmp_nn(secret, secret_size, secret2, secret2_size) != 0) + return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 seal/unseal verification failed."); +diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +index af747af613..71334129a6 100644 +--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +@@ -44,11 +44,11 @@ int acquire_luks2_key( + assert(ret_decrypted_key_size); + + if (!device) { +- r = tpm2_find_device_auto(LOG_DEBUG, &auto_device); ++ r = tpm2_find_device_auto(&auto_device); + if (r == -ENODEV) + return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */ + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not find TPM2 device: %m"); + + device = auto_device; + } +@@ -76,11 +76,10 @@ int acquire_luks2_key( + if (pubkey_pcr_mask != 0) { + r = tpm2_load_pcr_signature(signature_path, &signature_json); + if (r < 0) +- return r; ++ return log_error_errno(r, "Failed to load PCR signature: %m"); + } + +- return tpm2_unseal( +- device, ++ r = tpm2_unseal(device, + hash_pcr_mask, + pcr_bank, + pubkey, pubkey_size, +@@ -92,4 +91,8 @@ int acquire_luks2_key( + policy_hash, policy_hash_size, + srk_buf, srk_buf_size, + ret_decrypted_key, ret_decrypted_key_size); ++ if (r < 0) ++ return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); ++ ++ return r; + } +diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c +index 6b650b0d26..ffb399a0b2 100644 +--- a/src/cryptsetup/cryptsetup-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tpm2.c +@@ -89,11 +89,11 @@ int acquire_tpm2_key( + int r; + + if (!device) { +- r = tpm2_find_device_auto(LOG_DEBUG, &auto_device); ++ r = tpm2_find_device_auto(&auto_device); + if (r == -ENODEV) + return -EAGAIN; /* Tell the caller to wait for a TPM2 device to show up */ + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not find TPM2 device: %m"); + + device = auto_device; + } +@@ -124,12 +124,11 @@ int acquire_tpm2_key( + if (pubkey_pcr_mask != 0) { + r = tpm2_load_pcr_signature(signature_path, &signature_json); + if (r < 0) +- return r; ++ return log_error_errno(r, "Failed to load pcr signature: %m"); + } + +- if (!(flags & TPM2_FLAGS_USE_PIN)) +- return tpm2_unseal( +- device, ++ if (!(flags & TPM2_FLAGS_USE_PIN)) { ++ r = tpm2_unseal(device, + hash_pcr_mask, + pcr_bank, + pubkey, pubkey_size, +@@ -145,6 +144,11 @@ int acquire_tpm2_key( + srk_buf_size, + ret_decrypted_key, + ret_decrypted_key_size); ++ if (r < 0) ++ return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); ++ ++ return r; ++ } + + for (int i = 5;; i--) { + _cleanup_(erase_and_freep) char *pin_str = NULL, *b64_salted_pin = NULL; +@@ -187,12 +191,14 @@ int acquire_tpm2_key( + srk_buf_size, + ret_decrypted_key, + ret_decrypted_key_size); +- /* We get this error in case there is an authentication policy mismatch. This should +- * not happen, but this avoids confusing behavior, just in case. */ +- if (IN_SET(r, -EPERM, -ENOLCK)) +- return r; +- if (r < 0) +- continue; ++ if (r < 0) { ++ log_error_errno(r, "Failed to unseal secret using TPM2: %m"); ++ ++ /* We get this error in case there is an authentication policy mismatch. This should ++ * not happen, but this avoids confusing behavior, just in case. */ ++ if (!IN_SET(r, -EPERM, -ENOLCK)) ++ continue; ++ } + + return r; + } +diff --git a/src/cryptsetup/cryptsetup.c b/src/cryptsetup/cryptsetup.c +index d70516c237..3f2cab1e41 100644 +--- a/src/cryptsetup/cryptsetup.c ++++ b/src/cryptsetup/cryptsetup.c +@@ -826,13 +826,13 @@ static int measure_volume_key( + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new(arg_tpm2_device, &c); + if (r < 0) +- return r; ++ return log_error_errno(r, "Failed to create TPM2 context: %m"); + + _cleanup_strv_free_ char **l = NULL; + if (strv_isempty(arg_tpm2_measure_banks)) { + r = tpm2_get_good_pcr_banks_strv(c, UINT32_C(1) << arg_tpm2_measure_pcr, &l); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not verify pcr banks: %m"); + } + + _cleanup_free_ char *joined = strv_join(l ?: arg_tpm2_measure_banks, ", "); +@@ -855,7 +855,7 @@ static int measure_volume_key( + + r = tpm2_extend_bytes(c, l ?: arg_tpm2_measure_banks, arg_tpm2_measure_pcr, s, SIZE_MAX, volume_key, volume_key_size); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not extend PCR: %m"); + + log_struct(LOG_INFO, + "MESSAGE_ID=" SD_MESSAGE_TPM_PCR_EXTEND_STR, +diff --git a/src/partition/repart.c b/src/partition/repart.c +index 611bd541b0..481680768a 100644 +--- a/src/partition/repart.c ++++ b/src/partition/repart.c +@@ -3043,7 +3043,7 @@ static int partition_encrypt( + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; + r = tpm2_context_new(arg_tpm2_device, &tpm2_context); + if (r < 0) +- return r; ++ return log_error_errno(r, "Failed to create TPM2 context: %m"); + + TPM2B_PUBLIC public; + if (pubkey) { +@@ -3054,7 +3054,7 @@ static int partition_encrypt( + + r = tpm2_pcr_read_missing_values(tpm2_context, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not read pcr values: %m"); + + uint16_t hash_pcr_bank = 0; + uint32_t hash_pcr_mask = 0; +@@ -3076,7 +3076,7 @@ static int partition_encrypt( + TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + r = tpm2_calculate_sealing_policy(arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, &public, /* use_pin= */ false, &policy); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not calculate sealing policy digest: %m"); + + r = tpm2_seal(tpm2_context, + &policy, +diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c +index ae7919631b..783fe75ca6 100644 +--- a/src/shared/creds-util.c ++++ b/src/shared/creds-util.c +@@ -651,11 +651,11 @@ int encrypt_credential_and_warn( + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; + r = tpm2_context_new(tpm2_device, &tpm2_context); + if (r < 0) +- return r; ++ return log_error_errno(r, "Failed to create TPM2 context: %m"); + + r = tpm2_get_best_pcr_bank(tpm2_context, tpm2_hash_pcr_mask | tpm2_pubkey_pcr_mask, &tpm2_pcr_bank); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not find best pcr bank: %m"); + + TPML_PCR_SELECTION tpm2_hash_pcr_selection; + tpm2_tpml_pcr_selection_from_mask(tpm2_hash_pcr_mask, tpm2_pcr_bank, &tpm2_hash_pcr_selection); +@@ -664,7 +664,7 @@ int encrypt_credential_and_warn( + size_t tpm2_n_hash_pcr_values; + r = tpm2_pcr_read(tpm2_context, &tpm2_hash_pcr_selection, &tpm2_hash_pcr_values, &tpm2_n_hash_pcr_values); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not read PCR values: %m"); + + TPM2B_PUBLIC public; + if (pubkey) { +@@ -681,7 +681,7 @@ int encrypt_credential_and_warn( + /* use_pin= */ false, + &tpm2_policy); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not calculate sealing policy digest: %m"); + + r = tpm2_seal(tpm2_context, + &tpm2_policy, +@@ -695,7 +695,7 @@ int encrypt_credential_and_warn( + if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) + log_warning("Firmware reported a TPM2 being present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled."); + else if (!sd_id128_equal(with_key, _CRED_AUTO)) +- return r; ++ return log_error_errno(r, "Failed to seal to TPM2: %m"); + + log_notice_errno(r, "TPM2 sealing didn't work, continuing without TPM2: %m"); + } +@@ -927,7 +927,7 @@ int decrypt_credential_and_warn( + if (with_tpm2_pk) { + r = tpm2_load_pcr_signature(tpm2_signature_path, &signature_json); + if (r < 0) +- return r; ++ return log_error_errno(r, "Failed to load pcr signature: %m"); + } + + if (is_tpm2_absent) { +@@ -1046,8 +1046,7 @@ int decrypt_credential_and_warn( + &tpm2_key, + &tpm2_key_size); + if (r < 0) +- return r; +- ++ return log_error_errno(r, "Failed to unseal secret using TPM2: %m"); + #else + return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Credential requires TPM2 support, but TPM2 support not available."); + #endif +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 944bf91019..ffadfa1498 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -188,12 +188,12 @@ static int tpm2_get_capability( + &more, + &capabilities); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to get TPM2 capability 0x%04" PRIx32 " property 0x%04" PRIx32 ": %s", + capability, property, sym_Tss2_RC_Decode(rc)); + + if (capabilities->capability != capability) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "TPM provided wrong capability: 0x%04" PRIx32 " instead of 0x%04" PRIx32 ".", + capabilities->capability, capability); + +@@ -240,7 +240,7 @@ static int tpm2_cache_capabilities(Tpm2Context *c) { + c->n_capability_algorithms, + algorithms.algProperties, + algorithms.count)) +- return log_oom(); ++ return log_oom_debug(); + + if (r == 0) + break; +@@ -273,7 +273,7 @@ static int tpm2_cache_capabilities(Tpm2Context *c) { + c->n_capability_commands, + commands.commandAttributes, + commands.count)) +- return log_oom(); ++ return log_oom_debug(); + + if (r == 0) + break; +@@ -299,7 +299,7 @@ static int tpm2_cache_capabilities(Tpm2Context *c) { + * TPM2_GetCapability states: "TPM_CAP_PCRS – Returns the current allocation of PCR in a + * TPML_PCR_SELECTION. The property parameter shall be zero. The TPM will always respond to + * this command with the full PCR allocation and moreData will be NO." */ +- log_warning("TPM bug: reported multiple PCR sets; using only first set."); ++ log_debug("TPM bug: reported multiple PCR sets; using only first set."); + c->capability_pcrs = capability.assignedPCR; + + return 0; +@@ -408,10 +408,10 @@ static int tpm2_get_capability_handles( + assert(handle_list.count <= max); + + if (n_handles > SIZE_MAX - handle_list.count) +- return log_oom(); ++ return log_oom_debug(); + + if (!GREEDY_REALLOC(handles, n_handles + handle_list.count)) +- return log_oom(); ++ return log_oom_debug(); + + memcpy_safe(&handles[n_handles], handle_list.handle, sizeof(handles[0]) * handle_list.count); + +@@ -537,7 +537,7 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + + context = new(Tpm2Context, 1); + if (!context) +- return log_oom(); ++ return log_oom_debug(); + + *context = (Tpm2Context) { + .n_ref = 1, +@@ -545,7 +545,7 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + + r = dlopen_tpm2(); + if (r < 0) +- return log_error_errno(r, "TPM2 support not installed: %m"); ++ return log_debug_errno(r, "TPM2 support not installed: %m"); + + if (!device) { + device = secure_getenv("SYSTEMD_TPM2_DEVICE"); +@@ -572,7 +572,7 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + /* Syntax #1: Pair of driver string and arbitrary parameter */ + driver = strndupa_safe(device, param - device); + if (isempty(driver)) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 driver name is empty, refusing."); ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 driver name is empty, refusing."); + + param++; + } else if (path_is_absolute(device) && path_is_valid(device)) { +@@ -580,7 +580,7 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + driver = "device"; + param = device; + } else +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid TPM2 driver string, refusing."); ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Invalid TPM2 driver string, refusing."); + + log_debug("Using TPM2 TCTI driver '%s' with device '%s'.", driver, param); + +@@ -588,42 +588,42 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + + /* Better safe than sorry, let's refuse strings that cannot possibly be valid driver early, before going to disk. */ + if (!filename_is_valid(fn)) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 driver name '%s' not valid, refusing.", driver); ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "TPM2 driver name '%s' not valid, refusing.", driver); + + context->tcti_dl = dlopen(fn, RTLD_NOW); + if (!context->tcti_dl) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to load %s: %s", fn, dlerror()); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to load %s: %s", fn, dlerror()); + + func = dlsym(context->tcti_dl, TSS2_TCTI_INFO_SYMBOL); + if (!func) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to find TCTI info symbol " TSS2_TCTI_INFO_SYMBOL ": %s", + dlerror()); + + info = func(); + if (!info) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to get TCTI info data."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Unable to get TCTI info data."); + + log_debug("Loaded TCTI module '%s' (%s) [Version %" PRIu32 "]", info->name, info->description, info->version); + + rc = info->init(NULL, &sz, NULL); + if (rc != TPM2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc)); + + context->tcti_context = malloc0(sz); + if (!context->tcti_context) +- return log_oom(); ++ return log_oom_debug(); + + rc = info->init(context->tcti_context, &sz, param); + if (rc != TPM2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to initialize TCTI context: %s", sym_Tss2_RC_Decode(rc)); + } + + rc = sym_Esys_Initialize(&context->esys_context, context->tcti_context, NULL); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to initialize TPM context: %s", sym_Tss2_RC_Decode(rc)); + + rc = sym_Esys_Startup(context->esys_context, TPM2_SU_CLEAR); +@@ -632,22 +632,22 @@ int tpm2_context_new(const char *device, Tpm2Context **ret_context) { + else if (rc == TSS2_RC_SUCCESS) + log_debug("TPM successfully started up."); + else +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to start up TPM: %s", sym_Tss2_RC_Decode(rc)); + + r = tpm2_cache_capabilities(context); + if (r < 0) +- return r; ++ return log_debug_errno(r, "Failed to cache TPM capbilities: %m"); + + /* We require AES and CFB support for session encryption. */ + if (!tpm2_supports_alg(context, TPM2_ALG_AES)) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES."); + + if (!tpm2_supports_alg(context, TPM2_ALG_CFB)) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support CFB."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support CFB."); + + if (!tpm2_supports_tpmt_sym_def(context, &SESSION_TEMPLATE_SYM_AES_128_CFB)) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES-128-CFB."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM does not support AES-128-CFB."); + + *ret_context = TAKE_PTR(context); + +@@ -693,7 +693,7 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle) { + + handle = new(Tpm2Handle, 1); + if (!handle) +- return log_oom(); ++ return log_oom_debug(); + + *handle = (Tpm2Handle) { + .tpm2_context = tpm2_context_ref(context), +@@ -732,17 +732,17 @@ int tpm2_index_to_handle( + case TPM2_HT_TRANSIENT: + break; + case TPM2_HT_PCR: +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid handle 0x%08" PRIx32 " (in PCR range).", index); + case TPM2_HT_HMAC_SESSION: + case TPM2_HT_POLICY_SESSION: +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid handle 0x%08" PRIx32 " (in session range).", index); + case TPM2_HT_PERMANENT: /* Permanent handles are defined, e.g. ESYS_TR_RH_OWNER. */ +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid handle 0x%08" PRIx32 " (in permanent range).", index); + default: +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid handle 0x%08" PRIx32 " (in unknown range).", index); + } + +@@ -779,7 +779,7 @@ int tpm2_index_to_handle( + ESYS_TR_NONE, + &handle->esys_handle); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to read public info: %s", sym_Tss2_RC_Decode(rc)); + + if (ret_public || ret_name || ret_qname) { +@@ -805,12 +805,12 @@ int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE + /* Esys_TR_GetTpmHandle was added to tpm2-tss in version 2.4.0. Once we can set a minimum tpm2-tss + * version of 2.4.0 this check can be removed. */ + if (!sym_Esys_TR_GetTpmHandle) +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "libtss2-esys too old, does not include Esys_TR_GetTpmHandle."); + + rc = sym_Esys_TR_GetTpmHandle(c->esys_context, handle->esys_handle, ret_index); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to get handle index: %s", sym_Tss2_RC_Decode(rc)); + + return 0; +@@ -882,7 +882,7 @@ static int tpm2_persist_handle( + return 1; + } + if (rc != TPM2_RC_NV_DEFINED) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to persist handle: %s", sym_Tss2_RC_Decode(rc)); + } + +@@ -928,16 +928,16 @@ static int tpm2_credit_random(Tpm2Context *c) { + MIN(rps, 32U), /* 32 is supposedly a safe choice, given that AES 256bit keys are this long, and TPM2 baseline requires support for those. */ + &buffer); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to acquire entropy from TPM: %s", sym_Tss2_RC_Decode(rc)); + + if (buffer->size == 0) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Zero-sized entropy returned from TPM."); + + r = random_write_entropy(-1, buffer->buffer, buffer->size, /* credit= */ false); + if (r < 0) +- return log_error_errno(r, "Failed wo write entropy to kernel: %m"); ++ return log_debug_errno(r, "Failed wo write entropy to kernel: %m"); + + done += buffer->size; + rps = LESS_BY(rps, buffer->size); +@@ -975,7 +975,7 @@ int tpm2_read_public( + ret_name, + ret_qname); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to read public info: %s", sym_Tss2_RC_Decode(rc)); + + return 0; +@@ -1188,7 +1188,7 @@ static int tpm2_get_or_create_srk( + TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), }; + r = tpm2_get_best_srk_template(c, &template.publicArea); + if (r < 0) +- return log_error_errno(r, "Could not get best SRK template: %m"); ++ return log_debug_errno(r, "Could not get best SRK template: %m"); + + _cleanup_(tpm2_handle_freep) Tpm2Handle *transient_handle = NULL; + r = tpm2_create_primary( +@@ -1214,7 +1214,7 @@ static int tpm2_get_or_create_srk( + return r; + if (r == 0) + /* This should never happen. */ +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "SRK we just persisted couldn't be found."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "SRK we just persisted couldn't be found."); + + return 0; + } +@@ -1238,8 +1238,8 @@ void tpm2_tpms_pcr_selection_from_mask(uint32_t mask, TPMI_ALG_HASH hash_alg, TP + + /* This is currently hardcoded at 24 PCRs, above. */ + if (!TPM2_PCR_MASK_VALID(mask)) +- log_warning("PCR mask selections (%x) out of range, ignoring.", +- mask & ~((uint32_t)TPM2_PCRS_MASK)); ++ log_debug("PCR mask selections (%x) out of range, ignoring.", ++ mask & ~((uint32_t)TPM2_PCRS_MASK)); + + *ret = (TPMS_PCR_SELECTION){ + .hash = hash_alg, +@@ -1752,22 +1752,22 @@ int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value) { + _cleanup_free_ char *index = NULL; + r = extract_first_word(&p, &index, ":", /* flags= */ 0); + if (r < 1) +- return log_error_errno(r, "Could not parse pcr value '%s': %m", p); ++ return log_debug_errno(r, "Could not parse pcr value '%s': %m", p); + + r = pcr_index_from_string(index); + if (r < 0) +- return log_error_errno(r, "Invalid pcr index '%s': %m", index); ++ return log_debug_errno(r, "Invalid pcr index '%s': %m", index); + pcr_value.index = (unsigned) r; + + if (!isempty(p)) { + _cleanup_free_ char *hash = NULL; + r = extract_first_word(&p, &hash, "=", /* flags= */ 0); + if (r < 1) +- return log_error_errno(r, "Could not parse pcr hash algorithm '%s': %m", p); ++ return log_debug_errno(r, "Could not parse pcr hash algorithm '%s': %m", p); + + r = tpm2_hash_alg_from_string(hash); + if (r < 0) +- return log_error_errno(r, "Invalid pcr hash algorithm '%s': %m", hash); ++ return log_debug_errno(r, "Invalid pcr hash algorithm '%s': %m", hash); + pcr_value.hash = (TPMI_ALG_HASH) r; + + if (!isempty(p)) { +@@ -1778,11 +1778,11 @@ int tpm2_pcr_value_from_string(const char *arg, Tpm2PCRValue *ret_pcr_value) { + size_t buf_size = 0; + r = unhexmem(p, SIZE_MAX, &buf, &buf_size); + if (r < 0) +- return log_error_errno(r, "Invalid pcr hash value '%s': %m", p); ++ return log_debug_errno(r, "Invalid pcr hash value '%s': %m", p); + + r = TPM2B_DIGEST_CHECK_SIZE(buf_size); + if (r < 0) +- return log_error_errno(r, "PCR hash value size %zu too large.", buf_size); ++ return log_debug_errno(r, "PCR hash value size %zu too large.", buf_size); + + pcr_value.value = TPM2B_DIGEST_MAKE(buf, buf_size); + } +@@ -1832,7 +1832,7 @@ int tpm2_pcr_values_from_string(const char *arg, Tpm2PCRValue **ret_pcr_values, + _cleanup_free_ char *pcr_arg = NULL; + r = extract_first_word(&p, &pcr_arg, ",+", /* flags= */ 0); + if (r < 0) +- return log_error_errno(r, "Could not parse pcr values '%s': %m", p); ++ return log_debug_errno(r, "Could not parse pcr values '%s': %m", p); + if (r == 0) + break; + +@@ -1842,7 +1842,7 @@ int tpm2_pcr_values_from_string(const char *arg, Tpm2PCRValue **ret_pcr_values, + return r; + + if (!GREEDY_REALLOC_APPEND(pcr_values, n_pcr_values, &pcr_value, 1)) +- return log_oom(); ++ return log_oom_debug(); + } + + *ret_pcr_values = TAKE_PTR(pcr_values); +@@ -1923,7 +1923,7 @@ static int tpm2_get_policy_digest( + ESYS_TR_NONE, + &policy_digest); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to get policy digest from TPM: %s", sym_Tss2_RC_Decode(rc)); + + tpm2_log_debug_digest(policy_digest, "Session policy digest"); +@@ -1975,7 +1975,7 @@ int tpm2_create_primary( + /* creationHash= */ NULL, + /* creationTicket= */ NULL); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to generate primary key in TPM: %s", + sym_Tss2_RC_Decode(rc)); + +@@ -2046,7 +2046,7 @@ int tpm2_create(Tpm2Context *c, + /* creationHash= */ NULL, + /* creationTicket= */ NULL); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to generate object in TPM: %s", + sym_Tss2_RC_Decode(rc)); + +@@ -2094,10 +2094,10 @@ static int tpm2_load( + public, + &handle->esys_handle); + if (rc == TPM2_RC_LOCKOUT) +- return log_error_errno(SYNTHETIC_ERRNO(ENOLCK), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOLCK), + "TPM2 device is in dictionary attack lockout mode."); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to load key into TPM: %s", sym_Tss2_RC_Decode(rc)); + + *ret_handle = TAKE_PTR(handle); +@@ -2141,7 +2141,7 @@ static int tpm2_load_external( + #endif + &handle->esys_handle); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to load public key into TPM: %s", sym_Tss2_RC_Decode(rc)); + + *ret_handle = TAKE_PTR(handle); +@@ -2185,7 +2185,7 @@ static int _tpm2_create_loaded( + sizeof(tpm2b_template.buffer), + &size); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal public key template: %s", sym_Tss2_RC_Decode(rc)); + assert(size <= UINT16_MAX); + tpm2b_template.size = size; +@@ -2218,7 +2218,7 @@ static int _tpm2_create_loaded( + &private, + &public); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to generate loaded object in TPM: %s", + sym_Tss2_RC_Decode(rc)); + +@@ -2314,13 +2314,13 @@ int tpm2_pcr_read( + ¤t_read, + ¤t_values); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to read TPM2 PCRs: %s", sym_Tss2_RC_Decode(rc)); + + tpm2_log_debug_tpml_pcr_selection(current_read, "Read PCR selection"); + + if (tpm2_tpml_pcr_selection_is_empty(current_read)) { +- log_warning("TPM2 refused to read possibly unimplemented PCRs, ignoring."); ++ log_debug("TPM2 refused to read possibly unimplemented PCRs, ignoring."); + break; + } + +@@ -2336,7 +2336,7 @@ int tpm2_pcr_read( + tpm2_log_debug_pcr_value(&pcr_value, /* msg= */ NULL); + + if (!GREEDY_REALLOC_APPEND(pcr_values, n_pcr_values, &pcr_value, 1)) +- return log_oom(); ++ return log_oom_debug(); + } + assert(i == current_values->count); + +@@ -2346,7 +2346,7 @@ int tpm2_pcr_read( + tpm2_sort_pcr_values(pcr_values, n_pcr_values); + + if (!tpm2_pcr_values_valid(pcr_values, n_pcr_values)) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PCR values read from TPM are not valid."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "PCR values read from TPM are not valid."); + + *ret_pcr_values = TAKE_PTR(pcr_values); + *ret_n_pcr_values = n_pcr_values; +@@ -2369,7 +2369,7 @@ int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_ + size_t hash_count; + r = tpm2_pcr_values_hash_count(pcr_values, n_pcr_values, &hash_count); + if (r < 0) +- return log_error_errno(r, "Could not get hash count from pcr values: %m"); ++ return log_debug_errno(r, "Could not get hash count from pcr values: %m"); + + if (hash_count == 1 && pcr_values[0].hash == 0) { + uint32_t mask; +@@ -2402,7 +2402,7 @@ int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_ + return r; + + if (n_read_values == 0) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Could not read PCR hash 0x%" PRIu16 " index %u", + v->hash, v->index); + +@@ -2551,7 +2551,7 @@ int tpm2_get_best_pcr_bank( + log_notice("TPM2 device lacks support for SHA256 bank, but SHA1 bank is supported, but none of the selected PCRs are valid! Firmware apparently did not initialize any of the selected PCRs. Proceeding anyway with SHA1 bank. PCR policy effectively unenforced!"); + *ret = TPM2_ALG_SHA1; + } else +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 module supports neither SHA1 nor SHA256 PCR banks, cannot operate."); + + return 0; +@@ -2585,16 +2585,16 @@ int tpm2_get_good_pcr_banks( + return r; + + if (n_good_banks + n_fallback_banks >= INT_MAX) +- return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many good TPM2 banks?"); ++ return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Too many good TPM2 banks?"); + + if (r) { + if (!GREEDY_REALLOC(good_banks, n_good_banks+1)) +- return log_oom(); ++ return log_oom_debug(); + + good_banks[n_good_banks++] = hash; + } else { + if (!GREEDY_REALLOC(fallback_banks, n_fallback_banks+1)) +- return log_oom(); ++ return log_oom_debug(); + + fallback_banks[n_fallback_banks++] = hash; + } +@@ -2642,26 +2642,26 @@ int tpm2_get_good_pcr_banks_strv( + + salg = tpm2_hash_alg_to_string(*a); + if (!salg) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unknown PCR algorithm, can't measure."); + + implementation = EVP_get_digestbyname(salg); + if (!implementation) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unsupported PCR algorithm, can't measure."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "TPM2 operates with unsupported PCR algorithm, can't measure."); + + n = strdup(ASSERT_PTR(EVP_MD_name(implementation))); + if (!n) +- return log_oom(); ++ return log_oom_debug(); + + ascii_strlower(n); /* OpenSSL uses uppercase digest names, we prefer them lower case. */ + + if (strv_consume(&l, TAKE_PTR(n)) < 0) +- return log_oom(); ++ return log_oom_debug(); + } + + *ret = TAKE_PTR(l); + return 0; + #else /* HAVE_OPENSSL */ +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); + #endif + } + +@@ -2689,11 +2689,11 @@ int tpm2_digest_many( + assert(data || n_data == 0); + + if (alg != TPM2_ALG_SHA256) +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Hash algorithm not supported: 0x%x", alg); + + if (extend && digest->size != SHA256_DIGEST_SIZE) +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Digest size 0x%x, require 0x%x", + digest->size, (unsigned)SHA256_DIGEST_SIZE); + +@@ -2734,7 +2734,7 @@ int tpm2_digest_many_digests( + + iovecs = new(struct iovec, n_data); + if (!iovecs) +- return log_oom(); ++ return log_oom_debug(); + + for (size_t i = 0; i < n_data; i++) + iovecs[i] = IOVEC_MAKE((void*) data[i].buffer, data[i].size); +@@ -2761,7 +2761,7 @@ static int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *p + + rc = sym_Esys_TR_SetAuth(c->esys_context, handle->esys_handle, &auth); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to load PIN in TPM: %s", sym_Tss2_RC_Decode(rc)); + + return 0; +@@ -2818,7 +2818,7 @@ static int tpm2_make_encryption_session( + TPM2_ALG_SHA256, + &session->esys_handle); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc)); + + /* Enable parameter encryption/decryption with AES in CFB mode. Together with HMAC digests (which are +@@ -2826,10 +2826,8 @@ static int tpm2_make_encryption_session( + * operations that use this session. */ + rc = sym_Esys_TRSess_SetAttributes(c->esys_context, session->esys_handle, sessionAttributes, 0xff); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno( +- SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to configure TPM session: %s", +- sym_Tss2_RC_Decode(rc)); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to configure TPM session: %s", sym_Tss2_RC_Decode(rc)); + + *ret_session = TAKE_PTR(session); + +@@ -2853,7 +2851,7 @@ static int tpm2_make_policy_session( + assert(ret_session); + + if (!tpm2_is_encryption_session(c, encryption_session)) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Missing encryption session"); + + log_debug("Starting policy session."); +@@ -2876,7 +2874,7 @@ static int tpm2_make_policy_session( + TPM2_ALG_SHA256, + &session->esys_handle); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to open session in TPM: %s", sym_Tss2_RC_Decode(rc)); + + *ret_session = TAKE_PTR(session); +@@ -2903,22 +2901,22 @@ static int find_signature( + * public key, and policy digest. */ + + if (!json_variant_is_object(v)) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Signature is not a JSON object."); ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Signature is not a JSON object."); + + uint16_t pcr_bank = pcr_selection->pcrSelections[0].hash; + uint32_t pcr_mask = tpm2_tpml_pcr_selection_to_mask(pcr_selection, pcr_bank); + + k = tpm2_hash_alg_to_string(pcr_bank); + if (!k) +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Don't know PCR bank %" PRIu16, pcr_bank); ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Don't know PCR bank %" PRIu16, pcr_bank); + + /* First, find field by bank */ + b = json_variant_by_key(v, k); + if (!b) +- return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Signature lacks data for PCR bank '%s'.", k); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "Signature lacks data for PCR bank '%s'.", k); + + if (!json_variant_is_array(b)) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Bank data is not a JSON array."); ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Bank data is not a JSON array."); + + /* Now iterate through all signatures known for this bank */ + JSON_VARIANT_ARRAY_FOREACH(i, b) { +@@ -2928,7 +2926,7 @@ static int find_signature( + uint32_t parsed_mask; + + if (!json_variant_is_object(i)) +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), "Bank data element is not a JSON object"); ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Bank data element is not a JSON object"); + + /* Check if the PCR mask matches our expectations */ + maskj = json_variant_by_key(i, "pcrs"); +@@ -2937,7 +2935,7 @@ static int find_signature( + + r = tpm2_parse_pcr_json_array(maskj, &parsed_mask); + if (r < 0) +- return log_error_errno(r, "Failed to parse JSON PCR mask"); ++ return log_debug_errno(r, "Failed to parse JSON PCR mask"); + + if (parsed_mask != pcr_mask) + continue; /* Not for this PCR mask */ +@@ -2949,7 +2947,7 @@ static int find_signature( + + r = json_variant_unhex(fpj, &fpj_data, &fpj_size); + if (r < 0) +- return log_error_errno(r, "Failed to decode fingerprint in JSON data: %m"); ++ return log_debug_errno(r, "Failed to decode fingerprint in JSON data: %m"); + + if (memcmp_nn(fp, fp_size, fpj_data, fpj_size) != 0) + continue; /* Not for this public key */ +@@ -2961,7 +2959,7 @@ static int find_signature( + + r = json_variant_unhex(polj, &polj_data, &polj_size); + if (r < 0) +- return log_error_errno(r, "Failed to decode policy hash JSON data: %m"); ++ return log_debug_errno(r, "Failed to decode policy hash JSON data: %m"); + + if (memcmp_nn(policy, policy_size, polj_data, polj_size) != 0) + continue; +@@ -2974,9 +2972,9 @@ static int find_signature( + return json_variant_unbase64(sigj, ret_signature, ret_signature_size); + } + +- return log_error_errno(SYNTHETIC_ERRNO(ENXIO), "Couldn't find signature for this PCR bank, PCR index and public key."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENXIO), "Couldn't find signature for this PCR bank, PCR index and public key."); + #else /* HAVE_OPENSSL */ +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); + #endif + } + +@@ -2999,10 +2997,10 @@ int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name) { + + r = dlopen_tpm2(); + if (r < 0) +- return log_error_errno(r, "TPM2 support not installed: %m"); ++ return log_debug_errno(r, "TPM2 support not installed: %m"); + + if (public->nameAlg != TPM2_ALG_SHA256) +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "Unsupported nameAlg: 0x%x", + public->nameAlg); + +@@ -3011,11 +3009,11 @@ int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name) { + + buf = (uint8_t*) new(TPMT_PUBLIC, 1); + if (!buf) +- return log_oom(); ++ return log_oom_debug(); + + rc = sym_Tss2_MU_TPMT_PUBLIC_Marshal(public, buf, sizeof(TPMT_PUBLIC), &size); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal public key: %s", sym_Tss2_RC_Decode(rc)); + + TPM2B_DIGEST digest = {}; +@@ -3033,7 +3031,7 @@ int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name) { + size = 0; + rc = sym_Tss2_MU_TPMT_HA_Marshal(&ha, name.name, sizeof(name.name), &size); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal key name: %s", sym_Tss2_RC_Decode(rc)); + name.size = size; + +@@ -3064,7 +3062,7 @@ static int tpm2_get_name( + + rc = sym_Esys_TR_GetName(c->esys_context, handle->esys_handle, &name); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to get name of public key from TPM: %s", sym_Tss2_RC_Decode(rc)); + + tpm2_log_debug_name(name, "Object name"); +@@ -3085,18 +3083,18 @@ int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest) { + + r = dlopen_tpm2(); + if (r < 0) +- return log_error_errno(r, "TPM2 support not installed: %m"); ++ return log_debug_errno(r, "TPM2 support not installed: %m"); + + uint8_t buf[sizeof(command)]; + size_t offset = 0; + + rc = sym_Tss2_MU_TPM2_CC_Marshal(command, buf, sizeof(buf), &offset); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal PolicyAuthValue command: %s", sym_Tss2_RC_Decode(rc)); + + if (offset != sizeof(command)) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Offset 0x%zx wrong after marshalling PolicyAuthValue command", offset); + + r = tpm2_digest_buffer(TPM2_ALG_SHA256, digest, buf, offset, /* extend= */ true); +@@ -3127,7 +3125,7 @@ static int tpm2_policy_auth_value( + ESYS_TR_NONE, + ESYS_TR_NONE); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to add authValue policy to TPM: %s", + sym_Tss2_RC_Decode(rc)); + +@@ -3150,14 +3148,14 @@ int tpm2_calculate_policy_pcr( + + r = dlopen_tpm2(); + if (r < 0) +- return log_error_errno(r, "TPM2 support not installed: %m"); ++ return log_debug_errno(r, "TPM2 support not installed: %m"); + + TPML_PCR_SELECTION pcr_selection; + _cleanup_free_ TPM2B_DIGEST *values = NULL; + size_t n_values; + r = tpm2_tpml_pcr_selection_from_pcr_values(pcr_values, n_pcr_values, &pcr_selection, &values, &n_values); + if (r < 0) +- return log_error_errno(r, "Could not convert PCR values to TPML_PCR_SELECTION: %m"); ++ return log_debug_errno(r, "Could not convert PCR values to TPML_PCR_SELECTION: %m"); + + TPM2B_DIGEST hash = {}; + r = tpm2_digest_many_digests(TPM2_ALG_SHA256, &hash, values, n_values, /* extend= */ false); +@@ -3169,16 +3167,16 @@ int tpm2_calculate_policy_pcr( + + buf = malloc(maxsize); + if (!buf) +- return log_oom(); ++ return log_oom_debug(); + + rc = sym_Tss2_MU_TPM2_CC_Marshal(command, buf, maxsize, &size); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal PolicyPCR command: %s", sym_Tss2_RC_Decode(rc)); + + rc = sym_Tss2_MU_TPML_PCR_SELECTION_Marshal(&pcr_selection, buf, maxsize, &size); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal PCR selection: %s", sym_Tss2_RC_Decode(rc)); + + struct iovec data[] = { +@@ -3217,7 +3215,7 @@ static int tpm2_policy_pcr( + NULL, + pcr_selection); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to add PCR policy to TPM: %s", sym_Tss2_RC_Decode(rc)); + + return tpm2_get_policy_digest(c, session, ret_policy_digest); +@@ -3239,18 +3237,18 @@ int tpm2_calculate_policy_authorize( + + r = dlopen_tpm2(); + if (r < 0) +- return log_error_errno(r, "TPM2 support not installed: %m"); ++ return log_debug_errno(r, "TPM2 support not installed: %m"); + + uint8_t buf[sizeof(command)]; + size_t offset = 0; + + rc = sym_Tss2_MU_TPM2_CC_Marshal(command, buf, sizeof(buf), &offset); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal PolicyAuthorize command: %s", sym_Tss2_RC_Decode(rc)); + + if (offset != sizeof(command)) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Offset 0x%zx wrong after marshalling PolicyAuthorize command", offset); + + TPM2B_NAME name = {}; +@@ -3350,7 +3348,7 @@ static int tpm2_policy_authorize( + + r = TPM2B_PUBLIC_KEY_RSA_CHECK_SIZE(signature_size); + if (r < 0) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Signature larger than buffer."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Signature larger than buffer."); + + TPMT_SIGNATURE policy_signature = { + .sigAlg = TPM2_ALG_RSASSA, +@@ -3370,7 +3368,7 @@ static int tpm2_policy_authorize( + &policy_signature, + &check_ticket_buffer); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to validate signature in TPM: %s", sym_Tss2_RC_Decode(rc)); + + check_ticket = check_ticket_buffer; +@@ -3395,7 +3393,7 @@ static int tpm2_policy_authorize( + pubkey_name, + check_ticket); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to push Authorize policy into TPM: %s", sym_Tss2_RC_Decode(rc)); + + return tpm2_get_policy_digest(c, session, ret_policy_digest); +@@ -3461,7 +3459,7 @@ static int tpm2_build_sealing_policy( + if (r < 0) + return r; + if (r == 0) +- log_warning("Selected TPM2 PCRs are not initialized on this system."); ++ log_debug("Selected TPM2 PCRs are not initialized on this system."); + } + + if (pubkey_pcr_mask != 0) { +@@ -3692,7 +3690,7 @@ int tpm2_tpm2b_public_to_fingerprint( + /* Hardcode fingerprint to SHA256 */ + return pubkey_fingerprint(pkey, EVP_sha256(), ret_fingerprint, ret_fingerprint_size); + #else +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); + #endif + } + +@@ -3710,7 +3708,7 @@ int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *r + + return tpm2_tpm2b_public_from_openssl_pkey(pkey, ret); + #else +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); + #endif + } + +@@ -3806,7 +3804,7 @@ static int tpm2_serialize( + size_t size = 0; + rc = sym_Esys_TR_Serialize(c->esys_context, handle->esys_handle, &serialized, &size); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to serialize: %s", sym_Tss2_RC_Decode(rc)); + + *ret_serialized = TAKE_PTR(serialized); +@@ -3838,7 +3836,7 @@ static int tpm2_deserialize( + + rc = sym_Esys_TR_Deserialize(c->esys_context, serialized, serialized_size, &handle->esys_handle); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to deserialize: %s", sym_Tss2_RC_Decode(rc)); + + *ret_handle = TAKE_PTR(handle); +@@ -3915,7 +3913,7 @@ int tpm2_seal(Tpm2Context *c, + + r = crypto_random_bytes(hmac_sensitive.data.buffer, hmac_sensitive.data.size); + if (r < 0) +- return log_error_errno(r, "Failed to generate secret key: %m"); ++ return log_debug_errno(r, "Failed to generate secret key: %m"); + + _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; + if (ret_srk_buf) { +@@ -3938,17 +3936,17 @@ int tpm2_seal(Tpm2Context *c, + TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), }; + r = tpm2_get_legacy_template(primary_alg, &template.publicArea); + if (r < 0) +- return log_error_errno(r, "Could not get legacy ECC template: %m"); ++ return log_debug_errno(r, "Could not get legacy ECC template: %m"); + + if (!tpm2_supports_tpmt_public(c, &template.publicArea)) { + primary_alg = TPM2_ALG_RSA; + + r = tpm2_get_legacy_template(primary_alg, &template.publicArea); + if (r < 0) +- return log_error_errno(r, "Could not get legacy RSA template: %m"); ++ return log_debug_errno(r, "Could not get legacy RSA template: %m"); + + if (!tpm2_supports_tpmt_public(c, &template.publicArea)) +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM does not support either ECC or RSA legacy template."); + } + +@@ -3977,7 +3975,7 @@ int tpm2_seal(Tpm2Context *c, + _cleanup_(erase_and_freep) void *secret = NULL; + secret = memdup(hmac_sensitive.data.buffer, hmac_sensitive.data.size); + if (!secret) +- return log_oom(); ++ return log_oom_debug(); + + log_debug("Marshalling private and public part of HMAC key."); + +@@ -3985,7 +3983,7 @@ int tpm2_seal(Tpm2Context *c, + size_t blob_size; + r = tpm2_marshal_blob(public, private, &blob, &blob_size); + if (r < 0) +- return log_error_errno(r, "Could not create sealed blob: %m"); ++ return log_debug_errno(r, "Could not create sealed blob: %m"); + + if (DEBUG_LOGGING) + log_debug("Completed TPM2 key sealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); +@@ -4005,7 +4003,7 @@ int tpm2_seal(Tpm2Context *c, + */ + srk_buf = memdup(tmp, srk_buf_size); + if (!srk_buf) +- return log_oom(); ++ return log_oom_debug(); + + *ret_srk_buf = TAKE_PTR(srk_buf); + *ret_srk_buf_size = srk_buf_size; +@@ -4057,7 +4055,7 @@ int tpm2_unseal(const char *device, + + r = dlopen_tpm2(); + if (r < 0) +- return log_error_errno(r, "TPM2 support is not installed."); ++ return r; + + /* So here's what we do here: We connect to the TPM2 chip. As we do when sealing we generate a + * "primary" key on the TPM2 chip, with the same parameters as well as a PCR-bound policy session. +@@ -4073,7 +4071,7 @@ int tpm2_unseal(const char *device, + TPM2B_PRIVATE private; + r = tpm2_unmarshal_blob(blob, blob_size, &public, &private); + if (r < 0) +- return log_error_errno(r, "Could not extract parts from blob: %m"); ++ return log_debug_errno(r, "Could not extract parts from blob: %m"); + + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; + r = tpm2_context_new(device, &c); +@@ -4089,7 +4087,7 @@ int tpm2_unseal(const char *device, + TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), }; + r = tpm2_get_legacy_template(primary_alg, &template.publicArea); + if (r < 0) +- return log_error_errno(r, "Could not get legacy template: %m"); ++ return log_debug_errno(r, "Could not get legacy template: %m"); + + r = tpm2_create_primary( + c, +@@ -4101,7 +4099,7 @@ int tpm2_unseal(const char *device, + if (r < 0) + return r; + } else +- return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "No SRK or primary alg provided."); + + log_debug("Loading HMAC key into TPM."); +@@ -4123,11 +4121,11 @@ int tpm2_unseal(const char *device, + if (pubkey) { + r = tpm2_tpm2b_public_from_pem(pubkey, pubkey_size, &pubkey_tpm2b); + if (r < 0) +- return log_error_errno(r, "Could not create TPMT_PUBLIC: %m"); ++ return log_debug_errno(r, "Could not create TPMT_PUBLIC: %m"); + + r = tpm2_tpm2b_public_to_fingerprint(&pubkey_tpm2b, &fp, &fp_size); + if (r < 0) +- return log_error_errno(r, "Could not get key fingerprint: %m"); ++ return log_debug_errno(r, "Could not get key fingerprint: %m"); + } + + /* +@@ -4177,7 +4175,7 @@ int tpm2_unseal(const char *device, + * wait until the TPM2 tells us to go away. */ + if (known_policy_hash_size > 0 && + memcmp_nn(policy_digest->buffer, policy_digest->size, known_policy_hash, known_policy_hash_size) != 0) +- return log_error_errno(SYNTHETIC_ERRNO(EPERM), ++ return log_debug_errno(SYNTHETIC_ERRNO(EPERM), + "Current policy digest does not match stored policy digest, cancelling " + "TPM2 authentication attempt."); + +@@ -4193,7 +4191,7 @@ int tpm2_unseal(const char *device, + if (rc == TSS2_RC_SUCCESS) + break; + if (rc != TPM2_RC_PCR_CHANGED || i == 0) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to unseal HMAC key in TPM: %s", sym_Tss2_RC_Decode(rc)); + log_debug("A PCR value changed during the TPM2 policy session, restarting HMAC key unsealing (%u tries left).", i); + } +@@ -4202,7 +4200,7 @@ int tpm2_unseal(const char *device, + secret = memdup(unsealed->buffer, unsealed->size); + explicit_bzero_safe(unsealed->buffer, unsealed->size); + if (!secret) +- return log_oom(); ++ return log_oom_debug(); + + if (DEBUG_LOGGING) + log_debug("Completed TPM2 key unsealing in %s.", FORMAT_TIMESPAN(now(CLOCK_MONOTONIC) - start, 1)); +@@ -4290,21 +4288,18 @@ int tpm2_list_devices(void) { + #endif + } + +-int tpm2_find_device_auto( +- int log_level, /* log level when no device is found */ +- char **ret) { ++int tpm2_find_device_auto(char **ret) { + #if HAVE_TPM2 + _cleanup_(closedirp) DIR *d = NULL; + int r; + + r = dlopen_tpm2(); + if (r < 0) +- return log_error_errno(r, "TPM2 support is not installed."); ++ return log_debug_errno(r, "TPM2 support is not installed."); + + d = opendir("/sys/class/tpmrm"); + if (!d) { +- log_full_errno(errno == ENOENT ? LOG_DEBUG : LOG_ERR, errno, +- "Failed to open /sys/class/tpmrm: %m"); ++ log_debug_errno(errno, "Failed to open /sys/class/tpmrm: %m"); + if (errno != ENOENT) + return -errno; + } else { +@@ -4318,12 +4313,12 @@ int tpm2_find_device_auto( + break; + + if (node) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTUNIQ), ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTUNIQ), + "More than one TPM2 (tpmrm) device found."); + + node = path_join("/dev", de->d_name); + if (!node) +- return log_oom(); ++ return log_oom_debug(); + } + + if (node) { +@@ -4332,9 +4327,9 @@ int tpm2_find_device_auto( + } + } + +- return log_full_errno(log_level, SYNTHETIC_ERRNO(ENODEV), "No TPM2 (tpmrm) device found."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENODEV), "No TPM2 (tpmrm) device found."); + #else +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 not supported on this build."); + #endif + } +@@ -4363,7 +4358,7 @@ int tpm2_extend_bytes( + secret_size = strlen(secret); + + if (pcr_index >= TPM2_PCRS_MAX) +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Can't measure into unsupported PCR %u, refusing.", pcr_index); ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Can't measure into unsupported PCR %u, refusing.", pcr_index); + + if (strv_isempty(banks)) + return 0; +@@ -4375,14 +4370,14 @@ int tpm2_extend_bytes( + assert_se(implementation = EVP_get_digestbyname(*bank)); + + if (values.count >= ELEMENTSOF(values.digests)) +- return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Too many banks selected."); ++ return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Too many banks selected."); + + if ((size_t) EVP_MD_size(implementation) > sizeof(values.digests[values.count].digest)) +- return log_error_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2."); ++ return log_debug_errno(SYNTHETIC_ERRNO(E2BIG), "Hash result too large for TPM2."); + + id = tpm2_hash_alg_from_string(EVP_MD_name(implementation)); + if (id < 0) +- return log_error_errno(id, "Can't map hash name to TPM2."); ++ return log_debug_errno(id, "Can't map hash name to TPM2."); + + values.digests[values.count].hashAlg = id; + +@@ -4394,9 +4389,9 @@ int tpm2_extend_bytes( + * private non-secret string instead. */ + if (secret_size > 0) { + if (!HMAC(implementation, secret, secret_size, data, data_size, (unsigned char*) &values.digests[values.count].digest, NULL)) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to calculate HMAC of data to measure."); + } else if (EVP_Digest(data, data_size, (unsigned char*) &values.digests[values.count].digest, NULL, implementation, NULL) != 1) +- return log_error_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data to measure."); ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "Failed to hash data to measure."); + + values.count++; + } +@@ -4409,7 +4404,7 @@ int tpm2_extend_bytes( + ESYS_TR_NONE, + &values); + if (rc != TSS2_RC_SUCCESS) +- return log_error_errno( ++ return log_debug_errno( + SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to measure into PCR %u: %s", + pcr_index, +@@ -4417,7 +4412,7 @@ int tpm2_extend_bytes( + + return 0; + #else /* HAVE_OPENSSL */ +- return log_error_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); + #endif + } + #endif +@@ -4838,6 +4833,9 @@ static void tpm2_pcr_values_apply_default_hash_alg(Tpm2PCRValue *pcr_values, siz + } + #endif + ++/* The following tpm2_parse_pcr_argument*() functions all log errors, to match the behavior of system-wide ++ * parse_*_argument() functions. */ ++ + /* Parse the PCR selection/value arg(s) and return a corresponding array of Tpm2PCRValue objects. + * + * The format is the same as tpm2_pcr_values_from_string(). The first provided entry with a hash algorithm +@@ -4858,7 +4856,7 @@ int tpm2_parse_pcr_argument(const char *arg, Tpm2PCRValue **ret_pcr_values, size + size_t n_pcr_values = 0; + r = tpm2_pcr_values_from_string(arg, &pcr_values, &n_pcr_values); + if (r < 0) +- return r; ++ return log_error_errno(r, "Could not parse PCR values from '%s': %m", arg); + + tpm2_pcr_values_apply_default_hash_alg(pcr_values, n_pcr_values); + +@@ -4976,7 +4974,7 @@ int tpm2_load_pcr_signature(const char *path, JsonVariant **ret) { + + search = strv_split_nulstr(CONF_PATHS_NULSTR("systemd")); + if (!search) +- return log_oom(); ++ return log_oom_debug(); + + if (!path) { + /* If no path is specified, then look for "tpm2-pcr-signature.json" automatically. Also, in +@@ -4987,7 +4985,7 @@ int tpm2_load_pcr_signature(const char *path, JsonVariant **ret) { + + if (in_initrd()) + if (strv_extend(&search, "/.extra") < 0) +- return log_oom(); ++ return log_oom_debug(); + } + + r = search_and_fopen(path, "re", NULL, (const char**) search, &f, &discovered_path); +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index c8da857eb4..35c13eda82 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -248,7 +248,7 @@ typedef struct {} Tpm2PCRValue; + #endif /* HAVE_TPM2 */ + + int tpm2_list_devices(void); +-int tpm2_find_device_auto(int log_level, char **ret); ++int tpm2_find_device_auto(char **ret); + + int tpm2_make_pcr_json_array(uint32_t pcr_mask, JsonVariant **ret); + int tpm2_parse_pcr_json_array(JsonVariant *v, uint32_t *ret); diff --git a/SOURCES/0618-tpm2-handle-older-tpm-enrollments-without-a-saved-pc.patch b/SOURCES/0618-tpm2-handle-older-tpm-enrollments-without-a-saved-pc.patch new file mode 100644 index 0000000..cfbcaa5 --- /dev/null +++ b/SOURCES/0618-tpm2-handle-older-tpm-enrollments-without-a-saved-pc.patch @@ -0,0 +1,37 @@ +From 9935268b03aa24fbf0e70eaa42ee58db94f237d3 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 14 Jul 2023 13:15:48 -0400 +Subject: [PATCH] tpm2: handle older tpm enrollments without a saved pcr bank + +Older code did not save the pcr bank (i.e. pcr hash algorithm), and instead let +tpm2_unseal() find the best pcr bank to use. In commit +2cd9d57548b0dadd52523df486d33aa4cf7c3b84 we changed tpm2_unseal() to no longer +handle an unset pcr bank. This adds back in the handling of an unset pcr_bank +so older sealed data should continue to work. + +(cherry picked from commit 730d6ab9302f42a2d49355ec8851bd5e3929b36d) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 8 ++++++++ + 1 file changed, 8 insertions(+) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index ffadfa1498..e743dad392 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -4078,6 +4078,14 @@ int tpm2_unseal(const char *device, + if (r < 0) + return r; + ++ /* Older code did not save the pcr_bank, and unsealing needed to detect the best pcr bank to use, ++ * so we need to handle that legacy situation. */ ++ if (pcr_bank == UINT16_MAX) { ++ r = tpm2_get_best_pcr_bank(c, hash_pcr_mask|pubkey_pcr_mask, &pcr_bank); ++ if (r < 0) ++ return r; ++ } ++ + _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; + if (srk_buf) { + r = tpm2_deserialize(c, srk_buf, srk_buf_size, &primary_handle); diff --git a/SOURCES/0619-tpm2-allow-tpm2_make_encryption_session-without-bind.patch b/SOURCES/0619-tpm2-allow-tpm2_make_encryption_session-without-bind.patch new file mode 100644 index 0000000..e06640a --- /dev/null +++ b/SOURCES/0619-tpm2-allow-tpm2_make_encryption_session-without-bind.patch @@ -0,0 +1,44 @@ +From 424f1346bb66fe7aeb252cd6d3cfc660120e9279 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 30 Jun 2023 12:57:23 -0400 +Subject: [PATCH] tpm2: allow tpm2_make_encryption_session() without bind key + +Allow providing no bind key, and use ESYS_TR_NONE instead. + +(cherry picked from commit 73592a7cca194a375d78e15df9e33c2d40f2ea74) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 5 +++-- + 1 file changed, 3 insertions(+), 2 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index e743dad392..5a5f4db45e 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -2793,6 +2793,7 @@ static int tpm2_make_encryption_session( + int r; + + assert(c); ++ assert(primary); + assert(ret_session); + + log_debug("Starting HMAC encryption session."); +@@ -2808,7 +2809,7 @@ static int tpm2_make_encryption_session( + rc = sym_Esys_StartAuthSession( + c->esys_context, + primary->esys_handle, +- bind_key->esys_handle, ++ bind_key ? bind_key->esys_handle : ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, + ESYS_TR_NONE, +@@ -3962,7 +3963,7 @@ int tpm2_seal(Tpm2Context *c, + } + + _cleanup_(tpm2_handle_freep) Tpm2Handle *encryption_session = NULL; +- r = tpm2_make_encryption_session(c, primary_handle, &TPM2_HANDLE_NONE, &encryption_session); ++ r = tpm2_make_encryption_session(c, primary_handle, /* bind_key= */ NULL, &encryption_session); + if (r < 0) + return r; + diff --git a/SOURCES/0620-tpm2-update-tpm2-test-for-supported-commands.patch b/SOURCES/0620-tpm2-update-tpm2-test-for-supported-commands.patch new file mode 100644 index 0000000..afe3d1e --- /dev/null +++ b/SOURCES/0620-tpm2-update-tpm2-test-for-supported-commands.patch @@ -0,0 +1,44 @@ +From a85607dfc7fc09898780dda5a93feed4559e8e22 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 2 Aug 2023 13:35:46 -0400 +Subject: [PATCH] tpm2: update tpm2 test for supported commands + +The test expects TPM2_CC_FIRST - 1 and TPM2_CC_LAST + 1 to be unsupported, but +those are not necessarily invalid commands. Instead test known-invalid +commands. Also add some more valid commands. + +(cherry picked from commit 171d5b69c025c4a86a5100e9437b934ad84daca4) + +Related: RHEL-16182 +--- + src/test/test-tpm2.c | 17 ++++++++++++----- + 1 file changed, 12 insertions(+), 5 deletions(-) + +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index a3a2700a8a..b8bddcc4f0 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -986,11 +986,18 @@ TEST(tpm_required_tests) { + assert_se(tpm2_supports_alg(c, TPM2_ALG_AES)); + assert_se(tpm2_supports_alg(c, TPM2_ALG_CFB)); + +- /* Test invalid commands */ +- assert_se(!tpm2_supports_command(c, TPM2_CC_FIRST - 1)); +- assert_se(!tpm2_supports_command(c, TPM2_CC_LAST + 1)); +- +- /* Test valid commands */ ++ /* Test invalid commands. TPM specification Part 2 ("Structures") section "TPM_CC (Command Codes)" ++ * states bits 31:30 and 28:16 are reserved and must be 0. */ ++ assert_se(!tpm2_supports_command(c, UINT32_C(0x80000000))); ++ assert_se(!tpm2_supports_command(c, UINT32_C(0x40000000))); ++ assert_se(!tpm2_supports_command(c, UINT32_C(0x00100000))); ++ assert_se(!tpm2_supports_command(c, UINT32_C(0x80000144))); ++ assert_se(!tpm2_supports_command(c, UINT32_C(0x40000144))); ++ assert_se(!tpm2_supports_command(c, UINT32_C(0x00100144))); ++ ++ /* Test valid commands. We should be able to expect all TPMs support these. */ ++ assert_se(tpm2_supports_command(c, TPM2_CC_Startup)); ++ assert_se(tpm2_supports_command(c, TPM2_CC_StartAuthSession)); + assert_se(tpm2_supports_command(c, TPM2_CC_Create)); + assert_se(tpm2_supports_command(c, TPM2_CC_CreatePrimary)); + assert_se(tpm2_supports_command(c, TPM2_CC_Unseal)); diff --git a/SOURCES/0621-tpm2-use-GREEDY_REALLOC_APPEND-in-tpm2_get_capabilit.patch b/SOURCES/0621-tpm2-use-GREEDY_REALLOC_APPEND-in-tpm2_get_capabilit.patch new file mode 100644 index 0000000..1ccf094 --- /dev/null +++ b/SOURCES/0621-tpm2-use-GREEDY_REALLOC_APPEND-in-tpm2_get_capabilit.patch @@ -0,0 +1,45 @@ +From 06e759d897bfe850edf9539de028c490c65adccf Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 8 Sep 2023 14:22:11 -0400 +Subject: [PATCH] tpm2: use GREEDY_REALLOC_APPEND() in + tpm2_get_capability_handles(), cap max value + +Simplify the function with GREEDY_REALLOC_APPEND(). Also limit the size_t-sized +max value to UINT32_MAX since that's the maximum of the range this searches, +and the max parameter for tpm2_get_capability() is uint32_t. + +(cherry picked from commit 7014006906113acf35d4927ef7f287ddaa935fca) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 7 +++---- + 1 file changed, 3 insertions(+), 4 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 5a5f4db45e..853761d50a 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -395,6 +395,8 @@ static int tpm2_get_capability_handles( + assert(ret_handles); + assert(ret_n_handles); + ++ max = MIN(max, UINT32_MAX); ++ + while (max > 0) { + TPMU_CAPABILITIES capability; + r = tpm2_get_capability(c, TPM2_CAP_HANDLES, current, (uint32_t) max, &capability); +@@ -410,13 +412,10 @@ static int tpm2_get_capability_handles( + if (n_handles > SIZE_MAX - handle_list.count) + return log_oom_debug(); + +- if (!GREEDY_REALLOC(handles, n_handles + handle_list.count)) ++ if (!GREEDY_REALLOC_APPEND(handles, n_handles, handle_list.handle, handle_list.count)) + return log_oom_debug(); + +- memcpy_safe(&handles[n_handles], handle_list.handle, sizeof(handles[0]) * handle_list.count); +- + max -= handle_list.count; +- n_handles += handle_list.count; + + /* Update current to the handle index after the last handle in the list. */ + current = handles[n_handles - 1] + 1; diff --git a/SOURCES/0622-tpm2-change-tpm2_unseal-to-accept-Tpm2Context-instea.patch b/SOURCES/0622-tpm2-change-tpm2_unseal-to-accept-Tpm2Context-instea.patch new file mode 100644 index 0000000..f427cca --- /dev/null +++ b/SOURCES/0622-tpm2-change-tpm2_unseal-to-accept-Tpm2Context-instea.patch @@ -0,0 +1,153 @@ +From 0a7c17aa28306a70f40f374f382c0c27046172c4 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Thu, 31 Aug 2023 09:10:40 -0400 +Subject: [PATCH] tpm2: change tpm2_unseal() to accept Tpm2Context instead of + device string + +This matches the change to tpm2_seal(), which now accepts a Tpm2Context instead +of a device string. + +This also allows using the same TPM context for sealing and unsealing, which +will be required by (future) test code when sealing/unsealing using a transient +key. + +(cherry picked from commit db7fdf152b5811c2d83e967010bcde8d435e5bc4) + +Related: RHEL-16182 +--- + src/cryptenroll/cryptenroll-tpm2.c | 2 +- + src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c | 7 ++++++- + src/cryptsetup/cryptsetup-tpm2.c | 9 +++++++-- + src/shared/creds-util.c | 7 ++++++- + src/shared/tpm2-util.c | 11 +---------- + src/shared/tpm2-util.h | 2 +- + 6 files changed, 22 insertions(+), 16 deletions(-) + +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index 96e0183d4f..0155f1a6ef 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -282,7 +282,7 @@ int enroll_tpm2(struct crypt_device *cd, + size_t secret2_size; + + log_debug("Unsealing for verification..."); +- r = tpm2_unseal(device, ++ r = tpm2_unseal(tpm2_context, + hash_pcr_mask, + hash_pcr_bank, + pubkey, pubkey_size, +diff --git a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +index 71334129a6..630a2d8d3e 100644 +--- a/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tokens/luks2-tpm2.c +@@ -79,7 +79,12 @@ int acquire_luks2_key( + return log_error_errno(r, "Failed to load PCR signature: %m"); + } + +- r = tpm2_unseal(device, ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; ++ r = tpm2_context_new(device, &tpm2_context); ++ if (r < 0) ++ return log_error_errno(r, "Failed to create TPM2 context: %m"); ++ ++ r = tpm2_unseal(tpm2_context, + hash_pcr_mask, + pcr_bank, + pubkey, pubkey_size, +diff --git a/src/cryptsetup/cryptsetup-tpm2.c b/src/cryptsetup/cryptsetup-tpm2.c +index ffb399a0b2..c049b8a313 100644 +--- a/src/cryptsetup/cryptsetup-tpm2.c ++++ b/src/cryptsetup/cryptsetup-tpm2.c +@@ -127,8 +127,13 @@ int acquire_tpm2_key( + return log_error_errno(r, "Failed to load pcr signature: %m"); + } + ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; ++ r = tpm2_context_new(device, &tpm2_context); ++ if (r < 0) ++ return log_error_errno(r, "Failed to create TPM2 context: %m"); ++ + if (!(flags & TPM2_FLAGS_USE_PIN)) { +- r = tpm2_unseal(device, ++ r = tpm2_unseal(tpm2_context, + hash_pcr_mask, + pcr_bank, + pubkey, pubkey_size, +@@ -175,7 +180,7 @@ int acquire_tpm2_key( + /* no salting needed, backwards compat with non-salted pins */ + b64_salted_pin = TAKE_PTR(pin_str); + +- r = tpm2_unseal(device, ++ r = tpm2_unseal(tpm2_context, + hash_pcr_mask, + pcr_bank, + pubkey, pubkey_size, +diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c +index 783fe75ca6..e269f1283c 100644 +--- a/src/shared/creds-util.c ++++ b/src/shared/creds-util.c +@@ -1026,9 +1026,14 @@ int decrypt_credential_and_warn( + le32toh(z->size)); + } + ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; ++ r = tpm2_context_new(tpm2_device, &tpm2_context); ++ if (r < 0) ++ return r; ++ + // TODO: Add the SRK data to the credential structure so it can be plumbed + // through and used to verify the TPM session. +- r = tpm2_unseal(tpm2_device, ++ r = tpm2_unseal(tpm2_context, + le64toh(t->pcr_mask), + le16toh(t->pcr_bank), + z ? z->data : NULL, +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 853761d50a..01deb6ebac 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -4022,7 +4022,7 @@ int tpm2_seal(Tpm2Context *c, + + #define RETRY_UNSEAL_MAX 30u + +-int tpm2_unseal(const char *device, ++int tpm2_unseal(Tpm2Context *c, + uint32_t hash_pcr_mask, + uint16_t pcr_bank, + const void *pubkey, +@@ -4053,10 +4053,6 @@ int tpm2_unseal(const char *device, + assert(TPM2_PCR_MASK_VALID(hash_pcr_mask)); + assert(TPM2_PCR_MASK_VALID(pubkey_pcr_mask)); + +- r = dlopen_tpm2(); +- if (r < 0) +- return r; +- + /* So here's what we do here: We connect to the TPM2 chip. As we do when sealing we generate a + * "primary" key on the TPM2 chip, with the same parameters as well as a PCR-bound policy session. + * Given we pass the same parameters, this will result in the same "primary" key, and same policy +@@ -4073,11 +4069,6 @@ int tpm2_unseal(const char *device, + if (r < 0) + return log_debug_errno(r, "Could not extract parts from blob: %m"); + +- _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; +- r = tpm2_context_new(device, &c); +- if (r < 0) +- return r; +- + /* Older code did not save the pcr_bank, and unsealing needed to detect the best pcr bank to use, + * so we need to handle that legacy situation. */ + if (pcr_bank == UINT16_MAX) { +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 35c13eda82..d8a221a7d6 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -173,7 +173,7 @@ int tpm2_marshal_blob(const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, + int tpm2_unmarshal_blob(const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_public, TPM2B_PRIVATE *ret_private); + + int tpm2_seal(Tpm2Context *c, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); +-int tpm2_unseal(const char *device, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); ++int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); + + #if HAVE_OPENSSL + int tpm2_tpm2b_public_to_openssl_pkey(const TPM2B_PUBLIC *public, EVP_PKEY **ret); diff --git a/SOURCES/0623-tpm2-cache-TPM-s-supported-ECC-curves.patch b/SOURCES/0623-tpm2-cache-TPM-s-supported-ECC-curves.patch new file mode 100644 index 0000000..4c36b99 --- /dev/null +++ b/SOURCES/0623-tpm2-cache-TPM-s-supported-ECC-curves.patch @@ -0,0 +1,121 @@ +From 8f899f5abca4c233cf9679c74cbd3839a03e77c2 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 8 Sep 2023 13:14:38 -0400 +Subject: [PATCH] tpm2: cache TPM's supported ECC curves + +This brings the tpm2_supports_ecc_curve() api in line with the other +tpm2_supports_*() functions, of returning a boolean. + +(cherry picked from commit 639dca030bc14b71b3dba0a486f282de316b3e65) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 57 +++++++++++++++++++++++++++++++----------- + src/shared/tpm2-util.h | 3 +++ + 2 files changed, 45 insertions(+), 15 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 01deb6ebac..966b524000 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -282,6 +282,39 @@ static int tpm2_cache_capabilities(Tpm2Context *c) { + current_cc = TPMA_CC_TO_TPM2_CC(commands.commandAttributes[commands.count - 1]) + 1; + } + ++ /* Cache the ECC curves. The spec isn't actually clear if ECC curves can be added/removed ++ * while running, but that would be crazy, so let's hope it is not possible. */ ++ TPM2_ECC_CURVE current_ecc_curve = TPM2_ECC_NONE; ++ for (;;) { ++ r = tpm2_get_capability( ++ c, ++ TPM2_CAP_ECC_CURVES, ++ current_ecc_curve, ++ TPM2_MAX_ECC_CURVES, ++ &capability); ++ if (r < 0) ++ return r; ++ ++ TPML_ECC_CURVE ecc_curves = capability.eccCurves; ++ ++ /* ECC support isn't required */ ++ if (ecc_curves.count == 0) ++ break; ++ ++ if (!GREEDY_REALLOC_APPEND( ++ c->capability_ecc_curves, ++ c->n_capability_ecc_curves, ++ ecc_curves.eccCurves, ++ ecc_curves.count)) ++ return log_oom_debug(); ++ ++ if (r == 0) ++ break; ++ ++ /* Set current_ecc_curve to index after last ecc curve the TPM provided */ ++ current_ecc_curve = ecc_curves.eccCurves[ecc_curves.count - 1] + 1; ++ } ++ + /* Cache the PCR capabilities, which are safe to cache, as the only way they can change is + * TPM2_PCR_Allocate(), which changes the allocation after the next _TPM_Init(). If the TPM is + * reinitialized while we are using it, all our context and sessions will be invalid, so we can +@@ -351,23 +384,16 @@ bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command) { + return tpm2_get_capability_command(c, command, NULL); + } + +-/* Returns 1 if the TPM supports the ECC curve, 0 if not, or < 0 for any error. */ +-static int tpm2_supports_ecc_curve(Tpm2Context *c, TPM2_ECC_CURVE curve) { +- TPMU_CAPABILITIES capability; +- int r; +- +- /* The spec explicitly states the TPM2_ECC_CURVE should be cast to uint32_t. */ +- r = tpm2_get_capability(c, TPM2_CAP_ECC_CURVES, (uint32_t) curve, 1, &capability); +- if (r < 0) +- return r; ++/* Returns true if the TPM supports the ECC curve, otherwise false. */ ++bool tpm2_supports_ecc_curve(Tpm2Context *c, TPM2_ECC_CURVE ecc_curve) { ++ assert(c); + +- TPML_ECC_CURVE eccCurves = capability.eccCurves; +- if (eccCurves.count == 0 || eccCurves.eccCurves[0] != curve) { +- log_debug("TPM does not support ECC curve 0x%02" PRIx16 ".", curve); +- return 0; +- } ++ FOREACH_ARRAY(curve, c->capability_ecc_curves, c->n_capability_ecc_curves) ++ if (*curve == ecc_curve) ++ return true; + +- return 1; ++ log_debug("TPM does not support ECC curve 0x%" PRIx16 ".", ecc_curve); ++ return false; + } + + /* Query the TPM for populated handles. +@@ -515,6 +541,7 @@ static Tpm2Context *tpm2_context_free(Tpm2Context *c) { + + c->capability_algorithms = mfree(c->capability_algorithms); + c->capability_commands = mfree(c->capability_commands); ++ c->capability_ecc_curves = mfree(c->capability_ecc_curves); + + return mfree(c); + } +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index d8a221a7d6..26050c9c55 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -47,6 +47,8 @@ typedef struct { + size_t n_capability_algorithms; + TPMA_CC *capability_commands; + size_t n_capability_commands; ++ TPM2_ECC_CURVE *capability_ecc_curves; ++ size_t n_capability_ecc_curves; + TPML_PCR_SELECTION capability_pcrs; + } Tpm2Context; + +@@ -103,6 +105,7 @@ int tpm2_create_loaded(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handl + + bool tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg); + bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command); ++bool tpm2_supports_ecc_curve(Tpm2Context *c, TPM2_ECC_CURVE ecc_curve); + + bool tpm2_test_parms(Tpm2Context *c, TPMI_ALG_PUBLIC alg, const TPMU_PUBLIC_PARMS *parms); + diff --git a/SOURCES/0624-tpm2-util-make-tpm2_marshal_blob-tpm2_unmarshal_blob.patch b/SOURCES/0624-tpm2-util-make-tpm2_marshal_blob-tpm2_unmarshal_blob.patch new file mode 100644 index 0000000..8cc0019 --- /dev/null +++ b/SOURCES/0624-tpm2-util-make-tpm2_marshal_blob-tpm2_unmarshal_blob.patch @@ -0,0 +1,64 @@ +From 6e3fef6c0331610168de29242f6f2a0a0839a310 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Mon, 23 Oct 2023 10:18:41 +0200 +Subject: [PATCH] tpm2-util: make tpm2_marshal_blob()/tpm2_unmarshal_blob() + static + +These are not used outside of tpm2-util.[ch], and the way they merge +public/private key pair into one blob is kinda specific to our +implementation, hence better should be hidden away, and not used for new +code anyway. + +(cherry picked from commit 9122edf9070f64b39f59e65a7976d190717e676b) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 6 +++--- + src/shared/tpm2-util.h | 3 --- + 2 files changed, 3 insertions(+), 6 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 966b524000..36de831812 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -3742,7 +3742,7 @@ int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *r + /* Marshal the public and private objects into a single nonstandard 'blob'. This is not a (publicly) standard + * format, this is specific to how we currently store the sealed object. This 'blob' can be unmarshalled by + * tpm2_unmarshal_blob(). */ +-int tpm2_marshal_blob( ++static int tpm2_marshal_blob( + const TPM2B_PUBLIC *public, + const TPM2B_PRIVATE *private, + void **ret_blob, +@@ -3781,7 +3781,7 @@ int tpm2_marshal_blob( + /* Unmarshal the 'blob' into public and private objects. This is not a (publicly) standard format, this is + * specific to how we currently store the sealed object. This expects the 'blob' to have been created by + * tpm2_marshal_blob(). */ +-int tpm2_unmarshal_blob( ++static int tpm2_unmarshal_blob( + const void *blob, + size_t blob_size, + TPM2B_PUBLIC *ret_public, +@@ -4007,7 +4007,7 @@ int tpm2_seal(Tpm2Context *c, + log_debug("Marshalling private and public part of HMAC key."); + + _cleanup_free_ void *blob = NULL; +- size_t blob_size; ++ size_t blob_size = 0; + r = tpm2_marshal_blob(public, private, &blob, &blob_size); + if (r < 0) + return log_debug_errno(r, "Could not create sealed blob: %m"); +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 26050c9c55..a9a3554fe1 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -172,9 +172,6 @@ int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGE + int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); + int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, TPM2B_DIGEST *digest); + +-int tpm2_marshal_blob(const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, void **ret_blob, size_t *ret_blob_size); +-int tpm2_unmarshal_blob(const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_public, TPM2B_PRIVATE *ret_private); +- + int tpm2_seal(Tpm2Context *c, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); + int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); + diff --git a/SOURCES/0625-tpm2-util-make-tpm2_read_public-static-as-we-use-it-.patch b/SOURCES/0625-tpm2-util-make-tpm2_read_public-static-as-we-use-it-.patch new file mode 100644 index 0000000..2c308a9 --- /dev/null +++ b/SOURCES/0625-tpm2-util-make-tpm2_read_public-static-as-we-use-it-.patch @@ -0,0 +1,164 @@ +From ca51629f0509cbbbc54e89db9df83f26ae61ac70 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Sat, 21 Oct 2023 16:33:41 +0200 +Subject: [PATCH] tpm2-util: make tpm2_read_public() static, as we use it only + internally in tpm2-util.c + +(cherry picked from commit add8091c972b439a2143a962b6344c0f9881da8d) + +[dtardon: This picks just the tpm2-util.[ch] change from the upstream +commit. It's not strictly necessary, but one of the following commits +introduces a test for the function. So it's either make the function +public or drop the test. I went with the former option, as the function +is there anyway, so it's better if it's test-covered. And one more +commit doesn't really make any difference.] + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 66 +++++++++++++++++++++--------------------- + src/shared/tpm2-util.h | 6 ++-- + 2 files changed, 37 insertions(+), 35 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 36de831812..6e6cbe076f 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -141,7 +141,7 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Tss2_MU_TPMT_PUBLIC_Marshal)); + } + +-static inline void Esys_Freep(void *p) { ++void Esys_Freep(void *p) { + if (*(void**) p) + sym_Esys_Free(*(void**) p); + } +@@ -732,6 +732,35 @@ int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle) { + return 0; + } + ++static int tpm2_read_public( ++ Tpm2Context *c, ++ const Tpm2Handle *session, ++ const Tpm2Handle *handle, ++ TPM2B_PUBLIC **ret_public, ++ TPM2B_NAME **ret_name, ++ TPM2B_NAME **ret_qname) { ++ ++ TSS2_RC rc; ++ ++ assert(c); ++ assert(handle); ++ ++ rc = sym_Esys_ReadPublic( ++ c->esys_context, ++ handle->esys_handle, ++ session ? session->esys_handle : ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ ret_public, ++ ret_name, ++ ret_qname); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to read public info: %s", sym_Tss2_RC_Decode(rc)); ++ ++ return 0; ++} ++ + /* Create a Tpm2Handle object that references a pre-existing handle in the TPM, at the handle index provided. + * This should be used only for persistent, transient, or NV handles; and the handle must already exist in + * the TPM at the specified handle index. The handle index should not be 0. Returns 1 if found, 0 if the +@@ -978,35 +1007,6 @@ static int tpm2_credit_random(Tpm2Context *c) { + return 0; + } + +-int tpm2_read_public( +- Tpm2Context *c, +- const Tpm2Handle *session, +- const Tpm2Handle *handle, +- TPM2B_PUBLIC **ret_public, +- TPM2B_NAME **ret_name, +- TPM2B_NAME **ret_qname) { +- +- TSS2_RC rc; +- +- assert(c); +- assert(handle); +- +- rc = sym_Esys_ReadPublic( +- c->esys_context, +- handle->esys_handle, +- session ? session->esys_handle : ESYS_TR_NONE, +- ESYS_TR_NONE, +- ESYS_TR_NONE, +- ret_public, +- ret_name, +- ret_qname); +- if (rc != TSS2_RC_SUCCESS) +- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "Failed to read public info: %s", sym_Tss2_RC_Decode(rc)); +- +- return 0; +-} +- + /* Get one of the legacy primary key templates. + * + * The legacy templates should only be used for older sealed data that did not use the SRK. Instead of a +@@ -1194,7 +1194,7 @@ static int tpm2_get_srk( + } + + /* Get the SRK, creating one if needed. Returns 0 on success, or < 0 on error. */ +-static int tpm2_get_or_create_srk( ++int tpm2_get_or_create_srk( + Tpm2Context *c, + const Tpm2Handle *session, + TPM2B_PUBLIC **ret_public, +@@ -1208,7 +1208,7 @@ static int tpm2_get_or_create_srk( + if (r < 0) + return r; + if (r == 1) +- return 0; ++ return 0; /* 0 → SRK already set up */ + + /* No SRK, create and persist one */ + TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), }; +@@ -1242,7 +1242,7 @@ static int tpm2_get_or_create_srk( + /* This should never happen. */ + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), "SRK we just persisted couldn't be found."); + +- return 0; ++ return 1; /* > 0 → SRK newly set up */ + } + + /* Utility functions for TPMS_PCR_SELECTION. */ +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index a9a3554fe1..15ca677c1c 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -67,6 +67,8 @@ typedef struct { + #define _tpm2_handle(c, h) { .tpm2_context = (c), .esys_handle = (h), } + static const Tpm2Handle TPM2_HANDLE_NONE = _tpm2_handle(NULL, ESYS_TR_NONE); + ++void Esys_Freep(void *p); ++ + int tpm2_handle_new(Tpm2Context *context, Tpm2Handle **ret_handle); + Tpm2Handle *tpm2_handle_free(Tpm2Handle *handle); + DEFINE_TRIVIAL_CLEANUP_FUNC(Tpm2Handle*, tpm2_handle_free); +@@ -161,8 +163,6 @@ void tpm2_log_debug_name(const TPM2B_NAME *name, const char *msg); + int tpm2_index_to_handle(Tpm2Context *c, TPM2_HANDLE index, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); + int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE *ret_index); + +-int tpm2_read_public(Tpm2Context *c, const Tpm2Handle *session, const Tpm2Handle *handle, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname); +- + int tpm2_pcr_read(Tpm2Context *c, const TPML_PCR_SELECTION *pcr_selection, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); + int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_t n_pcr_values); + +@@ -172,6 +172,8 @@ int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGE + int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); + int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, TPM2B_DIGEST *digest); + ++int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); ++ + int tpm2_seal(Tpm2Context *c, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); + int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); + diff --git a/SOURCES/0626-cryptenroll-allow-specifying-handle-index-of-key-to-.patch b/SOURCES/0626-cryptenroll-allow-specifying-handle-index-of-key-to-.patch new file mode 100644 index 0000000..c9e44ec --- /dev/null +++ b/SOURCES/0626-cryptenroll-allow-specifying-handle-index-of-key-to-.patch @@ -0,0 +1,276 @@ +From b76a221b28bb19f8e0bd80e20aeac7b56e2281ee Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 21 Jul 2023 15:49:16 -0400 +Subject: [PATCH] cryptenroll: allow specifying handle index of key to use for + sealing + +This defaults to the SRK index. + +(cherry picked from commit 382bfd90c316dfdd39066e42ead12e47522738fe) + +Related: RHEL-16182 +--- + man/systemd-cryptenroll.xml | 20 ++++++++++++ + src/cryptenroll/cryptenroll-tpm2.c | 2 ++ + src/cryptenroll/cryptenroll-tpm2.h | 4 +-- + src/cryptenroll/cryptenroll.c | 14 ++++++++- + src/partition/repart.c | 1 + + src/shared/creds-util.c | 1 + + src/shared/tpm2-util.c | 50 +++++++++++++++++++++++------- + src/shared/tpm2-util.h | 2 +- + 8 files changed, 79 insertions(+), 15 deletions(-) + +diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml +index 28e315bd1c..979e57d126 100644 +--- a/man/systemd-cryptenroll.xml ++++ b/man/systemd-cryptenroll.xml +@@ -375,6 +375,26 @@ + enrollment to. + + ++ ++ HANDLE ++ ++ Configures which parent key to use for sealing, using the TPM handle (index) of the ++ key. This is used to "seal" (encrypt) a secret and must be used later to "unseal" (decrypt) the ++ secret. Expects a hexadecimal 32bit integer, optionally prefixed with ++ 0x. Allowable values are any handle index in the persistent ++ (0x81000000-0x81ffffff) or transient ++ (0x80000000-0x80ffffff) ranges. Since transient handles are ++ lost after a TPM reset, and may be flushed during TPM context switching, they should not be used ++ except for very specific use cases, e.g. testing. ++ ++ The default is the Storage Root Key (SRK) handle index 0x81000001. A value ++ of 0 will use the default. For the SRK handle, a new key will be created and stored in the TPM if one ++ does not already exist; for any other handle, the key must already exist in the TPM at the specified ++ handle index. ++ ++ This should not be changed unless you know what you are doing. ++ ++ + + PCR + +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index 0155f1a6ef..98c45f42f6 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -133,6 +133,7 @@ int enroll_tpm2(struct crypt_device *cd, + const void *volume_key, + size_t volume_key_size, + const char *device, ++ uint32_t seal_key_handle, + Tpm2PCRValue *hash_pcr_values, + size_t n_hash_pcr_values, + const char *pubkey_path, +@@ -255,6 +256,7 @@ int enroll_tpm2(struct crypt_device *cd, + return r; + + r = tpm2_seal(tpm2_context, ++ seal_key_handle, + &policy, + pin_str, + &secret, &secret_size, +diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h +index d43a9a8ffe..8a57bdda01 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.h ++++ b/src/cryptenroll/cryptenroll-tpm2.h +@@ -8,9 +8,9 @@ + #include "tpm2-util.h" + + #if HAVE_TPM2 +-int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin); ++int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin); + #else +-static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin) { ++static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin) { + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 key enrollment not supported."); + } +diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c +index aeef45eea6..5ace7a9787 100644 +--- a/src/cryptenroll/cryptenroll.c ++++ b/src/cryptenroll/cryptenroll.c +@@ -33,6 +33,7 @@ static char *arg_unlock_keyfile = NULL; + static char *arg_pkcs11_token_uri = NULL; + static char *arg_fido2_device = NULL; + static char *arg_tpm2_device = NULL; ++static uint32_t arg_tpm2_seal_key_handle = 0; + static Tpm2PCRValue *arg_tpm2_hash_pcr_values = NULL; + static size_t arg_tpm2_n_hash_pcr_values = 0; + static bool arg_tpm2_hash_pcr_values_use_default = true; +@@ -121,6 +122,8 @@ static int help(void) { + " Whether to require user verification to unlock the volume\n" + " --tpm2-device=PATH\n" + " Enroll a TPM2 device\n" ++ " --tpm2-seal-key-handle=HANDLE\n" ++ " Specify handle of key to use for sealing\n" + " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" + " Specify TPM2 PCRs to seal against\n" + " --tpm2-public-key=PATH\n" +@@ -153,6 +156,7 @@ static int parse_argv(int argc, char *argv[]) { + ARG_PKCS11_TOKEN_URI, + ARG_FIDO2_DEVICE, + ARG_TPM2_DEVICE, ++ ARG_TPM2_SEAL_KEY_HANDLE, + ARG_TPM2_PCRS, + ARG_TPM2_PUBLIC_KEY, + ARG_TPM2_PUBLIC_KEY_PCRS, +@@ -178,6 +182,7 @@ static int parse_argv(int argc, char *argv[]) { + { "fido2-with-user-presence", required_argument, NULL, ARG_FIDO2_WITH_UP }, + { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV }, + { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, ++ { "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE }, + { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, + { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, + { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, +@@ -337,6 +342,13 @@ static int parse_argv(int argc, char *argv[]) { + break; + } + ++ case ARG_TPM2_SEAL_KEY_HANDLE: ++ r = safe_atou32_full(optarg, 16, &arg_tpm2_seal_key_handle); ++ if (r < 0) ++ return log_error_errno(r, "Could not parse TPM2 seal key handle index '%s': %m", optarg); ++ ++ break; ++ + case ARG_TPM2_PCRS: + arg_tpm2_hash_pcr_values_use_default = false; + r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); +@@ -667,7 +679,7 @@ static int run(int argc, char *argv[]) { + break; + + case ENROLL_TPM2: +- slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin); ++ slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_seal_key_handle, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin); + break; + + case _ENROLL_TYPE_INVALID: +diff --git a/src/partition/repart.c b/src/partition/repart.c +index 481680768a..2b3b384743 100644 +--- a/src/partition/repart.c ++++ b/src/partition/repart.c +@@ -3079,6 +3079,7 @@ static int partition_encrypt( + return log_error_errno(r, "Could not calculate sealing policy digest: %m"); + + r = tpm2_seal(tpm2_context, ++ /* seal_key_handle= */ 0, + &policy, + /* pin= */ NULL, + &secret, &secret_size, +diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c +index e269f1283c..27548a0eec 100644 +--- a/src/shared/creds-util.c ++++ b/src/shared/creds-util.c +@@ -684,6 +684,7 @@ int encrypt_credential_and_warn( + return log_error_errno(r, "Could not calculate sealing policy digest: %m"); + + r = tpm2_seal(tpm2_context, ++ /* seal_key_handle= */ 0, + &tpm2_policy, + /* pin= */ NULL, + &tpm2_key, &tpm2_key_size, +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 6e6cbe076f..b7f55ad7d3 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -780,20 +780,23 @@ int tpm2_index_to_handle( + + assert(c); + +- /* Let's restrict this, at least for now, to allow only some handle types. */ ++ /* Only allow persistent, transient, or NV index handle types. */ + switch (TPM2_HANDLE_TYPE(index)) { + case TPM2_HT_PERSISTENT: + case TPM2_HT_NV_INDEX: + case TPM2_HT_TRANSIENT: + break; + case TPM2_HT_PCR: ++ /* PCR handles are referenced by their actual index number and do not need a Tpm2Handle */ + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid handle 0x%08" PRIx32 " (in PCR range).", index); + case TPM2_HT_HMAC_SESSION: + case TPM2_HT_POLICY_SESSION: ++ /* Session indexes are only used internally by tpm2-tss (or lower code) */ + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid handle 0x%08" PRIx32 " (in session range).", index); +- case TPM2_HT_PERMANENT: /* Permanent handles are defined, e.g. ESYS_TR_RH_OWNER. */ ++ case TPM2_HT_PERMANENT: ++ /* Permanent handles are defined, e.g. ESYS_TR_RH_OWNER. */ + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "Invalid handle 0x%08" PRIx32 " (in permanent range).", index); + default: +@@ -3872,6 +3875,7 @@ static int tpm2_deserialize( + } + + int tpm2_seal(Tpm2Context *c, ++ uint32_t seal_key_handle, + const TPM2B_DIGEST *policy, + const char *pin, + void **ret_secret, +@@ -3945,18 +3949,42 @@ int tpm2_seal(Tpm2Context *c, + _cleanup_(tpm2_handle_freep) Tpm2Handle *primary_handle = NULL; + if (ret_srk_buf) { + _cleanup_(Esys_Freep) TPM2B_PUBLIC *primary_public = NULL; +- r = tpm2_get_or_create_srk( +- c, +- /* session= */ NULL, +- &primary_public, +- /* ret_name= */ NULL, +- /* ret_qname= */ NULL, +- &primary_handle); +- if (r < 0) +- return r; ++ ++ if (IN_SET(seal_key_handle, 0, TPM2_SRK_HANDLE)) { ++ r = tpm2_get_or_create_srk( ++ c, ++ /* session= */ NULL, ++ &primary_public, ++ /* ret_name= */ NULL, ++ /* ret_qname= */ NULL, ++ &primary_handle); ++ if (r < 0) ++ return r; ++ } else if (IN_SET(TPM2_HANDLE_TYPE(seal_key_handle), TPM2_HT_TRANSIENT, TPM2_HT_PERSISTENT)) { ++ r = tpm2_index_to_handle( ++ c, ++ seal_key_handle, ++ /* session= */ NULL, ++ &primary_public, ++ /* ret_name= */ NULL, ++ /* ret_qname= */ NULL, ++ &primary_handle); ++ if (r < 0) ++ return r; ++ if (r == 0) ++ /* We do NOT automatically create anything other than the SRK */ ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOENT), ++ "No handle found at index 0x%" PRIx32, seal_key_handle); ++ } else ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Seal key handle 0x%" PRIx32 " is neither transient nor persistent.", ++ seal_key_handle); + + primary_alg = primary_public->publicArea.type; + } else { ++ if (seal_key_handle != 0) ++ log_debug("Using primary alg sealing, but seal key handle also provided; ignoring seal key handle."); ++ + /* TODO: force all callers to provide ret_srk_buf, so we can stop sealing with the legacy templates. */ + primary_alg = TPM2_ALG_ECC; + +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 15ca677c1c..8d60d43c51 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -174,7 +174,7 @@ int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_v + + int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); + +-int tpm2_seal(Tpm2Context *c, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); ++int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); + int tpm2_unseal(Tpm2Context *c, uint32_t hash_pcr_mask, uint16_t pcr_bank, const void *pubkey, size_t pubkey_size, uint32_t pubkey_pcr_mask, JsonVariant *signature, const char *pin, uint16_t primary_alg, const void *blob, size_t blob_size, const void *policy_hash, size_t policy_hash_size, const void *srk_buf, size_t srk_buf_size, void **ret_secret, size_t *ret_secret_size); + + #if HAVE_OPENSSL diff --git a/SOURCES/0627-test-add-tests-for-systemd-cryptenroll-tpm2-seal-key.patch b/SOURCES/0627-test-add-tests-for-systemd-cryptenroll-tpm2-seal-key.patch new file mode 100644 index 0000000..298b4b3 --- /dev/null +++ b/SOURCES/0627-test-add-tests-for-systemd-cryptenroll-tpm2-seal-key.patch @@ -0,0 +1,416 @@ +From 324ecadd672aecc1331035ede0e1b72dd6af3abd Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 6 Oct 2023 11:14:25 -0400 +Subject: [PATCH] test: add tests for systemd-cryptenroll + --tpm2-seal-key-handle + +In TEST-70-TPM2, test systemd-cryptenroll --tpm2-seal-key-handle using the +default (0) as well as the SRK handle (0x81000001), and test using a non-SRK +handle index after creating and persisting a primary key. + +In test/test-tpm2, test tpm2_seal() and tpm2_unseal() using default (0), the SRK +handle, and a transient handle. + +(cherry picked from commit adcd3266ecddd8527374b2ba905ed0e98b19385c) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 10 +-- + src/shared/tpm2-util.h | 9 ++ + src/test/test-tpm2.c | 173 +++++++++++++++++++++++++++++-------- + test/TEST-70-TPM2/test.sh | 8 ++ + test/units/testsuite-70.sh | 63 ++++++++++++++ + 5 files changed, 216 insertions(+), 47 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index b7f55ad7d3..c287809450 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1076,7 +1076,7 @@ static int tpm2_get_legacy_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_templa + * + * These templates are only needed to create a new persistent SRK (or a new transient key that is + * SRK-compatible). Preferably, the TPM should contain a shared SRK located at the reserved shared SRK handle +- * (see TPM2_SRK_HANDLE and tpm2_get_srk() below). ++ * (see TPM2_SRK_HANDLE in tpm2-util.h, and tpm2_get_srk() below). + * + * The alg must be TPM2_ALG_RSA or TPM2_ALG_ECC. Returns error if the requested template is not supported on + * this TPM. Also see tpm2_get_best_srk_template() below. */ +@@ -1175,14 +1175,6 @@ static int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template) + "TPM does not support either SRK template L-1 (RSA) or L-2 (ECC)."); + } + +-/* The SRK handle is defined in the Provisioning Guidance document (see above) in the table "Reserved Handles +- * for TPM Provisioning Fundamental Elements". The SRK is useful because it is "shared", meaning it has no +- * authValue nor authPolicy set, and thus may be used by anyone on the system to generate derived keys or +- * seal secrets. This is useful if the TPM has an auth (password) set for the 'owner hierarchy', which would +- * prevent users from generating primary transient keys, unless they knew the owner hierarchy auth. See +- * the Provisioning Guidance document for more details. */ +-#define TPM2_SRK_HANDLE UINT32_C(0x81000001) +- + /* Get the SRK. Returns 1 if SRK is found, 0 if there is no SRK, or < 0 on error. Also see + * tpm2_get_or_create_srk() below. */ + static int tpm2_get_srk( +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 8d60d43c51..959a428f08 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -18,6 +18,15 @@ typedef enum TPM2Flags { + * TPM2 on a Client PC must have at least 24 PCRs. This hardcodes our expectation of 24. */ + #define TPM2_PCRS_MAX 24U + #define TPM2_PCRS_MASK ((UINT32_C(1) << TPM2_PCRS_MAX) - 1) ++ ++/* The SRK handle is defined in the Provisioning Guidance document (see above) in the table "Reserved Handles ++ * for TPM Provisioning Fundamental Elements". The SRK is useful because it is "shared", meaning it has no ++ * authValue nor authPolicy set, and thus may be used by anyone on the system to generate derived keys or ++ * seal secrets. This is useful if the TPM has an auth (password) set for the 'owner hierarchy', which would ++ * prevent users from generating primary transient keys, unless they knew the owner hierarchy auth. See ++ * the Provisioning Guidance document for more details. */ ++#define TPM2_SRK_HANDLE UINT32_C(0x81000001) ++ + static inline bool TPM2_PCR_INDEX_VALID(unsigned pcr) { + return pcr < TPM2_PCRS_MAX; + } +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index b8bddcc4f0..a4beb1ff8d 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -1,6 +1,7 @@ + /* SPDX-License-Identifier: LGPL-2.1-or-later */ + + #include "hexdecoct.h" ++#include "macro.h" + #include "tpm2-util.h" + #include "tests.h" + +@@ -701,21 +702,44 @@ TEST(parse_pcr_argument) { + check_parse_pcr_argument_to_mask("debug+24", -EINVAL); + } + +-static void tpm2b_public_rsa_init(TPM2B_PUBLIC *public, const char *rsa_n) { +- TPMT_PUBLIC tpmt = { +- .type = TPM2_ALG_RSA, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, +- .parameters.rsaDetail = { +- .symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }, +- .scheme.scheme = TPM2_ALG_NULL, +- .keyBits = 2048, ++static const TPMT_PUBLIC test_rsa_template = { ++ .type = TPM2_ALG_RSA, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, ++ .parameters.rsaDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, + }, +- }; ++ .scheme.scheme = TPM2_ALG_NULL, ++ .keyBits = 2048, ++ }, ++}; ++ ++static const TPMT_PUBLIC test_ecc_template = { ++ .type = TPM2_ALG_ECC, ++ .nameAlg = TPM2_ALG_SHA256, ++ .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, ++ .parameters.eccDetail = { ++ .symmetric = { ++ .algorithm = TPM2_ALG_AES, ++ .keyBits.aes = 128, ++ .mode.aes = TPM2_ALG_CFB, ++ }, ++ .scheme.scheme = TPM2_ALG_NULL, ++ .curveID = TPM2_ECC_NIST_P256, ++ .kdf.scheme = TPM2_ALG_NULL, ++ }, ++}; ++ ++static const TPMT_PUBLIC *test_templates[] = { ++ &test_rsa_template, ++ &test_ecc_template, ++}; ++ ++static void tpm2b_public_rsa_init(TPM2B_PUBLIC *public, const char *rsa_n) { ++ TPMT_PUBLIC tpmt = test_rsa_template; + + DEFINE_HEX_PTR(key, rsa_n); + tpmt.unique.rsa = TPM2B_PUBLIC_KEY_RSA_MAKE(key, key_len); +@@ -725,21 +749,8 @@ static void tpm2b_public_rsa_init(TPM2B_PUBLIC *public, const char *rsa_n) { + } + + static void tpm2b_public_ecc_init(TPM2B_PUBLIC *public, TPMI_ECC_CURVE curve, const char *x, const char *y) { +- TPMT_PUBLIC tpmt = { +- .type = TPM2_ALG_ECC, +- .nameAlg = TPM2_ALG_SHA256, +- .objectAttributes = TPMA_OBJECT_RESTRICTED|TPMA_OBJECT_DECRYPT|TPMA_OBJECT_FIXEDTPM|TPMA_OBJECT_FIXEDPARENT|TPMA_OBJECT_SENSITIVEDATAORIGIN|TPMA_OBJECT_USERWITHAUTH, +- .parameters.eccDetail = { +- .symmetric = { +- .algorithm = TPM2_ALG_AES, +- .keyBits.aes = 128, +- .mode.aes = TPM2_ALG_CFB, +- }, +- .scheme.scheme = TPM2_ALG_NULL, +- .curveID = curve, +- .kdf.scheme = TPM2_ALG_NULL, +- }, +- }; ++ TPMT_PUBLIC tpmt = test_ecc_template; ++ tpmt.parameters.eccDetail.curveID = curve; + + DEFINE_HEX_PTR(buf_x, x); + tpmt.unique.ecc.x = TPM2B_ECC_PARAMETER_MAKE(buf_x, buf_x_len); +@@ -949,15 +960,8 @@ TEST(calculate_policy_pcr) { + assert_se(digest_check(&d, "7481fd1b116078eb3ac2456e4ad542c9b46b9b8eb891335771ca8e7c8f8e4415")); + } + +-TEST(tpm_required_tests) { +- int r; +- +- _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; +- r = tpm2_context_new(NULL, &c); +- if (r < 0) { +- log_tests_skipped("Could not find TPM"); +- return; +- } ++static void check_test_parms(Tpm2Context *c) { ++ assert(c); + + TPMU_PUBLIC_PARMS parms = { + .symDetail.sym = { +@@ -976,6 +980,10 @@ TEST(tpm_required_tests) { + + /* Test with valid parms */ + assert_se(tpm2_test_parms(c, TPM2_ALG_SYMCIPHER, &parms)); ++} ++ ++static void check_supports_alg(Tpm2Context *c) { ++ assert(c); + + /* Test invalid algs */ + assert_se(!tpm2_supports_alg(c, TPM2_ALG_ERROR)); +@@ -985,6 +993,10 @@ TEST(tpm_required_tests) { + assert_se(tpm2_supports_alg(c, TPM2_ALG_RSA)); + assert_se(tpm2_supports_alg(c, TPM2_ALG_AES)); + assert_se(tpm2_supports_alg(c, TPM2_ALG_CFB)); ++} ++ ++static void check_supports_command(Tpm2Context *c) { ++ assert(c); + + /* Test invalid commands. TPM specification Part 2 ("Structures") section "TPM_CC (Command Codes)" + * states bits 31:30 and 28:16 are reserved and must be 0. */ +@@ -1003,6 +1015,91 @@ TEST(tpm_required_tests) { + assert_se(tpm2_supports_command(c, TPM2_CC_Unseal)); + } + ++static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) { ++ TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); ++ ++ assert(c); ++ ++ log_debug("Check seal/unseal for handle 0x%" PRIx32, handle); ++ ++ _cleanup_free_ void *secret = NULL, *blob = NULL, *srk = NULL, *unsealed_secret = NULL; ++ size_t secret_size, blob_size, srk_size, unsealed_secret_size; ++ assert_se(tpm2_seal( ++ c, ++ handle, ++ &policy, ++ /* pin= */ NULL, ++ &secret, &secret_size, ++ &blob, &blob_size, ++ /* ret_primary_alg= */ NULL, ++ &srk, &srk_size) >= 0); ++ ++ assert_se(tpm2_unseal( ++ c, ++ /* hash_pcr_mask= */ 0, ++ /* pcr_bank= */ 0, ++ /* pubkey= */ NULL, /* pubkey_size= */ 0, ++ /* pubkey_pcr_mask= */ 0, ++ /* signature= */ NULL, ++ /* pin= */ NULL, ++ /* primary_alg= */ 0, ++ blob, blob_size, ++ /* policy_hash= */ NULL, /* policy_hash_size= */ 0, ++ srk, srk_size, ++ &unsealed_secret, &unsealed_secret_size) >= 0); ++ ++ assert_se(memcmp_nn(secret, secret_size, unsealed_secret, unsealed_secret_size) == 0); ++} ++ ++static void check_seal_unseal(Tpm2Context *c) { ++ int r; ++ ++ assert(c); ++ ++ check_seal_unseal_for_handle(c, 0); ++ check_seal_unseal_for_handle(c, TPM2_SRK_HANDLE); ++ ++ FOREACH_ARRAY(template, test_templates, ELEMENTSOF(test_templates)) { ++ TPM2B_PUBLIC public = { ++ .publicArea = **template, ++ .size = sizeof(**template), ++ }; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *transient_handle = NULL; ++ assert_se(tpm2_create_primary( ++ c, ++ /* session= */ NULL, ++ &public, ++ /* sensitive= */ NULL, ++ /* ret_public= */ NULL, ++ &transient_handle) >= 0); ++ ++ TPMI_DH_PERSISTENT transient_handle_index; ++ r = tpm2_index_from_handle(c, transient_handle, &transient_handle_index); ++ if (r == -EOPNOTSUPP) { ++ /* libesys too old */ ++ log_tests_skipped("libesys too old for tpm2_index_from_handle"); ++ return; ++ } ++ assert_se(r >= 0); ++ ++ check_seal_unseal_for_handle(c, transient_handle_index); ++ } ++} ++ ++TEST_RET(tests_which_require_tpm) { ++ _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; ++ ++ if (tpm2_context_new(NULL, &c) < 0) ++ return log_tests_skipped("Could not find TPM"); ++ ++ check_test_parms(c); ++ check_supports_alg(c); ++ check_supports_command(c); ++ check_seal_unseal(c); ++ ++ return 0; ++} ++ + #endif /* HAVE_TPM2 */ + + DEFINE_TEST_MAIN(LOG_DEBUG); +diff --git a/test/TEST-70-TPM2/test.sh b/test/TEST-70-TPM2/test.sh +index f448a4a5f1..72784ec418 100755 +--- a/test/TEST-70-TPM2/test.sh ++++ b/test/TEST-70-TPM2/test.sh +@@ -12,6 +12,11 @@ TEST_REQUIRE_INSTALL_TESTS=0 + + command -v swtpm >/dev/null 2>&1 || exit 0 + command -v tpm2_pcrextend >/dev/null 2>&1 || exit 0 ++command -v openssl >/dev/null 2>&1 || exit 0 ++command -v tpm2_createprimary >/dev/null 2>&1 || exit 0 ++command -v tpm2_evictcontrol >/dev/null 2>&1 || exit 0 ++command -v tpm2_flushcontext >/dev/null 2>&1 || exit 0 ++ + + test_append_files() { + local workspace="${1:?}" +@@ -22,6 +27,9 @@ test_append_files() { + inst_binary tpm2_pcrextend + inst_binary tpm2_pcrread + inst_binary openssl ++ inst_binary tpm2_createprimary ++ inst_binary tpm2_evictcontrol ++ inst_binary tpm2_flushcontext + } + + TEST_70_TPM_DEVICE="tpm-tis" +diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh +index 91f31cd169..12b47f329c 100755 +--- a/test/units/testsuite-70.sh ++++ b/test/units/testsuite-70.sh +@@ -5,9 +5,19 @@ set -o pipefail + + export SYSTEMD_LOG_LEVEL=debug + ++trap cleanup ERR ++cleanup() { ++ # Evict the TPM primary key that we persisted ++ if [[ -n $persistent ]]; then ++ tpm2_evictcontrol -c "$persistent" ++ fi ++} ++persistent="" ++ + # Prepare fresh disk image + img="/var/tmp/test.img" + truncate -s 20M $img ++ + echo -n passphrase >/tmp/passphrase + cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom $img /tmp/passphrase + +@@ -94,6 +104,57 @@ if tpm_has_pcr sha256 12; then + rm -f /tmp/pcr.dat + fi + ++# Use default (0) seal key handle ++systemd-cryptenroll --wipe-slot=tpm2 "$img" ++PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0 "$img" ++/usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 ++/usr/lib/systemd/systemd-cryptsetup detach test-volume ++ ++systemd-cryptenroll --wipe-slot=tpm2 "$img" ++PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x0 "$img" ++/usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 ++/usr/lib/systemd/systemd-cryptsetup detach test-volume ++ ++# Use SRK seal key handle ++systemd-cryptenroll --wipe-slot=tpm2 "$img" ++PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=81000001 "$img" ++/usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 ++/usr/lib/systemd/systemd-cryptsetup detach test-volume ++ ++systemd-cryptenroll --wipe-slot=tpm2 "$img" ++PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x81000001 "$img" ++/usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 ++/usr/lib/systemd/systemd-cryptsetup detach test-volume ++ ++# Test invalid ranges: pcr, nv, session, permanent ++systemd-cryptenroll --wipe-slot=tpm2 "$img" ++(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=7 "$img") # PCR ++(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x01000001 "$img") # NV index ++(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x02000001 "$img") # HMAC/loaded session ++(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x03000001 "$img") # Policy/saved session ++(! PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle=0x40000001 "$img") # Permanent ++ ++# Use non-SRK persistent seal key handle (by creating/persisting new key) ++primary=/tmp/primary.ctx ++tpm2_createprimary -c "$primary" ++persistent_line=$(tpm2_evictcontrol -c "$primary" | grep persistent-handle) ++persistent="0x${persistent_line##*0x}" ++tpm2_flushcontext -t ++ ++systemd-cryptenroll --wipe-slot=tpm2 "$img" ++PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="${persistent#0x}" "$img" ++/usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 ++/usr/lib/systemd/systemd-cryptsetup detach test-volume ++ ++systemd-cryptenroll --wipe-slot=tpm2 "$img" ++PASSWORD=passphrase systemd-cryptenroll --tpm2-device=auto --tpm2-seal-key-handle="$persistent" "$img" ++/usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 ++/usr/lib/systemd/systemd-cryptsetup detach test-volume ++ ++tpm2_evictcontrol -c "$persistent" ++persistent="" ++rm -f "$primary" ++ + rm $img + + if [[ -e /usr/lib/systemd/systemd-measure ]]; then +@@ -294,6 +355,8 @@ systemd-cryptenroll --wipe-slot=10240000 $img_2 && { echo 'unexpected success'; + #fido2_multiple_auto + systemd-cryptenroll --fido2-device=auto --unlock-fido2-device=auto $img_2 && { echo 'unexpected success'; exit 1; } + ++cleanup ++ + echo OK >/testok + + exit 0 diff --git a/SOURCES/0628-tpm2-do-not-call-Esys_TR_Close.patch b/SOURCES/0628-tpm2-do-not-call-Esys_TR_Close.patch new file mode 100644 index 0000000..7bdaa8e --- /dev/null +++ b/SOURCES/0628-tpm2-do-not-call-Esys_TR_Close.patch @@ -0,0 +1,50 @@ +From 6ecf383d8680e1ccb9bf508b34ed9467e6d87574 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 9 Oct 2023 12:27:10 -0400 +Subject: [PATCH] tpm2: do not call Esys_TR_Close() + +Unfortunately, the tpm2-tss library doesn't reference count handles, and a call +to Esys_TR_Close() will remove the handle that could be in use by other +code. So stop calling Esys_TR_Close(), and leave the handle around until we +cleanup the entire ESYS_CONTEXT. + +(cherry picked from commit 1524184dd1cb9607ce3e4b9f278c9b231f639e38) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 22 ++++++++++++++++------ + 1 file changed, 16 insertions(+), 6 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index c287809450..c487b1f0ee 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -692,12 +692,22 @@ static void tpm2_handle_cleanup(ESYS_CONTEXT *esys_context, ESYS_TR esys_handle, + if (flush) + rc = sym_Esys_FlushContext(esys_context, esys_handle); + else +- rc = sym_Esys_TR_Close(esys_context, &esys_handle); +- if (rc != TSS2_RC_SUCCESS) /* We ignore failures here (besides debug logging), since this is called +- * in error paths, where we cannot do anything about failures anymore. And +- * when it is called in successful codepaths by this time we already did +- * what we wanted to do, and got the results we wanted so there's no +- * reason to make this fail more loudly than necessary. */ ++ /* We can't use Esys_TR_Close() because the tpm2-tss library does not use reference counting ++ * for handles, and a single Esys_TR_Close() will remove the handle (internal to the tpm2-tss ++ * library) that might be in use by other code that is using the same ESYS_CONTEXT. This ++ * directly affects us; for example the src/test/test-tpm2.c test function ++ * check_seal_unseal() will encounter this issue and will result in a failure when trying to ++ * cleanup (i.e. Esys_FlushContext) the transient primary key that the test function ++ * generates. However, not calling Esys_TR_Close() here should be ok, since any leaked handle ++ * references will be cleaned up when we free our ESYS_CONTEXT. ++ * ++ * An upstream bug is open here: https://github.com/tpm2-software/tpm2-tss/issues/2693 */ ++ rc = TSS2_RC_SUCCESS; // FIXME: restore sym_Esys_TR_Close() use once tpm2-tss is fixed and adopted widely enough ++ if (rc != TSS2_RC_SUCCESS) ++ /* We ignore failures here (besides debug logging), since this is called in error paths, ++ * where we cannot do anything about failures anymore. And when it is called in successful ++ * codepaths by this time we already did what we wanted to do, and got the results we wanted ++ * so there's no reason to make this fail more loudly than necessary. */ + log_debug("Failed to %s TPM handle, ignoring: %s", flush ? "flush" : "close", sym_Tss2_RC_Decode(rc)); + } + diff --git a/SOURCES/0629-tpm2-don-t-use-GetCapability-to-check-transient-hand.patch b/SOURCES/0629-tpm2-don-t-use-GetCapability-to-check-transient-hand.patch new file mode 100644 index 0000000..2f6ce46 --- /dev/null +++ b/SOURCES/0629-tpm2-don-t-use-GetCapability-to-check-transient-hand.patch @@ -0,0 +1,61 @@ +From b4d4cbd6c0e4fae721d64432c79ad00f531e6fdd Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 10 Oct 2023 16:55:39 -0400 +Subject: [PATCH] tpm2: don't use GetCapability() to check transient handles + +The kernel tpm "resource manager" interface doesn't report that any transient +handles exist, even if they do, so don't bother asking if the handle is +transient. + +(cherry picked from commit 9c18019787a767fb6ed5cb906b6ad52847ee34cd) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 34 ++++++++++++++++++++-------------- + 1 file changed, 20 insertions(+), 14 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index c487b1f0ee..7baa86a4e1 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -814,20 +814,26 @@ int tpm2_index_to_handle( + "Invalid handle 0x%08" PRIx32 " (in unknown range).", index); + } + +- r = tpm2_get_capability_handle(c, index); +- if (r < 0) +- return r; +- if (r == 0) { +- log_debug("TPM handle 0x%08" PRIx32 " not populated.", index); +- if (ret_public) +- *ret_public = NULL; +- if (ret_name) +- *ret_name = NULL; +- if (ret_qname) +- *ret_qname = NULL; +- if (ret_handle) +- *ret_handle = NULL; +- return 0; ++ /* For transient handles, the kernel tpm "resource manager" (i.e. /dev/tpmrm0) never acknowleges that ++ * any transient handles exist, even if they actually do. So a failure to find the requested handle ++ * index, if it's a transient handle, may not actually mean it's not present in the tpm; thus, only ++ * check GetCapability() if the handle isn't transient. */ ++ if (TPM2_HANDLE_TYPE(index) != TPM2_HT_TRANSIENT) { // FIXME: once kernel tpmrm is fixed to acknowledge transient handles, check transient handles too ++ r = tpm2_get_capability_handle(c, index); ++ if (r < 0) ++ return r; ++ if (r == 0) { ++ log_debug("TPM handle 0x%08" PRIx32 " not populated.", index); ++ if (ret_public) ++ *ret_public = NULL; ++ if (ret_name) ++ *ret_name = NULL; ++ if (ret_qname) ++ *ret_qname = NULL; ++ if (ret_handle) ++ *ret_handle = NULL; ++ return 0; ++ } + } + + _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; diff --git a/SOURCES/0630-tpm2-util-pick-up-a-few-new-symbols-from-tpm2-tss.patch b/SOURCES/0630-tpm2-util-pick-up-a-few-new-symbols-from-tpm2-tss.patch new file mode 100644 index 0000000..da2cc3f --- /dev/null +++ b/SOURCES/0630-tpm2-util-pick-up-a-few-new-symbols-from-tpm2-tss.patch @@ -0,0 +1,72 @@ +From c2f7cc209eba897537648dbc912c48444ee401f1 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Thu, 19 Oct 2023 17:44:48 +0200 +Subject: [PATCH] tpm2-util: pick up a few new symbols from tpm2-tss + +(cherry picked from commit 199d758879e46d6405d48692fc35601169dd9dc9) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 20 ++++++++++++++++++-- + 1 file changed, 18 insertions(+), 2 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 7baa86a4e1..cb05b2a01c 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -44,11 +44,16 @@ static TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1 + static TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL; + static TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL; + static TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle) = NULL; ++static TSS2_RC (*sym_Esys_NV_DefineSpace)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_AUTH *auth, const TPM2B_NV_PUBLIC *publicInfo, ESYS_TR *nvHandle); ++static TSS2_RC (*sym_Esys_NV_UndefineSpace)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR nvIndex, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3); ++static TSS2_RC (*sym_Esys_NV_Write)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR nvIndex, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_MAX_NV_BUFFER *data, UINT16 offset); + static TSS2_RC (*sym_Esys_PCR_Extend)(ESYS_CONTEXT *esysContext, ESYS_TR pcrHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST_VALUES *digests) = NULL; + static TSS2_RC (*sym_Esys_PCR_Read)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1,ESYS_TR shandle2, ESYS_TR shandle3, const TPML_PCR_SELECTION *pcrSelectionIn, UINT32 *pcrUpdateCounter, TPML_PCR_SELECTION **pcrSelectionOut, TPML_DIGEST **pcrValues) = NULL; +-static TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *approvedPolicy, const TPM2B_NONCE *policyRef, const TPM2B_NAME *keySign, const TPMT_TK_VERIFIED *checkTicket) = NULL; + static TSS2_RC (*sym_Esys_PolicyAuthValue)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3) = NULL; ++static TSS2_RC (*sym_Esys_PolicyAuthorize)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *approvedPolicy, const TPM2B_NONCE *policyRef, const TPM2B_NAME *keySign, const TPMT_TK_VERIFIED *checkTicket) = NULL; ++static TSS2_RC (*sym_Esys_PolicyAuthorizeNV)(ESYS_CONTEXT *esysContext, ESYS_TR authHandle, ESYS_TR nvIndex, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3); + static TSS2_RC (*sym_Esys_PolicyGetDigest)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_DIGEST **policyDigest) = NULL; ++static TSS2_RC (*sym_Esys_PolicyOR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPML_DIGEST *pHashList) = NULL; + static TSS2_RC (*sym_Esys_PolicyPCR)(ESYS_CONTEXT *esysContext, ESYS_TR policySession, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *pcrDigest, const TPML_PCR_SELECTION *pcrs) = NULL; + static TSS2_RC (*sym_Esys_ReadPublic)(ESYS_CONTEXT *esysContext, ESYS_TR objectHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2B_PUBLIC **outPublic, TPM2B_NAME **name, TPM2B_NAME **qualifiedName) = NULL; + static TSS2_RC (*sym_Esys_StartAuthSession)(ESYS_CONTEXT *esysContext, ESYS_TR tpmKey, ESYS_TR bind, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_NONCE *nonceCaller, TPM2_SE sessionType, const TPMT_SYM_DEF *symmetric, TPMI_ALG_HASH authHash, ESYS_TR *sessionHandle) = NULL; +@@ -72,6 +77,9 @@ static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], si + static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPMS_NV_PUBLIC_Marshal)(TPMS_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Marshal)(TPM2B_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_NV_PUBLIC *dest) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + +@@ -94,11 +102,16 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Esys_Initialize), + DLSYM_ARG(Esys_Load), + DLSYM_ARG(Esys_LoadExternal), ++ DLSYM_ARG(Esys_NV_DefineSpace), ++ DLSYM_ARG(Esys_NV_UndefineSpace), ++ DLSYM_ARG(Esys_NV_Write), + DLSYM_ARG(Esys_PCR_Extend), + DLSYM_ARG(Esys_PCR_Read), +- DLSYM_ARG(Esys_PolicyAuthorize), + DLSYM_ARG(Esys_PolicyAuthValue), ++ DLSYM_ARG(Esys_PolicyAuthorize), ++ DLSYM_ARG(Esys_PolicyAuthorizeNV), + DLSYM_ARG(Esys_PolicyGetDigest), ++ DLSYM_ARG(Esys_PolicyOR), + DLSYM_ARG(Esys_PolicyPCR), + DLSYM_ARG(Esys_ReadPublic), + DLSYM_ARG(Esys_StartAuthSession), +@@ -137,6 +150,9 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal), + DLSYM_ARG(Tss2_MU_TPML_PCR_SELECTION_Marshal), ++ DLSYM_ARG(Tss2_MU_TPMS_NV_PUBLIC_Marshal), ++ DLSYM_ARG(Tss2_MU_TPM2B_NV_PUBLIC_Marshal), ++ DLSYM_ARG(Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal), + DLSYM_ARG(Tss2_MU_TPMT_HA_Marshal), + DLSYM_ARG(Tss2_MU_TPMT_PUBLIC_Marshal)); + } diff --git a/SOURCES/0631-tpm2-add-tpm2_get_pin_auth.patch b/SOURCES/0631-tpm2-add-tpm2_get_pin_auth.patch new file mode 100644 index 0000000..9298617 --- /dev/null +++ b/SOURCES/0631-tpm2-add-tpm2_get_pin_auth.patch @@ -0,0 +1,74 @@ +From fd901d06e52786cfb2bd67919c72d05eab5a4c65 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Sat, 15 Jul 2023 08:30:40 -0400 +Subject: [PATCH] tpm2: add tpm2_get_pin_auth() + +Add function to calculate the hash digest for a provided pin, and also verify +that the final byte in the digest is not 0. This is required because the TPM +will always remove all trailing 0's from an auth value before using it. + +Fixes: #27716 +(cherry picked from commit f230572f56a34fd7269c5fda4fb92be5ee0ea281) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 32 ++++++++++++++++++++++++++++++-- + 1 file changed, 30 insertions(+), 2 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index cb05b2a01c..49e50a83ac 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -2795,6 +2795,34 @@ int tpm2_digest_many_digests( + return tpm2_digest_many(alg, digest, iovecs, n_data, extend); + } + ++/* This hashes the provided pin into a digest value, but also verifies that the final byte is not 0, because ++ * the TPM specification Part 1 ("Architecture") section Authorization Values (subsection "Authorization Size ++ * Convention") states "Trailing octets of zero are to be removed from any string before it is used as an ++ * authValue". Since the TPM doesn't know if the auth value is a "string" or just a hash digest, any hash ++ * digest that randomly happens to end in 0 must have the final 0 changed, or the TPM will remove it before ++ * using the value in its HMAC calculations, resulting in failed HMAC checks. */ ++static int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth) { ++ TPM2B_AUTH auth = {}; ++ int r; ++ ++ assert(pin); ++ assert(ret_auth); ++ ++ r = tpm2_digest_buffer(hash, &auth, pin, strlen(pin), /* extend= */ false); ++ if (r < 0) ++ return r; ++ ++ assert(auth.size > 0); ++ if (auth.buffer[auth.size - 1] == 0) { ++ log_debug("authValue digest ends in 0 which the TPM will remove and cause HMAC authorization failures, adjusting."); ++ auth.buffer[auth.size - 1] = 0xff; ++ } ++ ++ *ret_auth = TAKE_STRUCT(auth); ++ ++ return 0; ++} ++ + static int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *pin) { + TPM2B_AUTH auth = {}; + TSS2_RC rc; +@@ -2808,7 +2836,7 @@ static int tpm2_set_auth(Tpm2Context *c, const Tpm2Handle *handle, const char *p + + CLEANUP_ERASE(auth); + +- r = tpm2_digest_buffer(TPM2_ALG_SHA256, &auth, pin, strlen(pin), /* extend= */ false); ++ r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &auth); + if (r < 0) + return r; + +@@ -3955,7 +3983,7 @@ int tpm2_seal(Tpm2Context *c, + CLEANUP_ERASE(hmac_sensitive); + + if (pin) { +- r = tpm2_digest_buffer(TPM2_ALG_SHA256, &hmac_sensitive.userAuth, pin, strlen(pin), /* extend= */ false); ++ r = tpm2_get_pin_auth(TPM2_ALG_SHA256, pin, &hmac_sensitive.userAuth); + if (r < 0) + return r; + } diff --git a/SOURCES/0632-tpm2-instead-of-adjusting-authValue-trailing-0-s-tri.patch b/SOURCES/0632-tpm2-instead-of-adjusting-authValue-trailing-0-s-tri.patch new file mode 100644 index 0000000..8b81706 --- /dev/null +++ b/SOURCES/0632-tpm2-instead-of-adjusting-authValue-trailing-0-s-tri.patch @@ -0,0 +1,81 @@ +From 7b0f212fe888bd03917de131ead35c4109cc723a Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 21 Jul 2023 11:23:22 -0400 +Subject: [PATCH] tpm2: instead of adjusting authValue trailing 0(s), trim them + as required by tpm spec + +To keep compatibility with any existing object authValues with trailing 0's, +change tpm2_get_pin_auth() to trim trailing 0's, which is what the TPM +implementation will do. This should retain compatibility with any existing +authValues that contain trailing 0's. + +Note that any existing authValues with trailing 0's are unlikely to have worked +in the way that systemd uses them in object sealing, which is as a bind key for +the encryption (and policy) session. However, it is better to be compatible +with the TPM spec (and implementations) even if previously created objects that +are affected may not have worked. + +Fixes: #28414 +(cherry picked from commit 63477a71dfa39f0cb43854cb28df6606733063ef) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 38 +++++++++++++++++++++++++++++++------- + 1 file changed, 31 insertions(+), 7 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 49e50a83ac..5bce39a994 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -2799,8 +2799,36 @@ int tpm2_digest_many_digests( + * the TPM specification Part 1 ("Architecture") section Authorization Values (subsection "Authorization Size + * Convention") states "Trailing octets of zero are to be removed from any string before it is used as an + * authValue". Since the TPM doesn't know if the auth value is a "string" or just a hash digest, any hash +- * digest that randomly happens to end in 0 must have the final 0 changed, or the TPM will remove it before +- * using the value in its HMAC calculations, resulting in failed HMAC checks. */ ++ * digest that randomly happens to end in 0 must have the final 0(s) trimmed. ++ * ++ * This is required at 2 points. First, when setting the authValue during creation of new sealed objects, in ++ * tpm2_seal(). This only applies to newly created objects, of course. Second, when using a previously ++ * created sealed object that has an authValue set, we use the sealed objects as the session bind key. This ++ * requires calling SetAuth so tpm2-tss can correctly calculate the HMAC to use for the encryption session. ++ * ++ * TPM implementations will perform the trimming for any authValue for existing sealed objects, so the ++ * tpm2-tss library must also perform the trimming before HMAC calculation, but it does not yet; this bug is ++ * open to add the trimming: https://github.com/tpm2-software/tpm2-tss/issues/2664 ++ * ++ * Until our minimum tpm2-tss version contains a fix for that bug, we must perform the trimming ++ * ourselves. Note that since we are trimming, which is exactly what a TPM implementation would do, this will ++ * work for both existing objects with a authValue ending in 0(s) as well as new sealed objects we create, ++ * which we will trim the 0(s) from before sending to the TPM. ++ */ ++static void tpm2_trim_auth_value(TPM2B_AUTH *auth) { ++ bool trimmed = false; ++ ++ assert(auth); ++ ++ while (auth->size > 0 && auth->buffer[auth->size - 1] == 0) { ++ trimmed = true; ++ auth->size--; ++ } ++ ++ if (trimmed) ++ log_debug("authValue ends in 0, trimming as required by the TPM2 specification Part 1 section 'HMAC Computation' authValue Note 2."); ++} ++ + static int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *ret_auth) { + TPM2B_AUTH auth = {}; + int r; +@@ -2812,11 +2840,7 @@ static int tpm2_get_pin_auth(TPMI_ALG_HASH hash, const char *pin, TPM2B_AUTH *re + if (r < 0) + return r; + +- assert(auth.size > 0); +- if (auth.buffer[auth.size - 1] == 0) { +- log_debug("authValue digest ends in 0 which the TPM will remove and cause HMAC authorization failures, adjusting."); +- auth.buffer[auth.size - 1] = 0xff; +- } ++ tpm2_trim_auth_value(&auth); + + *ret_auth = TAKE_STRUCT(auth); + diff --git a/SOURCES/0633-tpm2-util-rename-tpm2_calculate_name-tpm2_calculate_.patch b/SOURCES/0633-tpm2-util-rename-tpm2_calculate_name-tpm2_calculate_.patch new file mode 100644 index 0000000..d60c965 --- /dev/null +++ b/SOURCES/0633-tpm2-util-rename-tpm2_calculate_name-tpm2_calculate_.patch @@ -0,0 +1,83 @@ +From 0eb3ebe27c866b869d500733b82c7e5b3904a453 Mon Sep 17 00:00:00 2001 +From: Lennart Poettering +Date: Sat, 21 Oct 2023 16:17:39 +0200 +Subject: [PATCH] =?UTF-8?q?tpm2-util:=20rename=20tpm2=5Fcalculate=5Fname()?= + =?UTF-8?q?=20=E2=86=92=20tpm2=5Fcalculate=5Fpubkey=5Fname()?= +MIME-Version: 1.0 +Content-Type: text/plain; charset=UTF-8 +Content-Transfer-Encoding: 8bit + +We'll soon have a function for determining the name of an NV index, +hence let's rename the existing function for the same of a public key to +make clear it's about public keys only. + +(cherry picked from commit b98c4f1d48124f7152a28cda4bfc9a9dd3136a65) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 4 ++-- + src/shared/tpm2-util.h | 2 +- + src/test/test-tpm2.c | 6 +++--- + 3 files changed, 6 insertions(+), 6 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 5bce39a994..a16e611f27 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -3094,7 +3094,7 @@ static int find_signature( + * + * Since we (currently) hardcode to always using SHA256 for hashing, this returns an error if the public key + * nameAlg is not TPM2_ALG_SHA256. */ +-int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name) { ++int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name) { + TSS2_RC rc; + int r; + +@@ -3358,7 +3358,7 @@ int tpm2_calculate_policy_authorize( + "Offset 0x%zx wrong after marshalling PolicyAuthorize command", offset); + + TPM2B_NAME name = {}; +- r = tpm2_calculate_name(&public->publicArea, &name); ++ r = tpm2_calculate_pubkey_name(&public->publicArea, &name); + if (r < 0) + return r; + +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 959a428f08..c980567819 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -175,7 +175,7 @@ int tpm2_index_from_handle(Tpm2Context *c, const Tpm2Handle *handle, TPM2_HANDLE + int tpm2_pcr_read(Tpm2Context *c, const TPML_PCR_SELECTION *pcr_selection, Tpm2PCRValue **ret_pcr_values, size_t *ret_n_pcr_values); + int tpm2_pcr_read_missing_values(Tpm2Context *c, Tpm2PCRValue *pcr_values, size_t n_pcr_values); + +-int tpm2_calculate_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); ++int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); + int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest); + int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGEST *policy_ref, TPM2B_DIGEST *digest); + int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index a4beb1ff8d..3df6258934 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -858,18 +858,18 @@ static void check_name(const TPM2B_NAME *name, const char *expect) { + assert_se(memcmp(name->name, e, e_len) == 0); + } + +-TEST(calculate_name) { ++TEST(calculate_pubkey_name) { + TPM2B_PUBLIC public; + TPM2B_NAME name; + + /* RSA */ + tpm2b_public_rsa_init(&public, "9ec7341c52093ac40a1965a5df10432513c539adcf905e30577ab6ebc88ffe53cd08cef12ed9bec6125432f4fada3629b8b96d31b8f507aa35029188fe396da823fcb236027f7fbb01b0da3d87be7f999390449ced604bdf7e26c48657cc0671000f1147da195c3861c96642e54427cb7a11572e07567ec3fd6316978abc4bd92b27bb0a0e4958e599804eeb41d682b3b7fc1f960209f80a4fb8a1b64abfd96bf5d554e73cdd6ad1c8becb4fcf5e8f0c3e621d210e5e2f308f6520ad9a966779231b99f06c5989e5a23a9415c8808ab89ce81117632e2f8461cd4428bded40979236aeadafe8de3f51660a45e1dbc87694e6a36360201cca3ff9e7263e712727"); +- assert_se(tpm2_calculate_name(&public.publicArea, &name) >= 0); ++ assert_se(tpm2_calculate_pubkey_name(&public.publicArea, &name) >= 0); + check_name(&name, "000be78f74a470dd92e979ca067cdb2293a35f075e8560b436bd2ccea5da21486a07"); + + /* ECC */ + tpm2b_public_ecc_init(&public, TPM2_ECC_NIST_P256, "238e02ee4fd5598add6b502429f1815418515e4b0d6551c8e816b38cb15451d1", "70c2d491769775ec43ccd5a571c429233e9d30cf0f486c2e01acd6cb32ba93b6"); +- assert_se(tpm2_calculate_name(&public.publicArea, &name) >= 0); ++ assert_se(tpm2_calculate_pubkey_name(&public.publicArea, &name) >= 0); + check_name(&name, "000b302787187ba19c82011c987bd2dcdbb652b3a543ccc5cb0b49c33d4caae604a6"); + } + diff --git a/SOURCES/0634-cryptenroll-do-not-implicitly-verify-with-default-tp.patch b/SOURCES/0634-cryptenroll-do-not-implicitly-verify-with-default-tp.patch new file mode 100644 index 0000000..8d5e8cf --- /dev/null +++ b/SOURCES/0634-cryptenroll-do-not-implicitly-verify-with-default-tp.patch @@ -0,0 +1,53 @@ +From 83d8729b090803f37208995ff3b97544d8ce5725 Mon Sep 17 00:00:00 2001 +From: Luca Boccassi +Date: Wed, 8 Feb 2023 02:10:28 +0000 +Subject: [PATCH] cryptenroll: do not implicitly verify with default tpm policy + signature + +If it was not requested to use a tpm2 signature file when enrolling, do +not fallback to the default /run/systemd/tpm2-pcr-signature.json as it +likely will be unrelated if it exists. + +Fixes https://github.com/systemd/systemd/issues/25435 + +(cherry picked from commit b0fc23fae51d244d2c33d70c10003aa5d5840223) + +Related: RHEL-16182 +--- + src/cryptenroll/cryptenroll-tpm2.c | 5 +++-- + test/units/testsuite-70.sh | 6 ++++++ + 2 files changed, 9 insertions(+), 2 deletions(-) + +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index 98c45f42f6..d9de9aa96c 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -198,8 +198,9 @@ int enroll_tpm2(struct crypt_device *cd, + + log_debug_errno(r, "Failed to read TPM2 PCR public key, proceeding without: %m"); + pubkey_pcr_mask = 0; +- } else { +- /* Also try to load the signature JSON object, to verify that our enrollment will work. This is optional however. */ ++ } else if (signature_path) { ++ /* Also try to load the signature JSON object, to verify that our enrollment will work. ++ * This is optional however, skip it if it's not explicitly provided. */ + + r = tpm2_load_pcr_signature(signature_path, &signature_json); + if (r < 0) { +diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh +index 12b47f329c..9e42dd5b28 100755 +--- a/test/units/testsuite-70.sh ++++ b/test/units/testsuite-70.sh +@@ -225,6 +225,12 @@ if [ -e /usr/lib/systemd/systemd-measure ] && \ + # Now, do the same, but with a cryptsetup binding + truncate -s 20M $img + cryptsetup luksFormat -q --pbkdf pbkdf2 --pbkdf-force-iterations 1000 --use-urandom $img /tmp/passphrase ++ # Ensure that an unrelated signature, when not requested, is not used ++ touch /run/systemd/tpm2-pcr-signature.json ++ systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-public-key="/tmp/pcrsign-public.pem" $img ++ # Reset and use the signature now ++ rm -f /run/systemd/tpm2-pcr-signature.json ++ systemd-cryptenroll --wipe-slot=tpm2 $img + systemd-cryptenroll --unlock-key-file=/tmp/passphrase --tpm2-device=auto --tpm2-public-key="/tmp/pcrsign-public.pem" --tpm2-signature="/tmp/pcrsign.sig2" $img + + # Check if we can activate that (without the token module stuff) diff --git a/SOURCES/0635-cryptenroll-drop-deadcode.patch b/SOURCES/0635-cryptenroll-drop-deadcode.patch new file mode 100644 index 0000000..8bc2079 --- /dev/null +++ b/SOURCES/0635-cryptenroll-drop-deadcode.patch @@ -0,0 +1,38 @@ +From 35a698a6c8f5eff9c20869c361b2488024987d8c Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Fri, 10 Feb 2023 18:22:57 +0900 +Subject: [PATCH] cryptenroll: drop deadcode + +Follow-up for b0fc23fae51d244d2c33d70c10003aa5d5840223. + +After the commit, 'signature_path' is now always non-NULL, hence the +condition can be dropped. + +Fixes CID#1504492. + +(cherry picked from commit 645063d1bb309137f884d9c9f01d2b7d01ab2ca8) + +Related: RHEL-16182 +--- + src/cryptenroll/cryptenroll-tpm2.c | 8 ++------ + 1 file changed, 2 insertions(+), 6 deletions(-) + +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index d9de9aa96c..631aeea3b5 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -203,12 +203,8 @@ int enroll_tpm2(struct crypt_device *cd, + * This is optional however, skip it if it's not explicitly provided. */ + + r = tpm2_load_pcr_signature(signature_path, &signature_json); +- if (r < 0) { +- if (signature_path || r != -ENOENT) +- return log_debug_errno(r, "Failed to read TPM PCR signature: %m"); +- +- log_debug_errno(r, "Failed to read TPM2 PCR signature, proceeding without: %m"); +- } ++ if (r < 0) ++ return log_debug_errno(r, "Failed to read TPM PCR signature: %m"); + } + + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; diff --git a/SOURCES/0636-tpm2-allow-using-tpm2_get_srk_template-without-tpm.patch b/SOURCES/0636-tpm2-allow-using-tpm2_get_srk_template-without-tpm.patch new file mode 100644 index 0000000..bd9ef93 --- /dev/null +++ b/SOURCES/0636-tpm2-allow-using-tpm2_get_srk_template-without-tpm.patch @@ -0,0 +1,169 @@ +From 67238a5417fb32649b83f2da65356908abbcd821 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 3 Oct 2023 10:25:19 -0400 +Subject: [PATCH] tpm2: allow using tpm2_get_srk_template() without tpm + +The SRK templates are defined by specification, so move the check for TPM +support to the tpm2_get_best_srk_template() function, and allow anyone to get +the ECC and RSA templates. + +Also add test to verify the SRK templates are correct. + +(cherry picked from commit 788933379d9ab219ba085021d0d19ac5d0372cea) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 69 +++++++++++++++++++++++------------------- + src/shared/tpm2-util.h | 2 ++ + src/test/test-tpm2.c | 25 +++++++++++++++ + 3 files changed, 65 insertions(+), 31 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index a16e611f27..1751c82450 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1110,9 +1110,8 @@ static int tpm2_get_legacy_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_templa + * SRK-compatible). Preferably, the TPM should contain a shared SRK located at the reserved shared SRK handle + * (see TPM2_SRK_HANDLE in tpm2-util.h, and tpm2_get_srk() below). + * +- * The alg must be TPM2_ALG_RSA or TPM2_ALG_ECC. Returns error if the requested template is not supported on +- * this TPM. Also see tpm2_get_best_srk_template() below. */ +-static int tpm2_get_srk_template(Tpm2Context *c, TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template) { ++ * Returns 0 if the specified algorithm is ECC or RSA, otherwise -EOPNOTSUPP. */ ++int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template) { + /* The attributes are the same between ECC and RSA templates. This has the changes specified in the + * Provisioning Guidance document, specifically: + * TPMA_OBJECT_USERWITHAUTH is added. +@@ -1161,47 +1160,55 @@ static int tpm2_get_srk_template(Tpm2Context *c, TPMI_ALG_PUBLIC alg, TPMT_PUBLI + }, + }; + +- assert(c); + assert(ret_template); + +- if (alg == TPM2_ALG_ECC) { +- if (!tpm2_supports_alg(c, TPM2_ALG_ECC)) +- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "TPM does not support ECC."); +- +- if (!tpm2_supports_ecc_curve(c, srk_ecc.parameters.eccDetail.curveID)) +- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "TPM does not support ECC-NIST-P256 curve."); +- +- if (!tpm2_supports_tpmt_public(c, &srk_ecc)) +- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "TPM does not support SRK ECC template L-2."); +- ++ switch (alg) { ++ case TPM2_ALG_ECC: + *ret_template = srk_ecc; + return 0; +- } +- +- if (alg == TPM2_ALG_RSA) { +- if (!tpm2_supports_alg(c, TPM2_ALG_RSA)) +- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "TPM does not support RSA."); +- +- if (!tpm2_supports_tpmt_public(c, &srk_rsa)) +- return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), +- "TPM does not support SRK RSA template L-1."); +- ++ case TPM2_ALG_RSA: + *ret_template = srk_rsa; + return 0; + } + +- return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "Unsupported SRK alg: 0x%x.", alg); ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "No SRK for algorithm 0x%" PRIx16, alg); + } + + /* Get the best supported SRK template. ECC is preferred, then RSA. */ + static int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template) { +- if (tpm2_get_srk_template(c, TPM2_ALG_ECC, ret_template) >= 0 || +- tpm2_get_srk_template(c, TPM2_ALG_RSA, ret_template) >= 0) ++ TPMT_PUBLIC template; ++ int r; ++ ++ assert(c); ++ assert(ret_template); ++ ++ r = tpm2_get_srk_template(TPM2_ALG_ECC, &template); ++ if (r < 0) ++ return r; ++ ++ if (!tpm2_supports_alg(c, TPM2_ALG_ECC)) ++ log_debug("TPM does not support ECC."); ++ else if (!tpm2_supports_ecc_curve(c, template.parameters.eccDetail.curveID)) ++ log_debug("TPM does not support ECC-NIST-P256 curve."); ++ else if (!tpm2_supports_tpmt_public(c, &template)) ++ log_debug("TPM does not support SRK ECC template L-2."); ++ else { ++ *ret_template = template; + return 0; ++ } ++ ++ r = tpm2_get_srk_template(TPM2_ALG_RSA, &template); ++ if (r < 0) ++ return r; ++ ++ if (!tpm2_supports_alg(c, TPM2_ALG_RSA)) ++ log_debug("TPM does not support RSA."); ++ else if (!tpm2_supports_tpmt_public(c, &template)) ++ log_debug("TPM does not support SRK RSA template L-1."); ++ else { ++ *ret_template = template; ++ return 0; ++ } + + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM does not support either SRK template L-1 (RSA) or L-2 (ECC)."); +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index c980567819..fe26b98e76 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -181,6 +181,8 @@ int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGE + int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); + int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, TPM2B_DIGEST *digest); + ++int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template); ++ + int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); + + int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 3df6258934..8a27ffc7db 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -960,6 +960,31 @@ TEST(calculate_policy_pcr) { + assert_se(digest_check(&d, "7481fd1b116078eb3ac2456e4ad542c9b46b9b8eb891335771ca8e7c8f8e4415")); + } + ++TEST(tpm2_get_srk_template) { ++ TPMT_PUBLIC rsa; ++ assert_se(tpm2_get_srk_template(TPM2_ALG_RSA, &rsa) >= 0); ++ assert_se(rsa.type == TPM2_ALG_RSA); ++ assert_se(rsa.nameAlg == TPM2_ALG_SHA256); ++ assert_se(rsa.objectAttributes == 0x30472); ++ assert_se(rsa.parameters.rsaDetail.symmetric.algorithm == TPM2_ALG_AES); ++ assert_se(rsa.parameters.rsaDetail.symmetric.keyBits.sym == 128); ++ assert_se(rsa.parameters.rsaDetail.symmetric.mode.sym == TPM2_ALG_CFB); ++ assert_se(rsa.parameters.rsaDetail.scheme.scheme == TPM2_ALG_NULL); ++ assert_se(rsa.parameters.rsaDetail.keyBits == 2048); ++ ++ TPMT_PUBLIC ecc; ++ assert_se(tpm2_get_srk_template(TPM2_ALG_ECC, &ecc) >= 0); ++ assert_se(ecc.type == TPM2_ALG_ECC); ++ assert_se(ecc.nameAlg == TPM2_ALG_SHA256); ++ assert_se(ecc.objectAttributes == 0x30472); ++ assert_se(ecc.parameters.eccDetail.symmetric.algorithm == TPM2_ALG_AES); ++ assert_se(ecc.parameters.eccDetail.symmetric.keyBits.sym == 128); ++ assert_se(ecc.parameters.eccDetail.symmetric.mode.sym == TPM2_ALG_CFB); ++ assert_se(ecc.parameters.eccDetail.scheme.scheme == TPM2_ALG_NULL); ++ assert_se(ecc.parameters.eccDetail.kdf.scheme == TPM2_ALG_NULL); ++ assert_se(ecc.parameters.eccDetail.curveID == TPM2_ECC_NIST_P256); ++} ++ + static void check_test_parms(Tpm2Context *c) { + assert(c); + diff --git a/SOURCES/0637-tpm2-add-test-to-verify-srk-templates.patch b/SOURCES/0637-tpm2-add-test-to-verify-srk-templates.patch new file mode 100644 index 0000000..ec801ba --- /dev/null +++ b/SOURCES/0637-tpm2-add-test-to-verify-srk-templates.patch @@ -0,0 +1,163 @@ +From 9a44d5682e27885df560e3754586c85fb5bec382 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 3 Oct 2023 10:25:19 -0400 +Subject: [PATCH] tpm2: add test to verify srk templates + +Verify the tpm2_get_srk_template() and tpm2_get_best_srk_template() functions +work as expected. + +(cherry picked from commit 2eea1b8f2f787ea2ed4e571096b48c5a301f63f4) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 4 +- + src/shared/tpm2-util.h | 2 + + src/test/test-tpm2.c | 84 +++++++++++++++++++++++++++++++----------- + 3 files changed, 66 insertions(+), 24 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 1751c82450..4f1aafedf8 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -1175,7 +1175,7 @@ int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template) { + } + + /* Get the best supported SRK template. ECC is preferred, then RSA. */ +-static int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template) { ++int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template) { + TPMT_PUBLIC template; + int r; + +@@ -1216,7 +1216,7 @@ static int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template) + + /* Get the SRK. Returns 1 if SRK is found, 0 if there is no SRK, or < 0 on error. Also see + * tpm2_get_or_create_srk() below. */ +-static int tpm2_get_srk( ++int tpm2_get_srk( + Tpm2Context *c, + const Tpm2Handle *session, + TPM2B_PUBLIC **ret_public, +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index fe26b98e76..bf41bac76e 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -182,7 +182,9 @@ int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_value + int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, TPM2B_DIGEST *digest); + + int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template); ++int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template); + ++int tpm2_get_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); + int tpm2_get_or_create_srk(Tpm2Context *c, const Tpm2Handle *session, TPM2B_PUBLIC **ret_public, TPM2B_NAME **ret_name, TPM2B_NAME **ret_qname, Tpm2Handle **ret_handle); + + int tpm2_seal(Tpm2Context *c, uint32_t seal_key_handle, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, uint16_t *ret_primary_alg, void **ret_srk_buf, size_t *ret_srk_buf_size); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index 8a27ffc7db..ede08a39aa 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -960,29 +960,49 @@ TEST(calculate_policy_pcr) { + assert_se(digest_check(&d, "7481fd1b116078eb3ac2456e4ad542c9b46b9b8eb891335771ca8e7c8f8e4415")); + } + ++static void check_srk_rsa_template(TPMT_PUBLIC *template) { ++ assert_se(template->type == TPM2_ALG_RSA); ++ assert_se(template->nameAlg == TPM2_ALG_SHA256); ++ assert_se(template->objectAttributes == 0x30472); ++ assert_se(template->parameters.rsaDetail.symmetric.algorithm == TPM2_ALG_AES); ++ assert_se(template->parameters.rsaDetail.symmetric.keyBits.sym == 128); ++ assert_se(template->parameters.rsaDetail.symmetric.mode.sym == TPM2_ALG_CFB); ++ assert_se(template->parameters.rsaDetail.scheme.scheme == TPM2_ALG_NULL); ++ assert_se(template->parameters.rsaDetail.keyBits == 2048); ++} ++ ++static void check_srk_ecc_template(TPMT_PUBLIC *template) { ++ assert_se(template->type == TPM2_ALG_ECC); ++ assert_se(template->nameAlg == TPM2_ALG_SHA256); ++ assert_se(template->objectAttributes == 0x30472); ++ assert_se(template->parameters.eccDetail.symmetric.algorithm == TPM2_ALG_AES); ++ assert_se(template->parameters.eccDetail.symmetric.keyBits.sym == 128); ++ assert_se(template->parameters.eccDetail.symmetric.mode.sym == TPM2_ALG_CFB); ++ assert_se(template->parameters.eccDetail.scheme.scheme == TPM2_ALG_NULL); ++ assert_se(template->parameters.eccDetail.kdf.scheme == TPM2_ALG_NULL); ++ assert_se(template->parameters.eccDetail.curveID == TPM2_ECC_NIST_P256); ++} ++ + TEST(tpm2_get_srk_template) { +- TPMT_PUBLIC rsa; +- assert_se(tpm2_get_srk_template(TPM2_ALG_RSA, &rsa) >= 0); +- assert_se(rsa.type == TPM2_ALG_RSA); +- assert_se(rsa.nameAlg == TPM2_ALG_SHA256); +- assert_se(rsa.objectAttributes == 0x30472); +- assert_se(rsa.parameters.rsaDetail.symmetric.algorithm == TPM2_ALG_AES); +- assert_se(rsa.parameters.rsaDetail.symmetric.keyBits.sym == 128); +- assert_se(rsa.parameters.rsaDetail.symmetric.mode.sym == TPM2_ALG_CFB); +- assert_se(rsa.parameters.rsaDetail.scheme.scheme == TPM2_ALG_NULL); +- assert_se(rsa.parameters.rsaDetail.keyBits == 2048); +- +- TPMT_PUBLIC ecc; +- assert_se(tpm2_get_srk_template(TPM2_ALG_ECC, &ecc) >= 0); +- assert_se(ecc.type == TPM2_ALG_ECC); +- assert_se(ecc.nameAlg == TPM2_ALG_SHA256); +- assert_se(ecc.objectAttributes == 0x30472); +- assert_se(ecc.parameters.eccDetail.symmetric.algorithm == TPM2_ALG_AES); +- assert_se(ecc.parameters.eccDetail.symmetric.keyBits.sym == 128); +- assert_se(ecc.parameters.eccDetail.symmetric.mode.sym == TPM2_ALG_CFB); +- assert_se(ecc.parameters.eccDetail.scheme.scheme == TPM2_ALG_NULL); +- assert_se(ecc.parameters.eccDetail.kdf.scheme == TPM2_ALG_NULL); +- assert_se(ecc.parameters.eccDetail.curveID == TPM2_ECC_NIST_P256); ++ TPMT_PUBLIC template; ++ ++ assert_se(tpm2_get_srk_template(TPM2_ALG_RSA, &template) >= 0); ++ check_srk_rsa_template(&template); ++ ++ assert_se(tpm2_get_srk_template(TPM2_ALG_ECC, &template) >= 0); ++ check_srk_ecc_template(&template); ++} ++ ++static void check_best_srk_template(Tpm2Context *c) { ++ TPMT_PUBLIC template; ++ assert_se(tpm2_get_best_srk_template(c, &template) >= 0); ++ ++ assert_se(IN_SET(template.type, TPM2_ALG_ECC, TPM2_ALG_RSA)); ++ ++ if (template.type == TPM2_ALG_RSA) ++ check_srk_rsa_template(&template); ++ else ++ check_srk_ecc_template(&template); + } + + static void check_test_parms(Tpm2Context *c) { +@@ -1040,6 +1060,24 @@ static void check_supports_command(Tpm2Context *c) { + assert_se(tpm2_supports_command(c, TPM2_CC_Unseal)); + } + ++static void check_get_or_create_srk(Tpm2Context *c) { ++ _cleanup_free_ TPM2B_PUBLIC *public = NULL; ++ _cleanup_free_ TPM2B_NAME *name = NULL, *qname = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; ++ assert_se(tpm2_get_or_create_srk(c, NULL, &public, &name, &qname, &handle) >= 0); ++ assert_se(public && name && qname && handle); ++ ++ _cleanup_free_ TPM2B_PUBLIC *public2 = NULL; ++ _cleanup_free_ TPM2B_NAME *name2 = NULL, *qname2 = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle2 = NULL; ++ assert_se(tpm2_get_srk(c, NULL, &public2, &name2, &qname2, &handle2) >= 0); ++ assert_se(public2 && name2 && qname2 && handle2); ++ ++ assert_se(memcmp_nn(public, sizeof(*public), public2, sizeof(*public2)) == 0); ++ assert_se(memcmp_nn(name->name, name->size, name2->name, name2->size) == 0); ++ assert_se(memcmp_nn(qname->name, qname->size, qname2->name, qname2->size) == 0); ++} ++ + static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) { + TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + +@@ -1120,6 +1158,8 @@ TEST_RET(tests_which_require_tpm) { + check_test_parms(c); + check_supports_alg(c); + check_supports_command(c); ++ check_best_srk_template(c); ++ check_get_or_create_srk(c); + check_seal_unseal(c); + + return 0; diff --git a/SOURCES/0638-tpm2-add-tpm2_sym_alg_-_string-and-tpm2_sym_mode_-_s.patch b/SOURCES/0638-tpm2-add-tpm2_sym_alg_-_string-and-tpm2_sym_mode_-_s.patch new file mode 100644 index 0000000..bda21d9 --- /dev/null +++ b/SOURCES/0638-tpm2-add-tpm2_sym_alg_-_string-and-tpm2_sym_mode_-_s.patch @@ -0,0 +1,101 @@ +From 8d35a8961052e49922a0eac228f8ddc476845bd5 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 6 Nov 2023 13:40:11 -0500 +Subject: [PATCH] tpm2: add tpm2_sym_alg_*_string() and + tpm2_sym_mode_*_string() + +Add functions to convert between alg id and string name for symmetric +algorithms and symmetric encryption modes. + +(cherry picked from commit 2d784782bf700ae26dbeaa24e11ee8faebc29367) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 56 ++++++++++++++++++++++++++++++++++++++++++ + src/shared/tpm2-util.h | 6 +++++ + 2 files changed, 62 insertions(+) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 4f1aafedf8..2e2a3f5fb0 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -4923,6 +4923,62 @@ int tpm2_asym_alg_from_string(const char *alg) { + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown asymmetric algorithm name '%s'", alg); + } + ++const char *tpm2_sym_alg_to_string(uint16_t alg) { ++ switch (alg) { ++#if HAVE_TPM2 ++ case TPM2_ALG_AES: ++ return "aes"; ++#endif ++ default: ++ log_debug("Unknown symmetric algorithm id 0x%" PRIx16, alg); ++ return NULL; ++ } ++} ++ ++int tpm2_sym_alg_from_string(const char *alg) { ++#if HAVE_TPM2 ++ if (strcaseeq_ptr(alg, "aes")) ++ return TPM2_ALG_AES; ++#endif ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown symmetric algorithm name '%s'", alg); ++} ++ ++const char *tpm2_sym_mode_to_string(uint16_t mode) { ++ switch (mode) { ++#if HAVE_TPM2 ++ case TPM2_ALG_CTR: ++ return "ctr"; ++ case TPM2_ALG_OFB: ++ return "ofb"; ++ case TPM2_ALG_CBC: ++ return "cbc"; ++ case TPM2_ALG_CFB: ++ return "cfb"; ++ case TPM2_ALG_ECB: ++ return "ecb"; ++#endif ++ default: ++ log_debug("Unknown symmetric mode id 0x%" PRIx16, mode); ++ return NULL; ++ } ++} ++ ++int tpm2_sym_mode_from_string(const char *mode) { ++#if HAVE_TPM2 ++ if (strcaseeq_ptr(mode, "ctr")) ++ return TPM2_ALG_CTR; ++ if (strcaseeq_ptr(mode, "ofb")) ++ return TPM2_ALG_OFB; ++ if (strcaseeq_ptr(mode, "cbc")) ++ return TPM2_ALG_CBC; ++ if (strcaseeq_ptr(mode, "cfb")) ++ return TPM2_ALG_CFB; ++ if (strcaseeq_ptr(mode, "ecb")) ++ return TPM2_ALG_ECB; ++#endif ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), "Unknown symmetric mode name '%s'", mode); ++} ++ + Tpm2Support tpm2_support(void) { + Tpm2Support support = TPM2_SUPPORT_NONE; + int r; +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index bf41bac76e..59c6a6fa92 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -309,6 +309,12 @@ int tpm2_hash_alg_from_string(const char *alg); + const char *tpm2_asym_alg_to_string(uint16_t alg); + int tpm2_asym_alg_from_string(const char *alg); + ++const char *tpm2_sym_alg_to_string(uint16_t alg) _const_; ++int tpm2_sym_alg_from_string(const char *alg) _pure_; ++ ++const char *tpm2_sym_mode_to_string(uint16_t mode) _const_; ++int tpm2_sym_mode_from_string(const char *mode) _pure_; ++ + char *tpm2_pcr_mask_to_string(uint32_t mask); + + typedef struct { diff --git a/SOURCES/0639-tpm2-add-tpm2_calculate_seal-and-helper-functions.patch b/SOURCES/0639-tpm2-add-tpm2_calculate_seal-and-helper-functions.patch new file mode 100644 index 0000000..4c6066a --- /dev/null +++ b/SOURCES/0639-tpm2-add-tpm2_calculate_seal-and-helper-functions.patch @@ -0,0 +1,1003 @@ +From 7ff4ae3dac16a9717bfd74df6f12e336115aa491 Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Wed, 28 Jun 2023 11:46:31 -0400 +Subject: [PATCH] tpm2: add tpm2_calculate_seal() and helper functions + +Add functions to calculate a sealed secret object. + +(cherry picked from commit 0a7874ad55c9cd9114292186da74ba0fd91b8436) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 818 ++++++++++++++++++++++++++++++++++++++++- + src/shared/tpm2-util.h | 7 + + 2 files changed, 816 insertions(+), 9 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index 2e2a3f5fb0..e5fc8a72a7 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -41,6 +41,7 @@ static TSS2_RC (*sym_Esys_FlushContext)(ESYS_CONTEXT *esysContext, ESYS_TR flush + static void (*sym_Esys_Free)(void *ptr) = NULL; + static TSS2_RC (*sym_Esys_GetCapability)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, TPM2_CAP capability, UINT32 property, UINT32 propertyCount, TPMI_YES_NO *moreData, TPMS_CAPABILITY_DATA **capabilityData) = NULL; + static TSS2_RC (*sym_Esys_GetRandom)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, UINT16 bytesRequested, TPM2B_DIGEST **randomBytes) = NULL; ++static TSS2_RC (*sym_Esys_Import)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DATA *encryptionKey, const TPM2B_PUBLIC *objectPublic, const TPM2B_PRIVATE *duplicate, const TPM2B_ENCRYPTED_SECRET *inSymSeed, const TPMT_SYM_DEF_OBJECT *symmetricAlg, TPM2B_PRIVATE **outPrivate) = NULL; + static TSS2_RC (*sym_Esys_Initialize)(ESYS_CONTEXT **esys_context, TSS2_TCTI_CONTEXT *tcti, TSS2_ABI_VERSION *abiVersion) = NULL; + static TSS2_RC (*sym_Esys_Load)(ESYS_CONTEXT *esysContext, ESYS_TR parentHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_PRIVATE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR *objectHandle) = NULL; + static TSS2_RC (*sym_Esys_LoadExternal)(ESYS_CONTEXT *esysContext, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_SENSITIVE *inPrivate, const TPM2B_PUBLIC *inPublic, ESYS_TR hierarchy, ESYS_TR *objectHandle) = NULL; +@@ -72,16 +73,24 @@ static TSS2_RC (*sym_Esys_Unseal)(ESYS_CONTEXT *esysContext, ESYS_TR itemHandle, + static TSS2_RC (*sym_Esys_VerifySignature)(ESYS_CONTEXT *esysContext, ESYS_TR keyHandle, ESYS_TR shandle1, ESYS_TR shandle2, ESYS_TR shandle3, const TPM2B_DIGEST *digest, const TPMT_SIGNATURE *signature, TPMT_TK_VERIFIED **validation) = NULL; + + static TSS2_RC (*sym_Tss2_MU_TPM2_CC_Marshal)(TPM2_CC src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPM2_HANDLE_Marshal)(TPM2_HANDLE src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPM2B_DIGEST_Marshal)(TPM2B_DIGEST const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal)(TPM2B_ENCRYPTED_SECRET const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_ENCRYPTED_SECRET *dest) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPM2B_NAME_Marshal)(TPM2B_NAME const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPM2B_SENSITIVE_Marshal)(TPM2B_SENSITIVE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPMS_NV_PUBLIC_Marshal)(TPMS_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Marshal)(TPM2B_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_NV_PUBLIC *dest) = NULL; ++static TSS2_RC (*sym_Tss2_MU_TPMS_ECC_POINT_Marshal)(TPMS_ECC_POINT const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPMT_HA_Marshal)(TPMT_HA const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPMT_PUBLIC_Marshal)(TPMT_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; ++static TSS2_RC (*sym_Tss2_MU_UINT32_Marshal)(UINT32 src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + + static const char* (*sym_Tss2_RC_Decode)(TSS2_RC rc) = NULL; + +@@ -99,6 +108,7 @@ int dlopen_tpm2(void) { + DLSYM_ARG(Esys_Free), + DLSYM_ARG(Esys_GetCapability), + DLSYM_ARG(Esys_GetRandom), ++ DLSYM_ARG(Esys_Import), + DLSYM_ARG(Esys_Initialize), + DLSYM_ARG(Esys_Load), + DLSYM_ARG(Esys_LoadExternal), +@@ -145,16 +155,24 @@ int dlopen_tpm2(void) { + return dlopen_many_sym_or_warn( + &libtss2_mu_dl, "libtss2-mu.so.0", LOG_DEBUG, + DLSYM_ARG(Tss2_MU_TPM2_CC_Marshal), ++ DLSYM_ARG(Tss2_MU_TPM2_HANDLE_Marshal), ++ DLSYM_ARG(Tss2_MU_TPM2B_DIGEST_Marshal), ++ DLSYM_ARG(Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal), ++ DLSYM_ARG(Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal), ++ DLSYM_ARG(Tss2_MU_TPM2B_NAME_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_PRIVATE_Unmarshal), + DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_PUBLIC_Unmarshal), ++ DLSYM_ARG(Tss2_MU_TPM2B_SENSITIVE_Marshal), + DLSYM_ARG(Tss2_MU_TPML_PCR_SELECTION_Marshal), + DLSYM_ARG(Tss2_MU_TPMS_NV_PUBLIC_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_NV_PUBLIC_Marshal), + DLSYM_ARG(Tss2_MU_TPM2B_NV_PUBLIC_Unmarshal), ++ DLSYM_ARG(Tss2_MU_TPMS_ECC_POINT_Marshal), + DLSYM_ARG(Tss2_MU_TPMT_HA_Marshal), +- DLSYM_ARG(Tss2_MU_TPMT_PUBLIC_Marshal)); ++ DLSYM_ARG(Tss2_MU_TPMT_PUBLIC_Marshal), ++ DLSYM_ARG(Tss2_MU_UINT32_Marshal)); + } + + void Esys_Freep(void *p) { +@@ -2336,6 +2354,48 @@ int tpm2_create_loaded( + return 0; + } + ++static int tpm2_import( ++ Tpm2Context *c, ++ const Tpm2Handle *parent, ++ const Tpm2Handle *session, ++ const TPM2B_PUBLIC *public, ++ const TPM2B_PRIVATE *private, ++ const TPM2B_ENCRYPTED_SECRET *seed, ++ const TPM2B_DATA *encryption_key, ++ const TPMT_SYM_DEF_OBJECT *symmetric, ++ TPM2B_PRIVATE **ret_private) { ++ ++ TSS2_RC rc; ++ ++ assert(c); ++ assert(parent); ++ assert(!!encryption_key == !!symmetric); ++ assert(public); ++ assert(private); ++ assert(seed); ++ assert(ret_private); ++ ++ log_debug("Importing key into TPM."); ++ ++ rc = sym_Esys_Import( ++ c->esys_context, ++ parent->esys_handle, ++ session ? session->esys_handle : ESYS_TR_PASSWORD, ++ ESYS_TR_NONE, ++ ESYS_TR_NONE, ++ encryption_key, ++ public, ++ private, ++ seed, ++ symmetric ?: &(TPMT_SYM_DEF_OBJECT){ .algorithm = TPM2_ALG_NULL, }, ++ ret_private); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to import key into TPM: %s", sym_Tss2_RC_Decode(rc)); ++ ++ return 0; ++} ++ + /* Read hash values from the specified PCR selection. Provides a Tpm2PCRValue array that contains all + * requested PCR values, in the order provided by the TPM. Normally, the provided pcr values will match + * exactly what is in the provided selection, but the TPM may ignore some selected PCRs (for example, if an +@@ -3157,7 +3217,7 @@ int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name) + + /* Get the "name" of a key from the TPM. + * +- * The "name" of a key is explained above in tpm2_calculate_name(). ++ * The "name" of a key is explained above in tpm2_calculate_pubkey_name(). + * + * The handle must reference a key already present in the TPM. It may be either a public key only, or a + * public/private keypair. */ +@@ -3825,12 +3885,14 @@ int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *r + #endif + } + +-/* Marshal the public and private objects into a single nonstandard 'blob'. This is not a (publicly) standard +- * format, this is specific to how we currently store the sealed object. This 'blob' can be unmarshalled by ++/* Marshal the public, private, and seed objects into a single nonstandard 'blob'. The public and private ++ * objects are required, while the seed is optional. This is not a (publicly) standard format, this is ++ * specific to how we currently store the sealed object. This 'blob' can be unmarshalled by + * tpm2_unmarshal_blob(). */ + static int tpm2_marshal_blob( + const TPM2B_PUBLIC *public, + const TPM2B_PRIVATE *private, ++ const TPM2B_ENCRYPTED_SECRET *seed, + void **ret_blob, + size_t *ret_blob_size) { + +@@ -3842,6 +3904,8 @@ static int tpm2_marshal_blob( + assert(ret_blob_size); + + size_t max_size = sizeof(*private) + sizeof(*public); ++ if (seed) ++ max_size += sizeof(*seed); + + _cleanup_free_ void *blob = malloc(max_size); + if (!blob) +@@ -3858,26 +3922,36 @@ static int tpm2_marshal_blob( + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to marshal public key: %s", sym_Tss2_RC_Decode(rc)); + ++ if (seed) { ++ rc = sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Marshal(seed, blob, max_size, &blob_size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal encrypted seed: %s", sym_Tss2_RC_Decode(rc)); ++ } ++ + *ret_blob = TAKE_PTR(blob); + *ret_blob_size = blob_size; + + return 0; + } + +-/* Unmarshal the 'blob' into public and private objects. This is not a (publicly) standard format, this is +- * specific to how we currently store the sealed object. This expects the 'blob' to have been created by ++/* Unmarshal the 'blob' into public, private, and seed objects. The public and private objects are required ++ * in the 'blob', while the seed is optional. This is not a (publicly) standard format, this is specific to ++ * how we currently store the sealed object. This expects the 'blob' to have been created by + * tpm2_marshal_blob(). */ + static int tpm2_unmarshal_blob( + const void *blob, + size_t blob_size, + TPM2B_PUBLIC *ret_public, +- TPM2B_PRIVATE *ret_private) { ++ TPM2B_PRIVATE *ret_private, ++ TPM2B_ENCRYPTED_SECRET *ret_seed) { + + TSS2_RC rc; + + assert(blob); + assert(ret_public); + assert(ret_private); ++ assert(ret_seed); + + TPM2B_PRIVATE private = {}; + size_t offset = 0; +@@ -3892,8 +3966,67 @@ static int tpm2_unmarshal_blob( + return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), + "Failed to unmarshal public key: %s", sym_Tss2_RC_Decode(rc)); + ++ TPM2B_ENCRYPTED_SECRET seed = {}; ++ if (blob_size > offset) { ++ rc = sym_Tss2_MU_TPM2B_ENCRYPTED_SECRET_Unmarshal(blob, blob_size, &offset, &seed); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to unmarshal encrypted seed: %s", sym_Tss2_RC_Decode(rc)); ++ } ++ + *ret_public = public; + *ret_private = private; ++ *ret_seed = seed; ++ ++ return 0; ++} ++ ++/* Calculate a serialized handle. Once the upstream tpm2-tss library provides an api to do this, we can ++ * remove this function. The addition of this functionality in tpm2-tss may be tracked here: ++ * https://github.com/tpm2-software/tpm2-tss/issues/2575 */ ++int tpm2_calculate_serialize( ++ TPM2_HANDLE handle, ++ const TPM2B_NAME *name, ++ const TPM2B_PUBLIC *public, ++ void **ret_serialized, ++ size_t *ret_serialized_size) { ++ ++ TSS2_RC rc; ++ ++ assert(name); ++ assert(public); ++ assert(ret_serialized); ++ assert(ret_serialized_size); ++ ++ size_t max_size = sizeof(TPM2_HANDLE) + sizeof(TPM2B_NAME) + sizeof(uint32_t) + sizeof(TPM2B_PUBLIC); ++ _cleanup_free_ void *serialized = malloc(max_size); ++ if (!serialized) ++ return log_oom_debug(); ++ ++ size_t serialized_size = 0; ++ rc = sym_Tss2_MU_TPM2_HANDLE_Marshal(handle, serialized, max_size, &serialized_size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal tpm handle: %s", sym_Tss2_RC_Decode(rc)); ++ ++ rc = sym_Tss2_MU_TPM2B_NAME_Marshal(name, serialized, max_size, &serialized_size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal name: %s", sym_Tss2_RC_Decode(rc)); ++ ++ /* This is defined (non-publicly) in the tpm2-tss source as IESYSC_KEY_RSRC, to a value of "1". */ ++ rc = sym_Tss2_MU_UINT32_Marshal(UINT32_C(1), serialized, max_size, &serialized_size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal esys resource id: %s", sym_Tss2_RC_Decode(rc)); ++ ++ rc = sym_Tss2_MU_TPM2B_PUBLIC_Marshal(public, serialized, max_size, &serialized_size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal public: %s", sym_Tss2_RC_Decode(rc)); ++ ++ *ret_serialized = TAKE_PTR(serialized); ++ *ret_serialized_size = serialized_size; + + return 0; + } +@@ -3957,6 +4090,654 @@ static int tpm2_deserialize( + return 0; + } + ++#if HAVE_OPENSSL ++ ++/* KDFa() as defined by the TPM spec. */ ++static int tpm2_kdfa( ++ TPMI_ALG_HASH hash_alg, ++ const void *key, ++ size_t key_len, ++ const char *label, ++ const void *context, ++ size_t context_len, ++ size_t bits, ++ void **ret_key, ++ size_t *ret_key_len) { ++ ++ int r; ++ ++ assert(key); ++ assert(label); ++ assert(context || context_len == 0); ++ assert(bits > 0); ++ assert(bits <= SIZE_MAX - 7); ++ assert(ret_key); ++ assert(ret_key_len); ++ ++ log_debug("Calculating KDFa()."); ++ ++ size_t len = DIV_ROUND_UP(bits, 8); ++ ++ const char *hash_alg_name = tpm2_hash_alg_to_string(hash_alg); ++ if (!hash_alg_name) ++ return -EOPNOTSUPP; ++ ++ _cleanup_free_ void *buf = NULL; ++ r = kdf_kb_hmac_derive( ++ "COUNTER", ++ hash_alg_name, ++ key, ++ key_len, ++ label, ++ strlen(label), ++ context, ++ context_len, ++ /* seed= */ NULL, ++ /* seed_len= */ 0, ++ len, ++ &buf); ++ if (r < 0) ++ return r; ++ ++ /* If the number of bits results in a partial byte, the TPM spec requires we zero the unrequested ++ * bits in the MSB (i.e. at index 0). From the spec Part 1 ("Architecture") section on Key ++ * Derivation Function, specifically KDFa(): ++ * ++ * "The implied return from this function is a sequence of octets with a length equal to (bits + 7) / ++ * 8. If bits is not an even multiple of 8, then the returned value occupies the least significant ++ * bits of the returned octet array, and the additional, high-order bits in the 0th octet are ++ * CLEAR. The unused bits of the most significant octet (MSO) are masked off and not shifted." */ ++ size_t partial = bits % 8; ++ if (partial > 0) ++ ((uint8_t*) buf)[0] &= 0xffu >> (8 - partial); ++ ++ *ret_key = TAKE_PTR(buf); ++ *ret_key_len = len; ++ ++ return 0; ++} ++ ++/* KDFe() as defined by the TPM spec. */ ++static int tpm2_kdfe( ++ TPMI_ALG_HASH hash_alg, ++ const void *shared_secret, ++ size_t shared_secret_len, ++ const char *label, ++ const void *context_u, ++ size_t context_u_size, ++ const void *context_v, ++ size_t context_v_size, ++ size_t bits, ++ void **ret_key, ++ size_t *ret_key_len) { ++ ++ int r; ++ ++ assert(shared_secret); ++ assert(label); ++ assert(context_u); ++ assert(context_v); ++ assert(bits > 0); ++ assert(bits <= SIZE_MAX - 7); ++ assert(ret_key); ++ assert(ret_key_len); ++ ++ log_debug("Calculating KDFe()."); ++ ++ size_t len = DIV_ROUND_UP(bits, 8); ++ ++ const char *hash_alg_name = tpm2_hash_alg_to_string(hash_alg); ++ if (!hash_alg_name) ++ return -EOPNOTSUPP; ++ ++ size_t info_len = strlen(label) + 1 + context_u_size + context_v_size; ++ _cleanup_free_ void *info = malloc(info_len); ++ if (!info) ++ return log_oom_debug(); ++ ++ void *end = mempcpy(mempcpy(stpcpy(info, label) + 1, context_u, context_u_size), context_v, context_v_size); ++ /* assert we copied exactly the right amount that we allocated */ ++ assert(end > info && (uintptr_t) end - (uintptr_t) info == info_len); ++ ++ _cleanup_free_ void *buf = NULL; ++ r = kdf_ss_derive( ++ hash_alg_name, ++ shared_secret, ++ shared_secret_len, ++ /* salt= */ NULL, ++ /* salt_size= */ 0, ++ info, ++ info_len, ++ len, ++ &buf); ++ if (r < 0) ++ return r; ++ ++ *ret_key = TAKE_PTR(buf); ++ *ret_key_len = len; ++ ++ return 0; ++} ++ ++static int tpm2_calculate_seal_public( ++ const TPM2B_PUBLIC *parent, ++ const TPMA_OBJECT *attributes, ++ const TPM2B_DIGEST *policy, ++ const TPM2B_DIGEST *seed, ++ const void *secret, ++ size_t secret_size, ++ TPM2B_PUBLIC *ret) { ++ ++ int r; ++ ++ assert(parent); ++ assert(seed); ++ assert(secret); ++ assert(ret); ++ ++ log_debug("Calculating public part of sealed object."); ++ ++ struct iovec data[] = { ++ IOVEC_MAKE((void*) seed->buffer, seed->size), ++ IOVEC_MAKE((void*) secret, secret_size), ++ }; ++ TPM2B_DIGEST unique; ++ r = tpm2_digest_many( ++ parent->publicArea.nameAlg, ++ &unique, ++ data, ++ ELEMENTSOF(data), ++ /* extend= */ false); ++ if (r < 0) ++ return r; ++ ++ *ret = (TPM2B_PUBLIC) { ++ .size = sizeof(TPMT_PUBLIC), ++ .publicArea = { ++ .type = TPM2_ALG_KEYEDHASH, ++ .nameAlg = parent->publicArea.nameAlg, ++ .objectAttributes = attributes ? *attributes : 0, ++ .authPolicy = policy ? *policy : TPM2B_DIGEST_MAKE(NULL, unique.size), ++ .parameters.keyedHashDetail.scheme.scheme = TPM2_ALG_NULL, ++ .unique.keyedHash = unique, ++ }, ++ }; ++ ++ return 0; ++} ++ ++static int tpm2_calculate_seal_private( ++ const TPM2B_PUBLIC *parent, ++ const TPM2B_NAME *name, ++ const char *pin, ++ const TPM2B_DIGEST *seed, ++ const void *secret, ++ size_t secret_size, ++ TPM2B_PRIVATE *ret) { ++ ++ TSS2_RC rc; ++ int r; ++ ++ assert(parent); ++ assert(name); ++ assert(seed); ++ assert(secret); ++ assert(ret); ++ ++ log_debug("Calculating private part of sealed object."); ++ ++ _cleanup_free_ void *storage_key = NULL; ++ size_t storage_key_size; ++ r = tpm2_kdfa(parent->publicArea.nameAlg, ++ seed->buffer, ++ seed->size, ++ "STORAGE", ++ name->name, ++ name->size, ++ (size_t) parent->publicArea.parameters.asymDetail.symmetric.keyBits.sym, ++ &storage_key, ++ &storage_key_size); ++ if (r < 0) ++ return log_debug_errno(r, "Could not calculate storage key KDFa: %m"); ++ ++ r = tpm2_hash_alg_to_size(parent->publicArea.nameAlg); ++ if (r < 0) ++ return -EOPNOTSUPP; ++ ++ size_t bits = (size_t) r * 8; ++ ++ _cleanup_free_ void *integrity_key = NULL; ++ size_t integrity_key_size; ++ r = tpm2_kdfa(parent->publicArea.nameAlg, ++ seed->buffer, ++ seed->size, ++ "INTEGRITY", ++ /* context= */ NULL, ++ /* n_context= */ 0, ++ bits, ++ &integrity_key, ++ &integrity_key_size); ++ if (r < 0) ++ return log_debug_errno(r, "Could not calculate integrity key KDFa: %m"); ++ ++ TPM2B_AUTH auth = {}; ++ if (pin) { ++ r = tpm2_get_pin_auth(parent->publicArea.nameAlg, pin, &auth); ++ if (r < 0) ++ return r; ++ } ++ ++ TPM2B_SENSITIVE sensitive = { ++ .size = sizeof(TPMT_SENSITIVE), ++ .sensitiveArea = { ++ .sensitiveType = TPM2_ALG_KEYEDHASH, ++ .authValue = auth, ++ .seedValue = *seed, ++ .sensitive.bits = TPM2B_SENSITIVE_DATA_MAKE(secret, secret_size), ++ }, ++ }; ++ ++ _cleanup_free_ void *marshalled_sensitive = malloc(sizeof(sensitive)); ++ if (!marshalled_sensitive) ++ return log_oom_debug(); ++ ++ size_t marshalled_sensitive_size = 0; ++ rc = sym_Tss2_MU_TPM2B_SENSITIVE_Marshal( ++ &sensitive, ++ marshalled_sensitive, ++ sizeof(sensitive), ++ &marshalled_sensitive_size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal sensitive: %s", sym_Tss2_RC_Decode(rc)); ++ ++ const char *sym_alg = tpm2_sym_alg_to_string(parent->publicArea.parameters.asymDetail.symmetric.algorithm); ++ if (!sym_alg) ++ return -EOPNOTSUPP; ++ ++ const char *sym_mode = tpm2_sym_mode_to_string(parent->publicArea.parameters.asymDetail.symmetric.mode.sym); ++ if (!sym_mode) ++ return -EOPNOTSUPP; ++ ++ _cleanup_free_ void *encrypted_sensitive = NULL; ++ size_t encrypted_sensitive_size; ++ r = openssl_cipher( ++ sym_alg, ++ parent->publicArea.parameters.asymDetail.symmetric.keyBits.sym, ++ sym_mode, ++ storage_key, storage_key_size, ++ /* iv= */ NULL, /* n_iv= */ 0, ++ marshalled_sensitive, marshalled_sensitive_size, ++ &encrypted_sensitive, &encrypted_sensitive_size); ++ if (r < 0) ++ return r; ++ ++ const char *hash_alg_name = tpm2_hash_alg_to_string(parent->publicArea.nameAlg); ++ if (!hash_alg_name) ++ return -EOPNOTSUPP; ++ ++ _cleanup_free_ void *hmac_buffer = NULL; ++ size_t hmac_size = 0; ++ struct iovec hmac_data[] = { ++ IOVEC_MAKE((void*) encrypted_sensitive, encrypted_sensitive_size), ++ IOVEC_MAKE((void*) name->name, name->size), ++ }; ++ r = openssl_hmac_many( ++ hash_alg_name, ++ integrity_key, ++ integrity_key_size, ++ hmac_data, ++ ELEMENTSOF(hmac_data), ++ &hmac_buffer, ++ &hmac_size); ++ if (r < 0) ++ return r; ++ ++ TPM2B_DIGEST outer_hmac = TPM2B_DIGEST_MAKE(hmac_buffer, hmac_size); ++ ++ TPM2B_PRIVATE private = {}; ++ size_t private_size = 0; ++ rc = sym_Tss2_MU_TPM2B_DIGEST_Marshal( ++ &outer_hmac, ++ private.buffer, ++ sizeof(private.buffer), ++ &private_size); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal digest: %s", sym_Tss2_RC_Decode(rc)); ++ private.size = private_size; ++ ++ assert(sizeof(private.buffer) - private.size >= encrypted_sensitive_size); ++ memcpy_safe(&private.buffer[private.size], encrypted_sensitive, encrypted_sensitive_size); ++ private.size += encrypted_sensitive_size; ++ ++ *ret = private; ++ ++ return 0; ++} ++ ++static int tpm2_calculate_seal_rsa_seed( ++ const TPM2B_PUBLIC *parent, ++ void **ret_seed, ++ size_t *ret_seed_size, ++ void **ret_encrypted_seed, ++ size_t *ret_encrypted_seed_size) { ++ ++ int r; ++ ++ assert(parent); ++ assert(ret_seed); ++ assert(ret_seed_size); ++ assert(ret_encrypted_seed); ++ assert(ret_encrypted_seed_size); ++ ++ log_debug("Calculating encrypted seed for RSA sealed object."); ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *parent_pkey = NULL; ++ r = tpm2_tpm2b_public_to_openssl_pkey(parent, &parent_pkey); ++ if (r < 0) ++ return log_debug_errno(r, "Could not convert TPM2B_PUBLIC to Openssl PKEY: %m"); ++ ++ r = tpm2_hash_alg_to_size(parent->publicArea.nameAlg); ++ if (r < 0) ++ return -EOPNOTSUPP; ++ ++ size_t seed_size = (size_t) r; ++ ++ _cleanup_free_ void *seed = malloc(seed_size); ++ if (!seed) ++ return log_oom_debug(); ++ ++ r = crypto_random_bytes(seed, seed_size); ++ if (r < 0) ++ return log_debug_errno(r, "Failed to generate random seed: %m"); ++ ++ const char *hash_alg_name = tpm2_hash_alg_to_string(parent->publicArea.nameAlg); ++ if (!hash_alg_name) ++ return -EOPNOTSUPP; ++ ++ _cleanup_free_ void *encrypted_seed = NULL; ++ size_t encrypted_seed_size; ++ r = rsa_oaep_encrypt_bytes( ++ parent_pkey, ++ hash_alg_name, ++ "DUPLICATE", ++ seed, ++ seed_size, ++ &encrypted_seed, ++ &encrypted_seed_size); ++ if (r < 0) ++ return log_debug_errno(r, "Could not RSA-OAEP encrypt random seed: %m"); ++ ++ *ret_seed = TAKE_PTR(seed); ++ *ret_seed_size = seed_size; ++ *ret_encrypted_seed = TAKE_PTR(encrypted_seed); ++ *ret_encrypted_seed_size = encrypted_seed_size; ++ ++ return 0; ++} ++ ++static int tpm2_calculate_seal_ecc_seed( ++ const TPM2B_PUBLIC *parent, ++ void **ret_seed, ++ size_t *ret_seed_size, ++ void **ret_encrypted_seed, ++ size_t *ret_encrypted_seed_size) { ++ ++ TSS2_RC rc; ++ int r; ++ ++ assert(parent); ++ assert(ret_seed); ++ assert(ret_seed_size); ++ assert(ret_encrypted_seed); ++ assert(ret_encrypted_seed_size); ++ ++ log_debug("Calculating encrypted seed for ECC sealed object."); ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *parent_pkey = NULL; ++ r = tpm2_tpm2b_public_to_openssl_pkey(parent, &parent_pkey); ++ if (r < 0) ++ return log_debug_errno(r, "Could not convert TPM2B_PUBLIC to Openssl PKEY: %m"); ++ ++ int curve_id; ++ r = ecc_pkey_to_curve_x_y( ++ parent_pkey, ++ &curve_id, ++ /* ret_x= */ NULL, /* ret_x_size= */ 0, ++ /* ret_y= */ NULL, /* ret_y_size= */ 0); ++ if (r < 0) ++ return r; ++ ++ _cleanup_(EVP_PKEY_freep) EVP_PKEY *pkey = NULL; ++ r = ecc_pkey_new(curve_id, &pkey); ++ if (r < 0) ++ return r; ++ ++ _cleanup_free_ void *shared_secret = NULL; ++ size_t shared_secret_size; ++ r = ecc_ecdh(pkey, parent_pkey, &shared_secret, &shared_secret_size); ++ if (r < 0) ++ return log_debug_errno(r, "Could not generate ECC shared secret: %m"); ++ ++ _cleanup_free_ void *x = NULL, *y = NULL; ++ size_t x_size, y_size; ++ r = ecc_pkey_to_curve_x_y(pkey, /* curve_id= */ NULL, &x, &x_size, &y, &y_size); ++ if (r < 0) ++ return log_debug_errno(r, "Could not get ECC get x/y: %m"); ++ ++ r = TPM2B_ECC_PARAMETER_CHECK_SIZE(x_size); ++ if (r < 0) ++ return log_debug_errno(r, "ECC point x size %zu is too large: %m", x_size); ++ ++ r = TPM2B_ECC_PARAMETER_CHECK_SIZE(y_size); ++ if (r < 0) ++ return log_debug_errno(r, "ECC point y size %zu is too large: %m", y_size); ++ ++ TPMS_ECC_POINT point = { ++ .x = TPM2B_ECC_PARAMETER_MAKE(x, x_size), ++ .y = TPM2B_ECC_PARAMETER_MAKE(y, y_size), ++ }; ++ ++ _cleanup_free_ void *encrypted_seed = malloc(sizeof(point)); ++ if (!encrypted_seed) ++ return log_oom_debug(); ++ ++ size_t encrypted_seed_size = 0; ++ rc = sym_Tss2_MU_TPMS_ECC_POINT_Marshal(&point, encrypted_seed, sizeof(point), &encrypted_seed_size); ++ if (rc != TPM2_RC_SUCCESS) ++ return log_debug_errno(SYNTHETIC_ERRNO(ENOTRECOVERABLE), ++ "Failed to marshal ECC point: %s", sym_Tss2_RC_Decode(rc)); ++ ++ r = tpm2_hash_alg_to_size(parent->publicArea.nameAlg); ++ if (r < 0) ++ return -EOPNOTSUPP; ++ ++ size_t bits = (size_t) r * 8; ++ ++ _cleanup_free_ void *seed = NULL; ++ size_t seed_size; ++ r = tpm2_kdfe(parent->publicArea.nameAlg, ++ shared_secret, ++ shared_secret_size, ++ "DUPLICATE", ++ x, ++ x_size, ++ parent->publicArea.unique.ecc.x.buffer, ++ parent->publicArea.unique.ecc.x.size, ++ bits, ++ &seed, ++ &seed_size); ++ if (r < 0) ++ return log_debug_errno(r, "Could not calculate KDFe: %m"); ++ ++ *ret_seed = TAKE_PTR(seed); ++ *ret_seed_size = seed_size; ++ *ret_encrypted_seed = TAKE_PTR(encrypted_seed); ++ *ret_encrypted_seed_size = encrypted_seed_size; ++ ++ return 0; ++} ++ ++static int tpm2_calculate_seal_seed( ++ const TPM2B_PUBLIC *parent, ++ TPM2B_DIGEST *ret_seed, ++ TPM2B_ENCRYPTED_SECRET *ret_encrypted_seed) { ++ ++ int r; ++ ++ assert(parent); ++ assert(ret_seed); ++ assert(ret_encrypted_seed); ++ ++ log_debug("Calculating encrypted seed for sealed object."); ++ ++ _cleanup_free_ void *seed = NULL, *encrypted_seed = NULL; ++ size_t seed_size, encrypted_seed_size; ++ if (parent->publicArea.type == TPM2_ALG_RSA) ++ r = tpm2_calculate_seal_rsa_seed(parent, &seed, &seed_size, &encrypted_seed, &encrypted_seed_size); ++ else if (parent->publicArea.type == TPM2_ALG_ECC) ++ r = tpm2_calculate_seal_ecc_seed(parent, &seed, &seed_size, &encrypted_seed, &encrypted_seed_size); ++ else ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), ++ "Unsupported parent key type 0x%" PRIx16, parent->publicArea.type); ++ if (r < 0) ++ return log_debug_errno(r, "Could not calculate encrypted seed: %m"); ++ ++ *ret_seed = TPM2B_DIGEST_MAKE(seed, seed_size); ++ *ret_encrypted_seed = TPM2B_ENCRYPTED_SECRET_MAKE(encrypted_seed, encrypted_seed_size); ++ ++ return 0; ++} ++ ++#endif /* HAVE_OPENSSL */ ++ ++int tpm2_calculate_seal( ++ TPM2_HANDLE parent_handle, ++ const TPM2B_PUBLIC *parent_public, ++ const TPMA_OBJECT *attributes, ++ const void *secret, ++ size_t secret_size, ++ const TPM2B_DIGEST *policy, ++ const char *pin, ++ void **ret_secret, ++ size_t *ret_secret_size, ++ void **ret_blob, ++ size_t *ret_blob_size, ++ void **ret_serialized_parent, ++ size_t *ret_serialized_parent_size) { ++ ++#if HAVE_OPENSSL ++ int r; ++ ++ assert(parent_public); ++ assert(secret || secret_size == 0); ++ assert(secret || ret_secret); ++ assert(!(secret && ret_secret)); /* Either provide a secret, or we create one, but not both */ ++ assert(ret_blob); ++ assert(ret_blob_size); ++ assert(ret_serialized_parent); ++ assert(ret_serialized_parent_size); ++ ++ log_debug("Calculating sealed object."); ++ ++ /* Default to the SRK. */ ++ if (parent_handle == 0) ++ parent_handle = TPM2_SRK_HANDLE; ++ ++ switch (TPM2_HANDLE_TYPE(parent_handle)) { ++ case TPM2_HT_PERSISTENT: ++ case TPM2_HT_NV_INDEX: ++ break; ++ case TPM2_HT_TRANSIENT: ++ log_warning("Handle is transient, sealed secret may not be recoverable."); ++ break; ++ default: ++ return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Handle 0x%" PRIx32 " not persistent, transient, or NV.", ++ parent_handle); ++ } ++ ++ _cleanup_(erase_and_freep) void *generated_secret = NULL; ++ if (!secret) { ++ /* No secret provided, generate a random secret. We use SHA256 digest length, though it can ++ * be up to TPM2_MAX_SEALED_DATA. The secret length is not limited to the nameAlg hash ++ * size. */ ++ secret_size = TPM2_SHA256_DIGEST_SIZE; ++ generated_secret = malloc(secret_size); ++ if (!generated_secret) ++ return log_oom_debug(); ++ ++ r = crypto_random_bytes(generated_secret, secret_size); ++ if (r < 0) ++ return log_debug_errno(r, "Failed to generate secret key: %m"); ++ ++ secret = generated_secret; ++ } ++ ++ if (secret_size > TPM2_MAX_SEALED_DATA) ++ return log_debug_errno(SYNTHETIC_ERRNO(EOVERFLOW), ++ "Secret size %zu too large, limit is %d bytes.", ++ secret_size, TPM2_MAX_SEALED_DATA); ++ ++ TPM2B_DIGEST random_seed; ++ TPM2B_ENCRYPTED_SECRET seed; ++ r = tpm2_calculate_seal_seed(parent_public, &random_seed, &seed); ++ if (r < 0) ++ return r; ++ ++ TPM2B_PUBLIC public; ++ r = tpm2_calculate_seal_public(parent_public, attributes, policy, &random_seed, secret, secret_size, &public); ++ if (r < 0) ++ return r; ++ ++ TPM2B_NAME name; ++ r = tpm2_calculate_pubkey_name(&public.publicArea, &name); ++ if (r < 0) ++ return r; ++ ++ TPM2B_PRIVATE private; ++ r = tpm2_calculate_seal_private(parent_public, &name, pin, &random_seed, secret, secret_size, &private); ++ if (r < 0) ++ return r; ++ ++ _cleanup_free_ void *blob = NULL; ++ size_t blob_size; ++ r = tpm2_marshal_blob(&public, &private, &seed, &blob, &blob_size); ++ if (r < 0) ++ return log_debug_errno(r, "Could not create sealed blob: %m"); ++ ++ TPM2B_NAME parent_name; ++ r = tpm2_calculate_pubkey_name(&parent_public->publicArea, &parent_name); ++ if (r < 0) ++ return r; ++ ++ _cleanup_free_ void *serialized_parent = NULL; ++ size_t serialized_parent_size; ++ r = tpm2_calculate_serialize( ++ parent_handle, ++ &parent_name, ++ parent_public, ++ &serialized_parent, ++ &serialized_parent_size); ++ if (r < 0) ++ return r; ++ ++ if (ret_secret) ++ *ret_secret = TAKE_PTR(generated_secret); ++ if (ret_secret_size) ++ *ret_secret_size = secret_size; ++ *ret_blob = TAKE_PTR(blob); ++ *ret_blob_size = blob_size; ++ *ret_serialized_parent = TAKE_PTR(serialized_parent); ++ *ret_serialized_parent_size = serialized_parent_size; ++ ++ return 0; ++#else /* HAVE_OPENSSL */ ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), "OpenSSL support is disabled."); ++#endif ++} ++ + int tpm2_seal(Tpm2Context *c, + uint32_t seal_key_handle, + const TPM2B_DIGEST *policy, +@@ -4119,7 +4900,7 @@ int tpm2_seal(Tpm2Context *c, + + _cleanup_free_ void *blob = NULL; + size_t blob_size = 0; +- r = tpm2_marshal_blob(public, private, &blob, &blob_size); ++ r = tpm2_marshal_blob(public, private, /* seed= */ NULL, &blob, &blob_size); + if (r < 0) + return log_debug_errno(r, "Could not create sealed blob: %m"); + +@@ -4203,7 +4984,8 @@ int tpm2_unseal(Tpm2Context *c, + + TPM2B_PUBLIC public; + TPM2B_PRIVATE private; +- r = tpm2_unmarshal_blob(blob, blob_size, &public, &private); ++ TPM2B_ENCRYPTED_SECRET seed = {}; ++ r = tpm2_unmarshal_blob(blob, blob_size, &public, &private, &seed); + if (r < 0) + return log_debug_errno(r, "Could not extract parts from blob: %m"); + +@@ -4239,6 +5021,24 @@ int tpm2_unseal(Tpm2Context *c, + return log_debug_errno(SYNTHETIC_ERRNO(EINVAL), + "No SRK or primary alg provided."); + ++ if (seed.size > 0) { ++ /* This is a calculated (or duplicated) sealed object, and must be imported. */ ++ _cleanup_free_ TPM2B_PRIVATE *imported_private = NULL; ++ r = tpm2_import(c, ++ primary_handle, ++ /* session= */ NULL, ++ &public, ++ &private, ++ &seed, ++ /* encryption_key= */ NULL, ++ /* symmetric= */ NULL, ++ &imported_private); ++ if (r < 0) ++ return r; ++ ++ private = *imported_private; ++ } ++ + log_debug("Loading HMAC key into TPM."); + + /* +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 59c6a6fa92..1b84783660 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -27,6 +27,11 @@ typedef enum TPM2Flags { + * the Provisioning Guidance document for more details. */ + #define TPM2_SRK_HANDLE UINT32_C(0x81000001) + ++/* The TPM specification limits sealed data to MAX_SYM_DATA. Unfortunately, tpm2-tss incorrectly ++ * defines this value as 256; the TPM specification Part 2 ("Structures") section ++ * "TPMU_SENSITIVE_CREATE" states "For interoperability, MAX_SYM_DATA should be 128." */ ++#define TPM2_MAX_SEALED_DATA UINT16_C(128) ++ + static inline bool TPM2_PCR_INDEX_VALID(unsigned pcr) { + return pcr < TPM2_PCRS_MAX; + } +@@ -179,7 +184,9 @@ int tpm2_calculate_pubkey_name(const TPMT_PUBLIC *public, TPM2B_NAME *ret_name); + int tpm2_calculate_policy_auth_value(TPM2B_DIGEST *digest); + int tpm2_calculate_policy_authorize(const TPM2B_PUBLIC *public, const TPM2B_DIGEST *policy_ref, TPM2B_DIGEST *digest); + int tpm2_calculate_policy_pcr(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, TPM2B_DIGEST *digest); ++int tpm2_calculate_serialize(TPM2_HANDLE handle, const TPM2B_NAME *name, const TPM2B_PUBLIC *public, void **ret_serialized, size_t *ret_serialized_size); + int tpm2_calculate_sealing_policy(const Tpm2PCRValue *pcr_values, size_t n_pcr_values, const TPM2B_PUBLIC *public, bool use_pin, TPM2B_DIGEST *digest); ++int tpm2_calculate_seal(TPM2_HANDLE parent_handle, const TPM2B_PUBLIC *parent_public, const TPMA_OBJECT *attributes, const void *secret, size_t secret_size, const TPM2B_DIGEST *policy, const char *pin, void **ret_secret, size_t *ret_secret_size, void **ret_blob, size_t *ret_blob_size, void **ret_serialized_parent, size_t *ret_serialized_parent_size); + + int tpm2_get_srk_template(TPMI_ALG_PUBLIC alg, TPMT_PUBLIC *ret_template); + int tpm2_get_best_srk_template(Tpm2Context *c, TPMT_PUBLIC *ret_template); diff --git a/SOURCES/0640-tpm2-update-test-tpm2-for-tpm2_calculate_seal.patch b/SOURCES/0640-tpm2-update-test-tpm2-for-tpm2_calculate_seal.patch new file mode 100644 index 0000000..4abb2ee --- /dev/null +++ b/SOURCES/0640-tpm2-update-test-tpm2-for-tpm2_calculate_seal.patch @@ -0,0 +1,175 @@ +From bbaf58dd3fc34556d552689f3f1b6c94860b3ffe Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Tue, 3 Oct 2023 10:25:19 -0400 +Subject: [PATCH] tpm2: update test-tpm2 for tpm2_calculate_seal() + +Add testing for tpm2_calculate_seal(). + +(cherry picked from commit 65883f6c1060249c3cd2de34398a787be149138c) + +Related: RHEL-16182 +--- + src/shared/tpm2-util.c | 7 ++-- + src/shared/tpm2-util.h | 2 + + src/test/test-tpm2.c | 89 +++++++++++++++++++++++++++++++++++++++++- + 3 files changed, 94 insertions(+), 4 deletions(-) + +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index e5fc8a72a7..e334564159 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -3889,7 +3889,7 @@ int tpm2_tpm2b_public_from_pem(const void *pem, size_t pem_size, TPM2B_PUBLIC *r + * objects are required, while the seed is optional. This is not a (publicly) standard format, this is + * specific to how we currently store the sealed object. This 'blob' can be unmarshalled by + * tpm2_unmarshal_blob(). */ +-static int tpm2_marshal_blob( ++int tpm2_marshal_blob( + const TPM2B_PUBLIC *public, + const TPM2B_PRIVATE *private, + const TPM2B_ENCRYPTED_SECRET *seed, +@@ -3939,7 +3939,7 @@ static int tpm2_marshal_blob( + * in the 'blob', while the seed is optional. This is not a (publicly) standard format, this is specific to + * how we currently store the sealed object. This expects the 'blob' to have been created by + * tpm2_marshal_blob(). */ +-static int tpm2_unmarshal_blob( ++int tpm2_unmarshal_blob( + const void *blob, + size_t blob_size, + TPM2B_PUBLIC *ret_public, +@@ -4197,7 +4197,8 @@ static int tpm2_kdfe( + + void *end = mempcpy(mempcpy(stpcpy(info, label) + 1, context_u, context_u_size), context_v, context_v_size); + /* assert we copied exactly the right amount that we allocated */ +- assert(end > info && (uintptr_t) end - (uintptr_t) info == info_len); ++ /* Use assert_se() here to avoid emitting warning with -DNDEBUG */ ++ assert_se(end > info && (uintptr_t) end - (uintptr_t) info == info_len); + + _cleanup_free_ void *buf = NULL; + r = kdf_ss_derive( +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 1b84783660..34d4610383 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -118,6 +118,8 @@ int tpm2_tpml_pcr_selection_from_pcr_values(const Tpm2PCRValue *pcr_values, size + int tpm2_create_primary(Tpm2Context *c, const Tpm2Handle *session, const TPM2B_PUBLIC *template, const TPM2B_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, Tpm2Handle **ret_handle); + int tpm2_create(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPMT_PUBLIC *template, const TPMS_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, TPM2B_PRIVATE **ret_private); + int tpm2_create_loaded(Tpm2Context *c, const Tpm2Handle *parent, const Tpm2Handle *session, const TPMT_PUBLIC *template, const TPMS_SENSITIVE_CREATE *sensitive, TPM2B_PUBLIC **ret_public, TPM2B_PRIVATE **ret_private, Tpm2Handle **ret_handle); ++int tpm2_marshal_blob(const TPM2B_PUBLIC *public, const TPM2B_PRIVATE *private, const TPM2B_ENCRYPTED_SECRET *seed, void **ret_blob, size_t *ret_blob_size); ++int tpm2_unmarshal_blob(const void *blob, size_t blob_size, TPM2B_PUBLIC *ret_public, TPM2B_PRIVATE *ret_private, TPM2B_ENCRYPTED_SECRET *ret_seed); + + bool tpm2_supports_alg(Tpm2Context *c, TPM2_ALG_ID alg); + bool tpm2_supports_command(Tpm2Context *c, TPM2_CC command); +diff --git a/src/test/test-tpm2.c b/src/test/test-tpm2.c +index ede08a39aa..44f8340451 100644 +--- a/src/test/test-tpm2.c ++++ b/src/test/test-tpm2.c +@@ -1078,6 +1078,88 @@ static void check_get_or_create_srk(Tpm2Context *c) { + assert_se(memcmp_nn(qname->name, qname->size, qname2->name, qname2->size) == 0); + } + ++#if HAVE_OPENSSL && OPENSSL_VERSION_MAJOR >= 3 ++static void calculate_seal_and_unseal( ++ Tpm2Context *c, ++ TPM2_HANDLE parent_index, ++ const TPM2B_PUBLIC *parent_public) { ++ ++ _cleanup_free_ char *secret_string = NULL; ++ assert_se(asprintf(&secret_string, "The classified documents are in room %x", parent_index) > 0); ++ size_t secret_size = strlen(secret_string) + 1; ++ ++ _cleanup_free_ void *blob = NULL; ++ size_t blob_size = 0; ++ _cleanup_free_ void *serialized_parent = NULL; ++ size_t serialized_parent_size; ++ assert_se(tpm2_calculate_seal( ++ parent_index, ++ parent_public, ++ /* attributes= */ NULL, ++ secret_string, secret_size, ++ /* policy= */ NULL, ++ /* pin= */ NULL, ++ /* ret_secret= */ NULL, /* ret_secret_size= */ 0, ++ &blob, &blob_size, ++ &serialized_parent, &serialized_parent_size) >= 0); ++ ++ _cleanup_free_ void *unsealed_secret = NULL; ++ size_t unsealed_secret_size; ++ assert_se(tpm2_unseal( ++ c, ++ /* hash_pcr_mask= */ 0, ++ /* pcr_bank= */ 0, ++ /* pubkey= */ NULL, /* pubkey_size= */ 0, ++ /* pubkey_pcr_mask= */ 0, ++ /* signature= */ NULL, ++ /* pin= */ NULL, ++ /* primary_alg= */ 0, ++ blob, blob_size, ++ /* known_policy_hash= */ NULL, /* known_policy_hash_size= */ 0, ++ serialized_parent, serialized_parent_size, ++ &unsealed_secret, &unsealed_secret_size) >= 0); ++ ++ assert_se(memcmp_nn(secret_string, secret_size, unsealed_secret, unsealed_secret_size) == 0); ++ ++ char unsealed_string[unsealed_secret_size]; ++ assert_se(snprintf(unsealed_string, unsealed_secret_size, "%s", (char*) unsealed_secret) == (int) unsealed_secret_size - 1); ++ log_debug("Unsealed secret is: %s", unsealed_string); ++} ++ ++static int check_calculate_seal(Tpm2Context *c) { ++ assert(c); ++ int r; ++ ++ _cleanup_free_ TPM2B_PUBLIC *srk_public = NULL; ++ assert_se(tpm2_get_srk(c, NULL, &srk_public, NULL, NULL, NULL) >= 0); ++ calculate_seal_and_unseal(c, TPM2_SRK_HANDLE, srk_public); ++ ++ TPMI_ALG_ASYM test_algs[] = { TPM2_ALG_RSA, TPM2_ALG_ECC, }; ++ for (unsigned i = 0; i < ELEMENTSOF(test_algs); i++) { ++ TPMI_ALG_ASYM alg = test_algs[i]; ++ ++ TPM2B_PUBLIC template = { .size = sizeof(TPMT_PUBLIC), }; ++ assert_se(tpm2_get_srk_template(alg, &template.publicArea) >= 0); ++ ++ _cleanup_free_ TPM2B_PUBLIC *public = NULL; ++ _cleanup_(tpm2_handle_freep) Tpm2Handle *handle = NULL; ++ assert_se(tpm2_create_primary(c, NULL, &template, NULL, &public, &handle) >= 0); ++ ++ /* Once our minimum libtss2-esys version is 2.4.0 or later, this can assume ++ * tpm2_index_from_handle() should always work. */ ++ TPM2_HANDLE index; ++ r = tpm2_index_from_handle(c, handle, &index); ++ if (r == -EOPNOTSUPP) ++ return log_tests_skipped("libtss2-esys version too old to support tpm2_index_from_handle()"); ++ assert_se(r >= 0); ++ ++ calculate_seal_and_unseal(c, index, public); ++ } ++ ++ return 0; ++} ++#endif /* HAVE_OPENSSL && OPENSSL_VERSION_MAJOR >= 3 */ ++ + static void check_seal_unseal_for_handle(Tpm2Context *c, TPM2_HANDLE handle) { + TPM2B_DIGEST policy = TPM2B_DIGEST_MAKE(NULL, TPM2_SHA256_DIGEST_SIZE); + +@@ -1151,6 +1233,7 @@ static void check_seal_unseal(Tpm2Context *c) { + + TEST_RET(tests_which_require_tpm) { + _cleanup_(tpm2_context_unrefp) Tpm2Context *c = NULL; ++ int r = 0; + + if (tpm2_context_new(NULL, &c) < 0) + return log_tests_skipped("Could not find TPM"); +@@ -1162,7 +1245,11 @@ TEST_RET(tests_which_require_tpm) { + check_get_or_create_srk(c); + check_seal_unseal(c); + +- return 0; ++#if HAVE_OPENSSL && OPENSSL_VERSION_MAJOR >= 3 /* calculating sealed object requires openssl >= 3 */ ++ r = check_calculate_seal(c); ++#endif ++ ++ return r; + } + + #endif /* HAVE_TPM2 */ diff --git a/SOURCES/0641-cryptenroll-add-support-for-calculated-TPM2-enrollme.patch b/SOURCES/0641-cryptenroll-add-support-for-calculated-TPM2-enrollme.patch new file mode 100644 index 0000000..da1b255 --- /dev/null +++ b/SOURCES/0641-cryptenroll-add-support-for-calculated-TPM2-enrollme.patch @@ -0,0 +1,288 @@ +From 9d50c30606ce9f04120c67fa1cdbe497ecb393ce Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Fri, 21 Jul 2023 15:49:16 -0400 +Subject: [PATCH] cryptenroll: add support for calculated TPM2 enrollment + +Instead of enrolling the local TPM to a luks volume, use the public key from a +TPM to enroll it into the luks volume. This is useful when enrolling a TPM that +is not currently accessible, for example if the TPM is located on a different +system. + +(cherry picked from commit c3a2a681bed77ce4f9218cd28405994ab5263077) + +Resolves: RHEL-16182 +--- + man/systemd-cryptenroll.xml | 20 +++++++ + src/cryptenroll/cryptenroll-tpm2.c | 84 +++++++++++++++++++++++------- + src/cryptenroll/cryptenroll-tpm2.h | 4 +- + src/cryptenroll/cryptenroll.c | 21 +++++++- + src/shared/tpm2-util.c | 2 +- + src/shared/tpm2-util.h | 2 + + 6 files changed, 111 insertions(+), 22 deletions(-) + +diff --git a/man/systemd-cryptenroll.xml b/man/systemd-cryptenroll.xml +index 979e57d126..36c59cd7c9 100644 +--- a/man/systemd-cryptenroll.xml ++++ b/man/systemd-cryptenroll.xml +@@ -375,6 +375,26 @@ + enrollment to. + + ++ ++ PATH ++ ++ Enroll a TPM2 security chip using its public key. Expects a path referring to the ++ TPM2 public key in TPM2B_PUBLIC format. This cannot be used with , as ++ it performs the same operation, but without connecting to the TPM2 security chip; instead the ++ enrollment is calculated using the provided TPM2 key. This is useful in situations where the TPM2 ++ security chip is not available at the time of enrollment. ++ ++ The key, in most cases, should be the Storage Root Key (SRK) from the TPM2 security chip. If a ++ key from a different handle (not the SRK) is used, you must specify its handle index using ++ . ++ ++ You may use tpm2-tss tools to get the SRK from the TPM2 security chip with tpm2_readpublic1, ++ for example: ++ ++ tpm2_readpublic -c 0x81000001 -o srk.pub ++ ++ + + HANDLE + +diff --git a/src/cryptenroll/cryptenroll-tpm2.c b/src/cryptenroll/cryptenroll-tpm2.c +index 631aeea3b5..6356b1317a 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.c ++++ b/src/cryptenroll/cryptenroll-tpm2.c +@@ -134,6 +134,7 @@ int enroll_tpm2(struct crypt_device *cd, + size_t volume_key_size, + const char *device, + uint32_t seal_key_handle, ++ const char *device_key, + Tpm2PCRValue *hash_pcr_values, + size_t n_hash_pcr_values, + const char *pubkey_path, +@@ -207,16 +208,52 @@ int enroll_tpm2(struct crypt_device *cd, + return log_debug_errno(r, "Failed to read TPM PCR signature: %m"); + } + ++ bool any_pcr_value_specified = tpm2_pcr_values_has_any_values(hash_pcr_values, n_hash_pcr_values); ++ + _cleanup_(tpm2_context_unrefp) Tpm2Context *tpm2_context = NULL; +- r = tpm2_context_new(device, &tpm2_context); +- if (r < 0) +- return log_error_errno(r, "Failed to create TPM2 context: %m"); ++ TPM2B_PUBLIC device_key_public = {}; ++ if (device_key) { ++ _cleanup_free_ char *device_key_buffer = NULL; ++ size_t device_key_buffer_size; ++ r = read_full_file(device_key, &device_key_buffer, &device_key_buffer_size); ++ if (r < 0) ++ return log_error_errno(r, "Failed to read device key from file: %m"); + +- bool pcr_value_specified = tpm2_pcr_values_has_any_values(hash_pcr_values, n_hash_pcr_values); ++ r = dlopen_tpm2(); ++ if (r < 0) ++ return log_debug_errno(r, "TPM2 support not installed: %m"); ++ ++ TSS2_RC rc; ++ size_t offset = 0; ++ rc = sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal( ++ (uint8_t*) device_key_buffer, ++ device_key_buffer_size, ++ &offset, ++ &device_key_public); ++ if (rc != TSS2_RC_SUCCESS) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Could not unmarshal public key from file."); + +- r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values); +- if (r < 0) +- return log_error_errno(r, "Could not read pcr values: %m"); ++ assert(offset <= device_key_buffer_size); ++ if (offset != device_key_buffer_size) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Found %zu bytes of trailing garbage in public key file.", ++ device_key_buffer_size - offset); ++ ++ if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values)) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Must provide all PCR values when using TPM2 device key."); ++ } else { ++ r = tpm2_context_new(device, &tpm2_context); ++ if (r < 0) ++ return log_error_errno(r, "Failed to create TPM2 context: %m"); ++ ++ if (!tpm2_pcr_values_has_all_values(hash_pcr_values, n_hash_pcr_values)) { ++ r = tpm2_pcr_read_missing_values(tpm2_context, hash_pcr_values, n_hash_pcr_values); ++ if (r < 0) ++ return log_error_errno(r, "Could not read pcr values: %m"); ++ } ++ } + + uint16_t hash_pcr_bank = 0; + uint32_t hash_pcr_mask = 0; +@@ -252,15 +289,26 @@ int enroll_tpm2(struct crypt_device *cd, + if (r < 0) + return r; + +- r = tpm2_seal(tpm2_context, +- seal_key_handle, +- &policy, +- pin_str, +- &secret, &secret_size, +- &blob, &blob_size, +- /* ret_primary_alg= */ NULL, +- &srk_buf, +- &srk_buf_size); ++ if (device_key) ++ r = tpm2_calculate_seal( ++ seal_key_handle, ++ &device_key_public, ++ /* attributes= */ NULL, ++ /* secret= */ NULL, /* secret_size= */ 0, ++ &policy, ++ pin_str, ++ &secret, &secret_size, ++ &blob, &blob_size, ++ &srk_buf, &srk_buf_size); ++ else ++ r = tpm2_seal(tpm2_context, ++ seal_key_handle, ++ &policy, ++ pin_str, ++ &secret, &secret_size, ++ &blob, &blob_size, ++ /* ret_primary_alg= */ NULL, ++ &srk_buf, &srk_buf_size); + if (r < 0) + return log_error_errno(r, "Failed to seal to TPM2: %m"); + +@@ -275,8 +323,8 @@ int enroll_tpm2(struct crypt_device *cd, + return r; /* return existing keyslot, so that wiping won't kill it */ + } + +- /* Quick verification that everything is in order, we are not in a hurry after all. */ +- if ((!pubkey || signature_json) && !pcr_value_specified) { ++ /* If possible, verify the sealed data object. */ ++ if ((!pubkey || signature_json) && !any_pcr_value_specified && !device_key) { + _cleanup_(erase_and_freep) void *secret2 = NULL; + size_t secret2_size; + +diff --git a/src/cryptenroll/cryptenroll-tpm2.h b/src/cryptenroll/cryptenroll-tpm2.h +index 8a57bdda01..c08a19c474 100644 +--- a/src/cryptenroll/cryptenroll-tpm2.h ++++ b/src/cryptenroll/cryptenroll-tpm2.h +@@ -8,9 +8,9 @@ + #include "tpm2-util.h" + + #if HAVE_TPM2 +-int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin); ++int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin); + #else +-static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin) { ++static inline int enroll_tpm2(struct crypt_device *cd, const void *volume_key, size_t volume_key_size, const char *device, uint32_t seal_key_handle, const char *device_key, Tpm2PCRValue *hash_pcrs, size_t n_hash_pcrs, const char *pubkey_path, uint32_t pubkey_pcr_mask, const char *signature_path, bool use_pin) { + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), + "TPM2 key enrollment not supported."); + } +diff --git a/src/cryptenroll/cryptenroll.c b/src/cryptenroll/cryptenroll.c +index 5ace7a9787..d2fffdad24 100644 +--- a/src/cryptenroll/cryptenroll.c ++++ b/src/cryptenroll/cryptenroll.c +@@ -34,6 +34,7 @@ static char *arg_pkcs11_token_uri = NULL; + static char *arg_fido2_device = NULL; + static char *arg_tpm2_device = NULL; + static uint32_t arg_tpm2_seal_key_handle = 0; ++static char *arg_tpm2_device_key = NULL; + static Tpm2PCRValue *arg_tpm2_hash_pcr_values = NULL; + static size_t arg_tpm2_n_hash_pcr_values = 0; + static bool arg_tpm2_hash_pcr_values_use_default = true; +@@ -60,6 +61,7 @@ STATIC_DESTRUCTOR_REGISTER(arg_unlock_keyfile, freep); + STATIC_DESTRUCTOR_REGISTER(arg_pkcs11_token_uri, freep); + STATIC_DESTRUCTOR_REGISTER(arg_fido2_device, freep); + STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device, freep); ++STATIC_DESTRUCTOR_REGISTER(arg_tpm2_device_key, freep); + STATIC_DESTRUCTOR_REGISTER(arg_tpm2_hash_pcr_values, freep); + STATIC_DESTRUCTOR_REGISTER(arg_tpm2_public_key, freep); + STATIC_DESTRUCTOR_REGISTER(arg_tpm2_signature, freep); +@@ -124,6 +126,8 @@ static int help(void) { + " Enroll a TPM2 device\n" + " --tpm2-seal-key-handle=HANDLE\n" + " Specify handle of key to use for sealing\n" ++ " --tpm2-device-key=PATH\n" ++ " Enroll a TPM2 device using its public key\n" + " --tpm2-pcrs=PCR1+PCR2+PCR3+…\n" + " Specify TPM2 PCRs to seal against\n" + " --tpm2-public-key=PATH\n" +@@ -157,6 +161,7 @@ static int parse_argv(int argc, char *argv[]) { + ARG_FIDO2_DEVICE, + ARG_TPM2_DEVICE, + ARG_TPM2_SEAL_KEY_HANDLE, ++ ARG_TPM2_DEVICE_KEY, + ARG_TPM2_PCRS, + ARG_TPM2_PUBLIC_KEY, + ARG_TPM2_PUBLIC_KEY_PCRS, +@@ -183,6 +188,7 @@ static int parse_argv(int argc, char *argv[]) { + { "fido2-with-user-verification", required_argument, NULL, ARG_FIDO2_WITH_UV }, + { "tpm2-device", required_argument, NULL, ARG_TPM2_DEVICE }, + { "tpm2-seal-key-handle", required_argument, NULL, ARG_TPM2_SEAL_KEY_HANDLE }, ++ { "tpm2-device-key", required_argument, NULL, ARG_TPM2_DEVICE_KEY }, + { "tpm2-pcrs", required_argument, NULL, ARG_TPM2_PCRS }, + { "tpm2-public-key", required_argument, NULL, ARG_TPM2_PUBLIC_KEY }, + { "tpm2-public-key-pcrs", required_argument, NULL, ARG_TPM2_PUBLIC_KEY_PCRS }, +@@ -349,6 +355,19 @@ static int parse_argv(int argc, char *argv[]) { + + break; + ++ case ARG_TPM2_DEVICE_KEY: ++ if (arg_enroll_type >= 0 || arg_tpm2_device_key) ++ return log_error_errno(SYNTHETIC_ERRNO(EINVAL), ++ "Multiple operations specified at once, refusing."); ++ ++ ++ r = parse_path_argument(optarg, /* suppress_root= */ false, &arg_tpm2_device_key); ++ if (r < 0) ++ return r; ++ ++ arg_enroll_type = ENROLL_TPM2; ++ break; ++ + case ARG_TPM2_PCRS: + arg_tpm2_hash_pcr_values_use_default = false; + r = tpm2_parse_pcr_argument_append(optarg, &arg_tpm2_hash_pcr_values, &arg_tpm2_n_hash_pcr_values); +@@ -679,7 +698,7 @@ static int run(int argc, char *argv[]) { + break; + + case ENROLL_TPM2: +- slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_seal_key_handle, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin); ++ slot = enroll_tpm2(cd, vk, vks, arg_tpm2_device, arg_tpm2_seal_key_handle, arg_tpm2_device_key, arg_tpm2_hash_pcr_values, arg_tpm2_n_hash_pcr_values, arg_tpm2_public_key, arg_tpm2_public_key_pcr_mask, arg_tpm2_signature, arg_tpm2_pin); + break; + + case _ENROLL_TYPE_INVALID: +diff --git a/src/shared/tpm2-util.c b/src/shared/tpm2-util.c +index e334564159..4e382f691e 100644 +--- a/src/shared/tpm2-util.c ++++ b/src/shared/tpm2-util.c +@@ -81,7 +81,7 @@ static TSS2_RC (*sym_Tss2_MU_TPM2B_NAME_Marshal)(TPM2B_NAME const *src, uint8_t + static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Marshal)(TPM2B_PRIVATE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPM2B_PRIVATE_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PRIVATE *dest) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Marshal)(TPM2B_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; +-static TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL; ++TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPM2B_SENSITIVE_Marshal)(TPM2B_SENSITIVE const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPML_PCR_SELECTION_Marshal)(TPML_PCR_SELECTION const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; + static TSS2_RC (*sym_Tss2_MU_TPMS_NV_PUBLIC_Marshal)(TPMS_NV_PUBLIC const *src, uint8_t buffer[], size_t buffer_size, size_t *offset) = NULL; +diff --git a/src/shared/tpm2-util.h b/src/shared/tpm2-util.h +index 34d4610383..380e96ec83 100644 +--- a/src/shared/tpm2-util.h ++++ b/src/shared/tpm2-util.h +@@ -263,6 +263,8 @@ int tpm2_tpm2b_public_to_fingerprint(const TPM2B_PUBLIC *public, void **ret_fing + 0; \ + }) + ++extern TSS2_RC (*sym_Tss2_MU_TPM2B_PUBLIC_Unmarshal)(uint8_t const buffer[], size_t buffer_size, size_t *offset, TPM2B_PUBLIC *dest); ++ + #else /* HAVE_TPM2 */ + typedef struct {} Tpm2Context; + typedef struct {} Tpm2Handle; diff --git a/SOURCES/0642-test-update-TEST-70-with-systemd-cryptenroll-calcula.patch b/SOURCES/0642-test-update-TEST-70-with-systemd-cryptenroll-calcula.patch new file mode 100644 index 0000000..a728a5a --- /dev/null +++ b/SOURCES/0642-test-update-TEST-70-with-systemd-cryptenroll-calcula.patch @@ -0,0 +1,57 @@ +From 53569e7b564d6de1cb1fbfbcbc5126b2f4f9f23c Mon Sep 17 00:00:00 2001 +From: Dan Streetman +Date: Mon, 24 Jul 2023 20:04:28 -0400 +Subject: [PATCH] test: update TEST-70 with systemd-cryptenroll calculated TPM2 + enrollment + +Update test to check systemd-cryptenroll --tpm2-device-key= enrollment. + +(cherry picked from commit 803e95932f8c749c7ec6fa00440c1a268af1d1f5) + +Related: RHEL-16182 +--- + test/TEST-70-TPM2/test.sh | 2 ++ + test/units/testsuite-70.sh | 10 +++++++++- + 2 files changed, 11 insertions(+), 1 deletion(-) + +diff --git a/test/TEST-70-TPM2/test.sh b/test/TEST-70-TPM2/test.sh +index 72784ec418..a228901b3d 100755 +--- a/test/TEST-70-TPM2/test.sh ++++ b/test/TEST-70-TPM2/test.sh +@@ -16,6 +16,7 @@ command -v openssl >/dev/null 2>&1 || exit 0 + command -v tpm2_createprimary >/dev/null 2>&1 || exit 0 + command -v tpm2_evictcontrol >/dev/null 2>&1 || exit 0 + command -v tpm2_flushcontext >/dev/null 2>&1 || exit 0 ++command -v tpm2_readpublic >/dev/null 2>&1 || exit 0 + + + test_append_files() { +@@ -30,6 +31,7 @@ test_append_files() { + inst_binary tpm2_createprimary + inst_binary tpm2_evictcontrol + inst_binary tpm2_flushcontext ++ inst_binary tpm2_readpublic + } + + TEST_70_TPM_DEVICE="tpm-tis" +diff --git a/test/units/testsuite-70.sh b/test/units/testsuite-70.sh +index 9e42dd5b28..222fa33d69 100755 +--- a/test/units/testsuite-70.sh ++++ b/test/units/testsuite-70.sh +@@ -101,7 +101,15 @@ if tpm_has_pcr sha256 12; then + /usr/lib/systemd/systemd-cryptsetup attach test-volume "$img" - tpm2-device=auto,headless=1 + /usr/lib/systemd/systemd-cryptsetup detach test-volume + +- rm -f /tmp/pcr.dat ++ # enroll TPM using device key instead of direct access, then verify unlock using TPM ++ tpm2_pcrread -Q -o /tmp/pcr.dat sha256:12 ++ CURRENT_PCR_VALUE=$(cat /sys/class/tpm/tpm0/pcr-sha256/12) ++ tpm2_readpublic -c 0x81000001 -o /tmp/srk.pub ++ PASSWORD=passphrase systemd-cryptenroll --tpm2-device-key=/tmp/srk.pub --tpm2-pcrs="12:sha256=$CURRENT_PCR_VALUE" "$IMAGE" ++ /usr/lib/systemd/systemd-cryptsetup attach test-volume "$IMAGE" - tpm2-device=auto,headless=1 ++ /usr/lib/systemd/systemd-cryptsetup detach test-volume ++ ++ rm -f /tmp/pcr.dat /tmp/srk.pub + fi + + # Use default (0) seal key handle diff --git a/SOURCES/0643-openssl-util-avoid-freeing-invalid-pointer.patch b/SOURCES/0643-openssl-util-avoid-freeing-invalid-pointer.patch new file mode 100644 index 0000000..5a94067 --- /dev/null +++ b/SOURCES/0643-openssl-util-avoid-freeing-invalid-pointer.patch @@ -0,0 +1,25 @@ +From fce4697dd99e7650d39559fabb35032a6adbd78e Mon Sep 17 00:00:00 2001 +From: David Tardon +Date: Tue, 12 Dec 2023 15:47:33 +0100 +Subject: [PATCH] openssl-util: avoid freeing invalid pointer + +(cherry picked from commit 38e1035befef69870735e7237eb0d3c0e8a007dd) + +Related: RHEL-16182 +--- + src/shared/openssl-util.c | 2 +- + 1 file changed, 1 insertion(+), 1 deletion(-) + +diff --git a/src/shared/openssl-util.c b/src/shared/openssl-util.c +index d863729708..8788c12a22 100644 +--- a/src/shared/openssl-util.c ++++ b/src/shared/openssl-util.c +@@ -1066,7 +1066,7 @@ int string_hashsum( + + _cleanup_free_ void *hash = NULL; + size_t hash_size; +- _cleanup_free_ char *enc; ++ _cleanup_free_ char *enc = NULL; + int r; + + assert(s || len == 0); diff --git a/SOURCES/0644-creds-util-check-for-CAP_DAC_READ_SEARCH.patch b/SOURCES/0644-creds-util-check-for-CAP_DAC_READ_SEARCH.patch new file mode 100644 index 0000000..ced8c4f --- /dev/null +++ b/SOURCES/0644-creds-util-check-for-CAP_DAC_READ_SEARCH.patch @@ -0,0 +1,57 @@ +From b052e96d1b8fb75ec2ae1e6da889de67fbbade7a Mon Sep 17 00:00:00 2001 +From: Alberto Planas +Date: Fri, 13 Jan 2023 15:31:39 +0100 +Subject: [PATCH] creds-util: check for CAP_DAC_READ_SEARCH + +In make_credential_host_secret, the credential.secret file is generated +first as a temporary anonymous file that is later instantiated with +linkat(2). This system call requires CAP_DAC_READ_SEARCH capability +when the flag AT_EMPTY_PATH is used. + +This patch check if the capability is effective, and if not uses the +alternative codepath for creating named temporary files. + +Non-root users can now create per-user credentials with: + + export SYSTEMD_CREDENTIAL_SECRET=$HOME/.config/systemd/credential.secret + systemd-creds setup + +Signed-off-by: Alberto Planas +(cherry picked from commit 1615578f2792fdeecaf65606861bd3db9eb949c3) + +Related: RHEL-16182 +--- + src/shared/creds-util.c | 12 +++++++++--- + 1 file changed, 9 insertions(+), 3 deletions(-) + +diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c +index 27548a0eec..20b3b83369 100644 +--- a/src/shared/creds-util.c ++++ b/src/shared/creds-util.c +@@ -9,6 +9,7 @@ + #include "sd-id128.h" + + #include "blockdev-util.h" ++#include "capability-util.h" + #include "chattr-util.h" + #include "creds-util.h" + #include "def.h" +@@ -172,10 +173,15 @@ static int make_credential_host_secret( + assert(dfd >= 0); + assert(fn); + +- fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400); ++ /* For non-root users creating a temporary file using the openat(2) over "." will fail later, in the ++ * linkat(2) step at the end. The reason is that linkat(2) requires the CAP_DAC_READ_SEARCH ++ * capability when it uses the AT_EMPTY_PATH flag. */ ++ if (have_effective_cap(CAP_DAC_READ_SEARCH) > 0) { ++ fd = openat(dfd, ".", O_CLOEXEC|O_WRONLY|O_TMPFILE, 0400); ++ if (fd < 0) ++ log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m"); ++ } + if (fd < 0) { +- log_debug_errno(errno, "Failed to create temporary credential file with O_TMPFILE, proceeding without: %m"); +- + if (asprintf(&t, "credential.secret.%016" PRIx64, random_u64()) < 0) + return -ENOMEM; + diff --git a/SOURCES/0645-creds-util-do-not-try-TPM2-if-there-is-not-support.patch b/SOURCES/0645-creds-util-do-not-try-TPM2-if-there-is-not-support.patch new file mode 100644 index 0000000..03834cf --- /dev/null +++ b/SOURCES/0645-creds-util-do-not-try-TPM2-if-there-is-not-support.patch @@ -0,0 +1,49 @@ +From 5d5a32888ec46d1cce61d46c92f6ad1df986213e Mon Sep 17 00:00:00 2001 +From: Alberto Planas +Date: Mon, 16 Jan 2023 11:16:53 +0100 +Subject: [PATCH] creds-util: do not try TPM2 if there is not support + +During the credentials encryption, if systemd it is compiled with TPM2 +support, it will try to use it depending on the key flags passed. + +The current code only checks if the system has a functional TPM2 if the +case of the INITRD flag. + +This patch do a similar check in the case that it is outside initrd (but +still automatic). + +Signed-off-by: Alberto Planas +(cherry picked from commit e653a194e490fae7d166f40762c334006d592051) + +Related: RHEL-16182 +--- + src/shared/creds-util.c | 15 ++++++--------- + 1 file changed, 6 insertions(+), 9 deletions(-) + +diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c +index 20b3b83369..075fd2327a 100644 +--- a/src/shared/creds-util.c ++++ b/src/shared/creds-util.c +@@ -609,16 +609,13 @@ int encrypt_credential_and_warn( + #if HAVE_TPM2 + bool try_tpm2; + if (sd_id128_equal(with_key, _CRED_AUTO)) { +- /* If automatic mode is selected and we are running in a container, let's not try TPM2. OTOH +- * if user picks TPM2 explicitly, let's always honour the request and try. */ ++ /* If automatic mode is selected lets see if a TPM2 it is present. If we are running in a ++ * container tpm2_support will detect this, and will return a different flag combination of ++ * TPM2_SUPPORT_FULL, effectively skipping the use of TPM2 when inside one. */ + +- r = detect_container(); +- if (r < 0) +- log_debug_errno(r, "Failed to determine whether we are running in a container, ignoring: %m"); +- else if (r > 0) +- log_debug("Running in container, not attempting to use TPM2."); +- +- try_tpm2 = r <= 0; ++ try_tpm2 = tpm2_support() == TPM2_SUPPORT_FULL; ++ if (!try_tpm2) ++ log_debug("System lacks TPM2 support or running in a container, not attempting to use TPM2."); + } else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) { + /* If automatic mode for initrds is selected, we'll use the TPM2 key if the firmware does it, + * otherwise we'll use a fixed key */ diff --git a/SOURCES/0646-creds-util-merge-the-TPM2-detection-for-initrd.patch b/SOURCES/0646-creds-util-merge-the-TPM2-detection-for-initrd.patch new file mode 100644 index 0000000..77f9570 --- /dev/null +++ b/SOURCES/0646-creds-util-merge-the-TPM2-detection-for-initrd.patch @@ -0,0 +1,52 @@ +From e62ee4deaa4c3e333c9895c43b939276335e116b Mon Sep 17 00:00:00 2001 +From: Alberto Planas +Date: Mon, 16 Jan 2023 13:35:49 +0100 +Subject: [PATCH] creds-util: merge the TPM2 detection for initrd + +This patch merge the TPM2 detection paths when we are inside and outside +an initrd. + +Signed-off-by: Alberto Planas +(cherry picked from commit e37dfcec528b43e203d198f978f9eaa87787c762) + +Related: RHEL-16182 +--- + src/shared/creds-util.c | 11 ++--------- + 1 file changed, 2 insertions(+), 9 deletions(-) + +diff --git a/src/shared/creds-util.c b/src/shared/creds-util.c +index 075fd2327a..027ad96640 100644 +--- a/src/shared/creds-util.c ++++ b/src/shared/creds-util.c +@@ -608,7 +608,7 @@ int encrypt_credential_and_warn( + + #if HAVE_TPM2 + bool try_tpm2; +- if (sd_id128_equal(with_key, _CRED_AUTO)) { ++ if (sd_id128_in_set(with_key, _CRED_AUTO, _CRED_AUTO_INITRD)) { + /* If automatic mode is selected lets see if a TPM2 it is present. If we are running in a + * container tpm2_support will detect this, and will return a different flag combination of + * TPM2_SUPPORT_FULL, effectively skipping the use of TPM2 when inside one. */ +@@ -616,13 +616,6 @@ int encrypt_credential_and_warn( + try_tpm2 = tpm2_support() == TPM2_SUPPORT_FULL; + if (!try_tpm2) + log_debug("System lacks TPM2 support or running in a container, not attempting to use TPM2."); +- } else if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) { +- /* If automatic mode for initrds is selected, we'll use the TPM2 key if the firmware does it, +- * otherwise we'll use a fixed key */ +- +- try_tpm2 = efi_has_tpm2(); +- if (!try_tpm2) +- log_debug("Firmware lacks TPM2 support, not attempting to use TPM2."); + } else + try_tpm2 = sd_id128_in_set(with_key, + CRED_AES256_GCM_BY_TPM2_HMAC, +@@ -697,7 +690,7 @@ int encrypt_credential_and_warn( + /* ret_srk_buf_size= */ NULL); + if (r < 0) { + if (sd_id128_equal(with_key, _CRED_AUTO_INITRD)) +- log_warning("Firmware reported a TPM2 being present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled."); ++ log_warning("TPM2 present and used, but we didn't manage to talk to it. Credential will be refused if SecureBoot is enabled."); + else if (!sd_id128_equal(with_key, _CRED_AUTO)) + return log_error_errno(r, "Failed to seal to TPM2: %m"); + diff --git a/SOURCES/0647-cryptenroll-fix-a-memory-leak.patch b/SOURCES/0647-cryptenroll-fix-a-memory-leak.patch new file mode 100644 index 0000000..a7514ee --- /dev/null +++ b/SOURCES/0647-cryptenroll-fix-a-memory-leak.patch @@ -0,0 +1,57 @@ +From a4aa2ab6aada7e8c0f4bb99a94830baa5a774d58 Mon Sep 17 00:00:00 2001 +From: Frantisek Sumsal +Date: Mon, 27 Mar 2023 16:51:00 +0200 +Subject: [PATCH] cryptenroll: fix a memory leak + +$ dd if=/dev/zero of=luks.img bs=1M count=64 +$ echo 1231dfsd234d | cryptsetup luksFormat luks.img +$ build-san/systemd-cryptenroll luks.img +SLOT TYPE + 0 password + +================================================================= +==640364==ERROR: LeakSanitizer: detected memory leaks + +Direct leak of 64 byte(s) in 1 object(s) allocated from: + #0 0x7f43ffeb95b5 in __interceptor_realloc.part.0 (/lib64/libasan.so.8+0xb95b5) + #1 0x7f43ff0a4f2f in greedy_realloc ../src/basic/alloc-util.c:70 + #2 0x404d9f in list_enrolled ../src/cryptenroll/cryptenroll-list.c:30 + #3 0x40f149 in run ../src/cryptenroll/cryptenroll.c:673 + #4 0x40f149 in main ../src/cryptenroll/cryptenroll.c:692 + #5 0x7f43fd64a50f in __libc_start_call_main (/lib64/libc.so.6+0x2750f) + +SUMMARY: AddressSanitizer: 64 byte(s) leaked in 1 allocation(s). +Aborted (core dumped) + +Reported in https://github.com/systemd/systemd/pull/27007. + +(cherry picked from commit 30dbadf65eaa64daa494118eb0cec7750f376c25) + +Related: RHEL-16182 +--- + src/cryptenroll/cryptenroll-list.c | 11 ++++++----- + 1 file changed, 6 insertions(+), 5 deletions(-) + +diff --git a/src/cryptenroll/cryptenroll-list.c b/src/cryptenroll/cryptenroll-list.c +index 62688da3d3..d21df7123b 100644 +--- a/src/cryptenroll/cryptenroll-list.c ++++ b/src/cryptenroll/cryptenroll-list.c +@@ -5,12 +5,13 @@ + #include "format-table.h" + #include "parse-util.h" + +-int list_enrolled(struct crypt_device *cd) { ++struct keyslot_metadata { ++ int slot; ++ const char *type; ++}; + +- struct keyslot_metadata { +- int slot; +- const char *type; +- } *keyslot_metadata = NULL; ++int list_enrolled(struct crypt_device *cd) { ++ _cleanup_free_ struct keyslot_metadata *keyslot_metadata = NULL; + _cleanup_(table_unrefp) Table *t = NULL; + size_t n_keyslot_metadata = 0; + int slot_max, r; diff --git a/SOURCES/0648-sd-journal-introduce-sd_journal_step_one.patch b/SOURCES/0648-sd-journal-introduce-sd_journal_step_one.patch new file mode 100644 index 0000000..7ecdb45 --- /dev/null +++ b/SOURCES/0648-sd-journal-introduce-sd_journal_step_one.patch @@ -0,0 +1,124 @@ +From 90d73383f5ade7b9a320b7636187cd846d60c9a4 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Sun, 28 May 2023 14:20:27 +0900 +Subject: [PATCH] sd-journal: introduce sd_journal_step_one() + +After the commit 7a4ee861615101ddd2f95056cf30e69e41da86ce, +sd_journal_next() following sd_journal_seek_tail() takes no-op, +and we need to call sd_journal_previous(). This may be useful in +some cases, e.g. to fix the issue explained in the previous commit. + +(cherry picked from commit b78f9481bc03455eafd9239c33fc2f124779760c) + +Related: RHEL-11591 +--- + man/rules/meson.build | 3 ++- + man/sd_journal_next.xml | 17 +++++++++++++++++ + src/libsystemd/libsystemd.sym | 6 ++++++ + src/libsystemd/sd-journal/sd-journal.c | 10 ++++++++++ + src/systemd/sd-journal.h | 1 + + 5 files changed, 36 insertions(+), 1 deletion(-) + +diff --git a/man/rules/meson.build b/man/rules/meson.build +index 65a16b1e2a..9c0d773e51 100644 +--- a/man/rules/meson.build ++++ b/man/rules/meson.build +@@ -726,7 +726,8 @@ manpages = [ + 'SD_JOURNAL_FOREACH_BACKWARDS', + 'sd_journal_next_skip', + 'sd_journal_previous', +- 'sd_journal_previous_skip'], ++ 'sd_journal_previous_skip', ++ 'sd_journal_step_one'], + ''], + ['sd_journal_open', + '3', +diff --git a/man/sd_journal_next.xml b/man/sd_journal_next.xml +index 628abb296c..cc267fa1bd 100644 +--- a/man/sd_journal_next.xml ++++ b/man/sd_journal_next.xml +@@ -18,6 +18,7 @@ + + sd_journal_next + sd_journal_previous ++ sd_journal_step_one + sd_journal_next_skip + sd_journal_previous_skip + SD_JOURNAL_FOREACH +@@ -39,6 +40,12 @@ + sd_journal *j + + ++ ++ int sd_journal_step_one ++ sd_journal *j ++ int advanced ++ ++ + + int sd_journal_next_skip + sd_journal *j +@@ -77,6 +84,16 @@ + Similarly, sd_journal_previous() sets + the read pointer back one entry. + ++ sd_journal_step_one() also moves the read pointer. If the current location ++ is the head of the journal, e.g. when this is called following ++ sd_journal_seek_head(), then this is equivalent to ++ sd_journal_next(), and the argument advanced will be ignored. ++ Similary, if the current location is the tail of the journal, e.g. when this is called following ++ sd_journal_seek_tail(), then this is equivalent to ++ sd_journal_previous(), and advanced will be ignored. Otherwise, ++ this is equivalent to sd_journal_next() when advanced is ++ non-zero, and sd_journal_previous() when advanced is zero. ++ + sd_journal_next_skip() and + sd_journal_previous_skip() advance/set back the read pointer by multiple + entries at once, as specified in the skip parameter. The skip +diff --git a/src/libsystemd/libsystemd.sym b/src/libsystemd/libsystemd.sym +index 3b72320f0c..26054ed2d4 100644 +--- a/src/libsystemd/libsystemd.sym ++++ b/src/libsystemd/libsystemd.sym +@@ -796,3 +796,9 @@ global: + + sd_hwdb_new_from_path; + } LIBSYSTEMD_251; ++ ++ ++LIBSYSTEMD_254 { ++global: ++ sd_journal_step_one; ++} LIBSYSTEMD_252; +diff --git a/src/libsystemd/sd-journal/sd-journal.c b/src/libsystemd/sd-journal/sd-journal.c +index 1e4d128f05..9ab31fbbc8 100644 +--- a/src/libsystemd/sd-journal/sd-journal.c ++++ b/src/libsystemd/sd-journal/sd-journal.c +@@ -874,6 +874,16 @@ _public_ int sd_journal_previous(sd_journal *j) { + return real_journal_next(j, DIRECTION_UP); + } + ++_public_ int sd_journal_step_one(sd_journal *j, int advanced) { ++ assert_return(j, -EINVAL); ++ ++ if (j->current_location.type == LOCATION_HEAD) ++ return sd_journal_next(j); ++ if (j->current_location.type == LOCATION_TAIL) ++ return sd_journal_previous(j); ++ return real_journal_next(j, advanced ? DIRECTION_DOWN : DIRECTION_UP); ++} ++ + static int real_journal_next_skip(sd_journal *j, direction_t direction, uint64_t skip) { + int c = 0, r; + +diff --git a/src/systemd/sd-journal.h b/src/systemd/sd-journal.h +index 24638f1e0a..e34abc3a65 100644 +--- a/src/systemd/sd-journal.h ++++ b/src/systemd/sd-journal.h +@@ -93,6 +93,7 @@ void sd_journal_close(sd_journal *j); + + int sd_journal_previous(sd_journal *j); + int sd_journal_next(sd_journal *j); ++int sd_journal_step_one(sd_journal *j, int advanced); + + int sd_journal_previous_skip(sd_journal *j, uint64_t skip); + int sd_journal_next_skip(sd_journal *j, uint64_t skip); diff --git a/SOURCES/0649-test-modernize-test-journal-flush.patch b/SOURCES/0649-test-modernize-test-journal-flush.patch new file mode 100644 index 0000000..f758db1 --- /dev/null +++ b/SOURCES/0649-test-modernize-test-journal-flush.patch @@ -0,0 +1,78 @@ +From 4c4170bc379ff7c89326ece892db93f12340dd81 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Thu, 5 Oct 2023 17:15:54 +0900 +Subject: [PATCH] test: modernize test-journal-flush + +(cherry picked from commit ff95b60d1abddf01a57c7e39d30142326e2373e8) + +Related: RHEL-11591 +--- + src/journal/test-journal-flush.c | 29 +++++++++++++++-------------- + 1 file changed, 15 insertions(+), 14 deletions(-) + +diff --git a/src/journal/test-journal-flush.c b/src/journal/test-journal-flush.c +index c734aa02ca..015604780f 100644 +--- a/src/journal/test-journal-flush.c ++++ b/src/journal/test-journal-flush.c +@@ -12,14 +12,15 @@ + #include "managed-journal-file.h" + #include "path-util.h" + #include "string-util.h" ++#include "tests.h" + +-static void test_journal_flush(int argc, char *argv[]) { ++static void test_journal_flush_one(int argc, char *argv[]) { + _cleanup_(mmap_cache_unrefp) MMapCache *m = NULL; + _cleanup_free_ char *fn = NULL; + char dn[] = "/var/tmp/test-journal-flush.XXXXXX"; +- ManagedJournalFile *new_journal = NULL; +- sd_journal *j = NULL; +- unsigned n = 0; ++ _cleanup_(managed_journal_file_closep) ManagedJournalFile *new_journal = NULL; ++ _cleanup_(sd_journal_closep) sd_journal *j = NULL; ++ unsigned n, limit; + int r; + + m = mmap_cache_new(); +@@ -40,6 +41,8 @@ static void test_journal_flush(int argc, char *argv[]) { + + sd_journal_set_data_threshold(j, 0); + ++ n = 0; ++ limit = slow_tests_enabled() ? 10000 : 1000; + SD_JOURNAL_FOREACH(j) { + Object *o; + JournalFile *f; +@@ -60,24 +63,22 @@ static void test_journal_flush(int argc, char *argv[]) { + -EPROTONOSUPPORT, /* unsupported compression */ + -EIO)); /* file rotated */ + +- if (++n >= 10000) ++ if (++n >= limit) + break; + } + +- sd_journal_close(j); +- +- (void) managed_journal_file_close(new_journal); +- + unlink(fn); + assert_se(rmdir(dn) == 0); + } + +-int main(int argc, char *argv[]) { ++TEST(journal_flush) { + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "0", 1) >= 0); +- test_journal_flush(argc, argv); ++ test_journal_flush_one(saved_argc, saved_argv); ++} + ++TEST(journal_flush_compact) { + assert_se(setenv("SYSTEMD_JOURNAL_COMPACT", "1", 1) >= 0); +- test_journal_flush(argc, argv); +- +- return 0; ++ test_journal_flush_one(saved_argc, saved_argv); + } ++ ++DEFINE_TEST_MAIN(LOG_INFO); diff --git a/SOURCES/0650-journal-file-util-do-not-fail-when-journal_file_set_.patch b/SOURCES/0650-journal-file-util-do-not-fail-when-journal_file_set_.patch new file mode 100644 index 0000000..f10266e --- /dev/null +++ b/SOURCES/0650-journal-file-util-do-not-fail-when-journal_file_set_.patch @@ -0,0 +1,39 @@ +From 79d1c46296052bef600b52851356618d583c9c6a Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Thu, 5 Oct 2023 18:20:40 +0900 +Subject: [PATCH] journal-file-util: do not fail when + journal_file_set_offline() called more than once + +Previously, if journal_file_set_offline() is called twice with 'wait = false', +the second call triggered segfaults, as the offline_state is OFFLINE_DONE, +and journal_file_set_offline_thread_join() tries to call pthread_join() +with NULL. + +(cherry picked from commit 46e98dfcc7563dd16a2db3b05bb3e803c27f40ea) + +Related: RHEL-11591 +--- + src/journal/managed-journal-file.c | 9 +++++++-- + 1 file changed, 7 insertions(+), 2 deletions(-) + +diff --git a/src/journal/managed-journal-file.c b/src/journal/managed-journal-file.c +index 810167772a..382a7ff45b 100644 +--- a/src/journal/managed-journal-file.c ++++ b/src/journal/managed-journal-file.c +@@ -345,9 +345,14 @@ int managed_journal_file_set_offline(ManagedJournalFile *f, bool wait) { + /* Initiate a new offline. */ + f->file->offline_state = OFFLINE_SYNCING; + +- if (wait) /* Without using a thread if waiting. */ ++ if (wait) { ++ /* Without using a thread if waiting. */ + managed_journal_file_set_offline_internal(f); +- else { ++ ++ assert(f->file->offline_state == OFFLINE_DONE); ++ f->file->offline_state = OFFLINE_JOINED; ++ ++ } else { + sigset_t ss, saved_ss; + int k; + diff --git a/SOURCES/0651-journal-file-util-Prefer-punching-holes-instead-of-t.patch b/SOURCES/0651-journal-file-util-Prefer-punching-holes-instead-of-t.patch new file mode 100644 index 0000000..250d60e --- /dev/null +++ b/SOURCES/0651-journal-file-util-Prefer-punching-holes-instead-of-t.patch @@ -0,0 +1,93 @@ +From 8ba8457d29c5976c57bfeb5034a6dc69e0c78577 Mon Sep 17 00:00:00 2001 +From: Daan De Meyer +Date: Wed, 4 Oct 2023 09:27:18 +0200 +Subject: [PATCH] journal-file-util: Prefer punching holes instead of + truncating + +It seems truncating might cause SIGBUS (#24320). Let's play it safe +and always prefer punching holes over truncating. + +(cherry picked from commit f20c07d5ad3f657fdb9400288d7becb1b686d48a) + +Resolves: RHEL-11591 +--- + src/journal/managed-journal-file.c | 44 +++++++++++------------------- + 1 file changed, 16 insertions(+), 28 deletions(-) + +diff --git a/src/journal/managed-journal-file.c b/src/journal/managed-journal-file.c +index 382a7ff45b..0924b86f7c 100644 +--- a/src/journal/managed-journal-file.c ++++ b/src/journal/managed-journal-file.c +@@ -19,22 +19,29 @@ + #define PAYLOAD_BUFFER_SIZE (16U * 1024U) + #define MINIMUM_HOLE_SIZE (1U * 1024U * 1024U / 2U) + +-static int managed_journal_file_truncate(JournalFile *f) { +- uint64_t p; ++static int managed_journal_file_end_punch_hole(JournalFile *f) { ++ uint64_t p, sz; + int r; + +- /* truncate excess from the end of archives */ + r = journal_file_tail_end_by_pread(f, &p); + if (r < 0) + return log_debug_errno(r, "Failed to determine end of tail object: %m"); + +- /* arena_size can't exceed the file size, ensure it's updated before truncating */ +- f->header->arena_size = htole64(p - le64toh(f->header->header_size)); ++ assert(p <= (uint64_t) f->last_stat.st_size); ++ ++ sz = ((uint64_t) f->last_stat.st_size) - p; ++ if (sz < MINIMUM_HOLE_SIZE) ++ return 0; ++ ++ if (fallocate(f->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, p, sz) < 0) { ++ if (ERRNO_IS_NOT_SUPPORTED(errno)) ++ return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), /* Make recognizable */ ++ "Hole punching not supported by backing file system, skipping."); + +- if (ftruncate(f->fd, p) < 0) +- return log_debug_errno(errno, "Failed to truncate %s: %m", f->path); ++ return log_debug_errno(errno, "Failed to punch hole at end of journal file %s: %m", f->path); ++ } + +- return journal_file_fstat(f); ++ return 0; + } + + static int managed_journal_file_entry_array_punch_hole(JournalFile *f, uint64_t p, uint64_t n_entries) { +@@ -73,25 +80,6 @@ static int managed_journal_file_entry_array_punch_hole(JournalFile *f, uint64_t + if (sz < MINIMUM_HOLE_SIZE) + return 0; + +- if (p == le64toh(f->header->tail_object_offset) && !JOURNAL_HEADER_SEALED(f->header)) { +- ssize_t n; +- +- o.object.size = htole64(offset - p); +- +- n = pwrite(f->fd, &o, sizeof(EntryArrayObject), p); +- if (n < 0) +- return log_debug_errno(errno, "Failed to modify entry array object size: %m"); +- if ((size_t) n != sizeof(EntryArrayObject)) +- return log_debug_errno(SYNTHETIC_ERRNO(EIO), "Short pwrite() while modifying entry array object size."); +- +- f->header->arena_size = htole64(ALIGN64(offset) - le64toh(f->header->header_size)); +- +- if (ftruncate(f->fd, ALIGN64(offset)) < 0) +- return log_debug_errno(errno, "Failed to truncate %s: %m", f->path); +- +- return 0; +- } +- + if (fallocate(f->fd, FALLOC_FL_PUNCH_HOLE | FALLOC_FL_KEEP_SIZE, offset, sz) < 0) { + if (ERRNO_IS_NOT_SUPPORTED(errno)) + return log_debug_errno(SYNTHETIC_ERRNO(EOPNOTSUPP), /* Make recognizable */ +@@ -192,7 +180,7 @@ static void managed_journal_file_set_offline_internal(ManagedJournalFile *f) { + + case OFFLINE_SYNCING: + if (f->file->archive) { +- (void) managed_journal_file_truncate(f->file); ++ (void) managed_journal_file_end_punch_hole(f->file); + (void) managed_journal_file_punch_holes(f->file); + } + diff --git a/SOURCES/0652-test-add-reproducer-for-SIGBUS-issue-caused-by-journ.patch b/SOURCES/0652-test-add-reproducer-for-SIGBUS-issue-caused-by-journ.patch new file mode 100644 index 0000000..b3b70d5 --- /dev/null +++ b/SOURCES/0652-test-add-reproducer-for-SIGBUS-issue-caused-by-journ.patch @@ -0,0 +1,77 @@ +From 9676d3b454a83c1b38a1c26524214c3251c8be94 Mon Sep 17 00:00:00 2001 +From: Yu Watanabe +Date: Thu, 5 Oct 2023 18:02:24 +0900 +Subject: [PATCH] test: add reproducer for SIGBUS issue caused by journal + truncation + +The added code fails without the previous commit. + +For issue #24320. + +(cherry picked from commit 3b0ae13bbf231ea01ff954de0c0c5375cbd85ff3) + +Related: RHEL-11591 +--- + src/journal/test-journal-flush.c | 38 +++++++++++++++++++++++++++++++- + 1 file changed, 37 insertions(+), 1 deletion(-) + +diff --git a/src/journal/test-journal-flush.c b/src/journal/test-journal-flush.c +index 015604780f..2a5f7fad5e 100644 +--- a/src/journal/test-journal-flush.c ++++ b/src/journal/test-journal-flush.c +@@ -8,9 +8,11 @@ + #include "alloc-util.h" + #include "chattr-util.h" + #include "journal-internal.h" ++#include "logs-show.h" + #include "macro.h" + #include "managed-journal-file.h" + #include "path-util.h" ++#include "rm-rf.h" + #include "string-util.h" + #include "tests.h" + +@@ -67,8 +69,42 @@ static void test_journal_flush_one(int argc, char *argv[]) { + break; + } + ++ if (n == 0) ++ return (void) log_tests_skipped("No journal entry found"); ++ ++ /* Open the new journal before archiving and offlining the file. */ ++ sd_journal_close(j); ++ assert_se(sd_journal_open_directory(&j, dn, 0) >= 0); ++ ++ /* Read the online journal. */ ++ assert_se(sd_journal_seek_tail(j) >= 0); ++ assert_se(sd_journal_step_one(j, 0) > 0); ++ printf("current_journal: %s (%i)\n", j->current_file->path, j->current_file->fd); ++ assert_se(show_journal_entry(stdout, j, OUTPUT_EXPORT, 0, 0, NULL, NULL, NULL, &(dual_timestamp) {}, &(sd_id128_t) {}) >= 0); ++ ++ uint64_t p; ++ assert_se(journal_file_tail_end_by_mmap(j->current_file, &p) >= 0); ++ for (uint64_t q = ALIGN64(p + 1); q < (uint64_t) j->current_file->last_stat.st_size; q = ALIGN64(q + 1)) { ++ Object *o; ++ ++ r = journal_file_move_to_object(j->current_file, OBJECT_UNUSED, q, &o); ++ assert_se(IN_SET(r, -EBADMSG, -EADDRNOTAVAIL)); ++ } ++ ++ /* Archive and offline file. */ ++ assert_se(journal_file_archive(new_journal->file, NULL) >= 0); ++ assert_se(managed_journal_file_set_offline(new_journal, /* wait = */ true) >= 0); ++ ++ /* Read the archived and offline journal. */ ++ for (uint64_t q = ALIGN64(p + 1); q < (uint64_t) j->current_file->last_stat.st_size; q = ALIGN64(q + 1)) { ++ Object *o; ++ ++ r = journal_file_move_to_object(j->current_file, OBJECT_UNUSED, q, &o); ++ assert_se(IN_SET(r, -EBADMSG, -EADDRNOTAVAIL, -EIDRM)); ++ } ++ + unlink(fn); +- assert_se(rmdir(dn) == 0); ++ assert_se(rm_rf(dn, REMOVE_ROOT|REMOVE_PHYSICAL) == 0); + } + + TEST(journal_flush) { diff --git a/SOURCES/split-files.py b/SOURCES/split-files.py index dd9ec8f..0036f16 100644 --- a/SOURCES/split-files.py +++ b/SOURCES/split-files.py @@ -17,6 +17,7 @@ def files(root): o_libs = open('.file-list-libs', 'w') o_udev = open('.file-list-udev', 'w') +o_ukify = open('.file-list-ukify', 'w') o_boot = open('.file-list-boot', 'w') o_pam = open('.file-list-pam', 'w') o_rpm_macros = open('.file-list-rpm-macros', 'w') @@ -59,6 +60,8 @@ for file in files(buildroot): o = o_rpm_macros elif '/usr/lib/systemd/tests' in n: o = o_tests + elif 'ukify' in n: + o = o_ukify elif re.search(r'/libsystemd-(shared|core)-.*\.so$', n): o = o_main elif re.search(r'/libcryptsetup-token-systemd-.*\.so$', n): @@ -165,7 +168,8 @@ for file in files(buildroot): oomd\.conf| oomctl| org.freedesktop.oom1| - systemd-oomd + systemd-oomd| + systemd-oom\.conf ''', n, re.X): o = o_oomd diff --git a/SPECS/systemd.spec b/SPECS/systemd.spec index 778682a..3c1f963 100644 --- a/SPECS/systemd.spec +++ b/SPECS/systemd.spec @@ -21,7 +21,7 @@ Name: systemd Url: https://systemd.io Version: 252 -Release: 18%{?dist} +Release: 27%{?dist} # For a breakdown of the licensing, see README License: LGPLv2+ and MIT and GPLv2+ Summary: System and Service Manager @@ -65,6 +65,11 @@ Source23: sysusers.prov Source24: sysusers.generate-pre.sh Source25: rc.local +# Download hwdb of RHEL net naming scheme; this is a temporary it will be later moved to kernel +# see: https://issues.redhat.com/browse/RHELBU-2374 +%global rhel_nns_version 0.4 +Source26: https://gitlab.com/mschmidt2/rhel-net-naming-sysattrs/-/archive/v%{rhel_nns_version}/rhel-net-naming-sysattrs-v%{rhel_nns_version}.tar.gz + %if 0 GIT_DIR=../../src/systemd/.git git format-patch-ab --no-signature -M -N v235..v235-stable i=1; for j in 00*patch; do printf "Patch%04d: %s\n" $i $j; i=$((i+1));done|xclip @@ -447,6 +452,289 @@ Patch0366: 0366-dissect-image-add-probe_sector_size-helper-for-detec.patch Patch0367: 0367-loop-util-always-tell-kernel-explicitly-about-loopba.patch Patch0368: 0368-Revert-Treat-EPERM-as-not-available-too.patch Patch0369: 0369-Revert-test-accept-EPERM-for-unavailable-idmapped-mo.patch +Patch0370: 0370-ci-Extend-source-git-automation.patch +Patch0371: 0371-netif-naming-scheme-let-s-also-include-rhel8-schemes.patch +Patch0372: 0372-systemd-analyze-Add-table-and-JSON-output-implementa.patch +Patch0373: 0373-systemd-analyze-Update-man-systemd-analyze.xml-with-.patch +Patch0374: 0374-systemd-analyze-Add-tab-complete-logic-for-plot.patch +Patch0375: 0375-systemd-analyze-Add-json-table-and-no-legend-tests-f.patch +Patch0376: 0376-ci-enable-source-git-automation-to-validate-reviews-.patch +Patch0377: 0377-ci-remove-Mergify-config-replaced-by-Pull-Request-Va.patch +Patch0378: 0378-ci-enable-auto-merge-GH-Action.patch +Patch0379: 0379-ci-add-missing-permissions.patch +Patch0380: 0380-ci-permissions-write-all.patch +Patch0381: 0381-ci-lint-exclude-.in-files-from-ShellCheck-lint.patch +Patch0382: 0382-udev-raise-RLIMIT_NOFILE-as-high-as-we-can.patch +Patch0383: 0383-udev-net-allow-new-link-name-as-an-altname-before-re.patch +Patch0384: 0384-sd-netlink-do-not-swap-old-name-and-alternative-name.patch +Patch0385: 0385-sd-netlink-restore-altname-on-error-in-rtnl_set_link.patch +Patch0386: 0386-udev-attempt-device-rename-even-if-interface-is-up.patch +Patch0387: 0387-sd-netlink-add-a-test-for-rtnl_set_link_name.patch +Patch0388: 0388-test-network-add-a-test-for-renaming-device-to-curre.patch +Patch0389: 0389-udev-align-table.patch +Patch0390: 0390-sd-device-make-device_set_syspath-clear-sysname-and-.patch +Patch0391: 0391-sd-device-do-not-directly-access-entry-in-sd-device-.patch +Patch0392: 0392-udev-move-device_rename-from-device-private.c.patch +Patch0393: 0393-udev-restore-syspath-and-properties-on-failure.patch +Patch0394: 0394-sd-device-introduce-device_get_property_int.patch +Patch0395: 0395-core-device-downgrade-log-level-for-ignored-errors.patch +Patch0396: 0396-core-device-ignore-failed-uevents.patch +Patch0397: 0397-test-add-tests-for-failure-in-renaming-network-inter.patch +Patch0398: 0398-test-modernize-test-netlink.c.patch +Patch0399: 0399-test-netlink-use-dummy-interface-to-test-assigning-n.patch +Patch0400: 0400-udev-use-SYNTHETIC_ERRNO-at-one-more-place.patch +Patch0401: 0401-udev-make-udev_builtin_run-take-UdevEvent.patch +Patch0402: 0402-udev-net-verify-ID_NET_XYZ-before-trying-to-assign-i.patch +Patch0403: 0403-udev-net-generate-new-network-interface-name-only-on.patch +Patch0404: 0404-sd-netlink-make-rtnl_set_link_name-optionally-append.patch +Patch0405: 0405-udev-net-assign-alternative-names-only-on-add-uevent.patch +Patch0406: 0406-test-add-tests-for-renaming-network-interface.patch +Patch0407: 0407-Backport-ukify-from-upstream.patch +Patch0408: 0408-bootctl-make-json-output-normal-json.patch +Patch0409: 0409-test-replace-readfp-with-read_file.patch +Patch0410: 0410-stub-measure-document-and-measure-.uname-UKI-section.patch +Patch0411: 0411-boot-measure-.sbat-section.patch +Patch0412: 0412-Revert-test_ukify-no-stinky-root-needed-for-signing.patch +Patch0413: 0413-ukify-move-to-usr-bin-and-mark-as-non-non-experiment.patch +Patch0414: 0414-kernel-install-Add-uki-layout.patch +Patch0415: 0415-kernel-install-remove-math-slang-from-man-page.patch +Patch0416: 0416-kernel-install-handle-uki-installs-automatically.patch +Patch0417: 0417-90-uki-copy.install-create-BOOT-EFI-Linux-directory-.patch +Patch0418: 0418-kernel-install-Log-location-that-uki-is-installed-in.patch +Patch0419: 0419-bootctl-fix-errno-logging.patch +Patch0420: 0420-bootctl-add-kernel-identity-command.patch +Patch0421: 0421-bootctl-add-kernel-inspect-command.patch +Patch0422: 0422-bootctl-add-kernel-inspect-to-help-text.patch +Patch0423: 0423-bootctl-drop-full-stop-at-end-of-help-texts.patch +Patch0424: 0424-bootctl-change-section-title-for-kernel-image-comman.patch +Patch0425: 0425-bootctl-remove-space-that-should-not-be-there.patch +Patch0426: 0426-bootctl-kernel-inspect-print-os-info.patch +Patch0427: 0427-bootctl-uki-several-coding-style-fixlets.patch +Patch0428: 0428-tree-wide-unify-how-we-pick-OS-pretty-name-to-displa.patch +Patch0429: 0429-bootctl-uki-several-follow-ups-for-inspect_osrel.patch +Patch0430: 0430-bootctl-Add-missing-m.patch +Patch0431: 0431-bootctl-tweak-DOS-header-magic-check.patch +Patch0432: 0432-meson-fix-installation-of-ukify.patch +Patch0433: 0433-sd-id128-introduce-id128_hash_ops_free.patch +Patch0434: 0434-udevadm-trigger-allow-to-fallback-without-synthetic-.patch +Patch0435: 0435-udevadm-trigger-settle-with-synthetic-UUID-if-the-ke.patch +Patch0436: 0436-udevadm-trigger-also-check-with-the-original-syspath.patch +Patch0437: 0437-test-use-udevadm-trigger-settle-even-if-device-is-re.patch +Patch0438: 0438-sd-event-don-t-mistake-USEC_INFINITY-passed-in-for-o.patch +Patch0439: 0439-pid1-rework-service_arm_timer-to-optionally-take-a-r.patch +Patch0440: 0440-manager-add-one-more-assert.patch +Patch0441: 0441-pid1-add-new-Type-notify-reload-service-type.patch +Patch0442: 0442-man-document-Type-notify-reload.patch +Patch0443: 0443-pid1-make-sure-we-send-our-calling-service-manager-R.patch +Patch0444: 0444-networkd-implement-Type-notify-reload-protocol.patch +Patch0445: 0445-udevd-implement-the-full-Type-notify-reload-protocol.patch +Patch0446: 0446-logind-implement-Type-notify-reload-protocol-properl.patch +Patch0447: 0447-notify-add-stopping-reloading-switches.patch +Patch0448: 0448-test-add-Type-notify-reload-testcase.patch +Patch0449: 0449-update-TODO.patch +Patch0450: 0450-core-check-for-SERVICE_RELOAD_NOTIFY-in-manager_dbus.patch +Patch0451: 0451-Revert-man-mention-System-Administrator-s-Guide-in-s.patch +Patch0452: 0452-man-mention-RHEL-documentation-in-systemctl-s-man-pa.patch +Patch0453: 0453-resolved-actually-check-authenticated-flag-of-SOA-tr.patch +Patch0454: 0454-udev-allow-denylist-for-reading-sysfs-attributes-whe.patch +Patch0455: 0455-man-environment-value-udev-property.patch +Patch0456: 0456-logind-don-t-setup-idle-session-watch-for-lock-scree.patch +Patch0457: 0457-logind-don-t-make-idle-action-timer-accuracy-more-co.patch +Patch0458: 0458-logind-do-TTY-idle-logic-only-for-sessions-marked-as.patch +Patch0459: 0459-meson-Properly-install-90-uki-copy.install.patch +Patch0460: 0460-ci-use-source-git-automation-composite-Action.patch +Patch0461: 0461-ci-increase-the-cron-interval-to-45-minutes.patch +Patch0462: 0462-ci-add-all-Z-Stream-versions-to-array-of-allowed-ver.patch +Patch0463: 0463-udev-net_id-introduce-naming-scheme-for-RHEL-9.4.patch +Patch0464: 0464-basic-errno-util-add-wrappers-which-only-accept-nega.patch +Patch0465: 0465-errno-util-allow-ERRNO_IS_-to-accept-types-wider-tha.patch +Patch0466: 0466-udev-add-new-builtin-net_driver.patch +Patch0467: 0467-udev-net_id-introduce-naming-scheme-for-RHEL-8.10.patch +Patch0468: 0468-test-merge-TEST-20-MAINPIDGAMES-into-TEST-07-PID1-fi.patch +Patch0469: 0469-test-use-the-default-nsec3-iterations-value.patch +Patch0470: 0470-test-explicitly-set-nsec3-iterations-to-0.patch +Patch0471: 0471-core-mount-namespaces-Remove-auxiliary-bind-mounts-d.patch +Patch0472: 0472-ci-deploy-systemd-man-to-GitHub-Pages.patch +Patch0473: 0473-doc-add-missing-listitem-to-systemd.net-naming-schem.patch +Patch0474: 0474-man-reorder-the-list-of-supported-naming-schemes.patch +Patch0475: 0475-tree-wide-fix-return-value-handling-of-base64mem.patch +Patch0476: 0476-Consolidate-various-TAKE_-into-TAKE_GENERIC-add-TAKE.patch +Patch0477: 0477-pcrphase-add-SYSTEMD_PCRPHASE_STUB_VERIFY-env-var-fo.patch +Patch0478: 0478-pcrphase-gracefully-exit-if-TPM2-support-is-incomple.patch +Patch0479: 0479-tpm2-util-split-out-code-that-derives-good-TPM2-bank.patch +Patch0480: 0480-tpm2-util-split-out-code-that-extends-a-PCR-from-pcr.patch +Patch0481: 0481-tpm2-util-optionally-do-HMAC-in-tpm2_extend_bytes-in.patch +Patch0482: 0482-cryptsetup-add-tpm2-measure-pcr-and-tpm2-measure-ban.patch +Patch0483: 0483-man-document-the-new-crypttab-measurement-options.patch +Patch0484: 0484-gpt-auto-generator-automatically-measure-root-var-vo.patch +Patch0485: 0485-blkid-util-define-enum-for-blkid_do_safeprobe-return.patch +Patch0486: 0486-pcrphase-make-tool-more-generic-reuse-for-measuring-.patch +Patch0487: 0487-units-measure-etc-machine-id-into-PCR-15-during-earl.patch +Patch0488: 0488-generators-optionally-measure-file-systems-at-boot.patch +Patch0489: 0489-tpm2-add-common-helper-for-checking-if-we-are-runnin.patch +Patch0490: 0490-man-document-new-machine-id-fs-measurement-options.patch +Patch0491: 0491-test-add-simple-integration-test-for-checking-PCR-ex.patch +Patch0492: 0492-update-TODO.patch +Patch0493: 0493-cryptsetup-retry-TPM2-unseal-operation-if-it-fails-w.patch +Patch0494: 0494-boot-Simplify-object-erasure.patch +Patch0495: 0495-tree-wide-use-CLEANUP_ERASE-at-various-places.patch +Patch0496: 0496-dlfcn-add-new-safe_dclose-helper.patch +Patch0497: 0497-tpm2-rename-tpm2-alg-id-string-functions.patch +Patch0498: 0498-tpm2-rename-struct-tpm2_context-to-Tpm2Context.patch +Patch0499: 0499-tpm2-use-ref-counter-for-Tpm2Context.patch +Patch0500: 0500-tpm2-use-Tpm2Context-instead-of-ESYS_CONTEXT.patch +Patch0501: 0501-tpm2-add-Tpm2Handle-with-automatic-cleanup.patch +Patch0502: 0502-tpm2-simplify-tpm2_seal-blob-creation.patch +Patch0503: 0503-tpm2-add-salt-to-pin.patch +Patch0504: 0504-basic-macro-add-macro-to-iterate-variadic-args.patch +Patch0505: 0505-test-test-macro-add-tests-for-FOREACH_VA_ARGS.patch +Patch0506: 0506-basic-bitfield-add-bitfield-operations.patch +Patch0507: 0507-test-test-bitfield-add-tests-for-bitfield-macros.patch +Patch0508: 0508-tpm2-add-tpm2_get_policy_digest.patch +Patch0509: 0509-tpm2-add-TPM2_PCR_VALID.patch +Patch0510: 0510-tpm2-add-rename-functions-to-manage-pcr-selections.patch +Patch0511: 0511-test-test-tpm2-add-tests-for-pcr-selection-functions.patch +Patch0512: 0512-tpm2-add-tpm2_pcr_read.patch +Patch0513: 0513-tpm2-move-openssl-required-ifdef-code-out-of-policy-.patch +Patch0514: 0514-tpm2-add-tpm2_is_encryption_session.patch +Patch0515: 0515-tpm2-move-policy-building-out-of-policy-session-crea.patch +Patch0516: 0516-tpm2-add-support-for-a-trusted-SRK.patch +Patch0517: 0517-tpm2-fix-nits-from-PR-26185.patch +Patch0518: 0518-tpm2-replace-magic-number.patch +Patch0519: 0519-tpm2-add-tpm2_digest_-functions.patch +Patch0520: 0520-tpm2-replace-hash_pin-with-tpm2_digest_-functions.patch +Patch0521: 0521-tpm2-add-tpm2_set_auth.patch +Patch0522: 0522-tpm2-add-tpm2_get_name.patch +Patch0523: 0523-tpm2-rename-pcr_values_size-vars-to-n_pcr_values.patch +Patch0524: 0524-tpm2-add-tpm2_policy_pcr.patch +Patch0525: 0525-tpm2-add-tpm2_policy_auth_value.patch +Patch0526: 0526-tpm2-add-tpm2_policy_authorize.patch +Patch0527: 0527-tpm2-use-tpm2_policy_authorize.patch +Patch0528: 0528-tpm2-add-tpm2_calculate_sealing_policy.patch +Patch0529: 0529-tpm-remove-external-calls-to-dlopen_tpm2.patch +Patch0530: 0530-tpm2-remove-all-extern-tpm2-tss-symbols.patch +Patch0531: 0531-tpm2-add-tpm2_get_capability-tpm2_cache_capabilities.patch +Patch0532: 0532-tpm2-verify-symmetric-parms-in-tpm2_context_new.patch +Patch0533: 0533-tpm2-replace-_cleanup_tpm2_-macros-with-_cleanup_.patch +Patch0534: 0534-tpm2-util-use-compound-initialization-when-allocatin.patch +Patch0535: 0535-tpm2-add-tpm2_get_capability_handle-tpm2_esys_handle.patch +Patch0536: 0536-tpm2-add-tpm2_read_public.patch +Patch0537: 0537-tpm2-add-tpm2_get_legacy_template-and-tpm2_get_srk_t.patch +Patch0538: 0538-tpm2-add-tpm2_load.patch +Patch0539: 0539-tpm2-add-tpm2_load_external.patch +Patch0540: 0540-tpm2-move-local-vars-in-tpm2_seal-to-point-of-use.patch +Patch0541: 0541-tpm2-replace-magic-number-in-hmac_sensitive-initiali.patch +Patch0542: 0542-tpm2-add-tpm2_create.patch +Patch0543: 0543-tpm2-replace-tpm2_capability_pcrs-macro-with-direct-.patch +Patch0544: 0544-basic-alloc-util-add-greedy_realloc_append.patch +Patch0545: 0545-tpm2-cache-the-TPM-supported-commands-add-tpm2_suppo.patch +Patch0546: 0546-tpm2-cache-TPM-algorithms.patch +Patch0547: 0547-tpm2-add-tpm2_persist_handle.patch +Patch0548: 0548-tpm2-add-tpm2_get_or_create_srk.patch +Patch0549: 0549-tpm2-move-local-vars-in-tpm2_unseal-to-point-of-use.patch +Patch0550: 0550-tpm2-remove-tpm2_make_primary.patch +Patch0551: 0551-tpm2-use-CreatePrimary-to-create-primary-keys-instea.patch +Patch0552: 0552-cryptsetup-downgrade-a-bunch-of-log-messages-that-to.patch +Patch0553: 0553-boot-measure-replace-TPM-PolicyPCR-session-with-calc.patch +Patch0554: 0554-core-imply-DeviceAllow-dev-tpmrm0-with-LoadCredentia.patch +Patch0555: 0555-added-more-test-cases.patch +Patch0556: 0556-test-fixed-negative-checks-in-TEST-70-TPM2.patch +Patch0557: 0557-systemd-cryptenroll-add-string-aliases-for-tpm2-PCRs.patch +Patch0558: 0558-cryptenroll-fix-an-assertion-with-weak-passwords.patch +Patch0559: 0559-man-systemd-cryptenroll-update-list-of-PCRs-link-to-.patch +Patch0560: 0560-tpm2-add-debug-logging-to-functions-converting-hash-.patch +Patch0561: 0561-tpm2-add-tpm2_hash_alg_to_size.patch +Patch0562: 0562-tpm2-change-tpm2_tpm-_pcr_selection_to_mask-to-retur.patch +Patch0563: 0563-tpm2-add-more-helper-functions-for-managing-TPML_PCR.patch +Patch0564: 0564-tpm2-add-Tpm2PCRValue-struct-and-associated-function.patch +Patch0565: 0565-tpm2-move-declared-functions-in-header-lower-down.patch +Patch0566: 0566-tpm2-declare-tpm2_log_debug_-functions-in-tpm2_util..patch +Patch0567: 0567-tpm2-change-tpm2_calculate_policy_pcr-tpm2_calculate.patch +Patch0568: 0568-tpm2-change-tpm2_parse_pcr_argument-parameters-to-pa.patch +Patch0569: 0569-tpm2-add-TPM2B_-_MAKE-TPM2B_-_CHECK_SIZE-macros.patch +Patch0570: 0570-tpm2-add-tpm2_pcr_read_missing_values.patch +Patch0571: 0571-openssl-add-openssl_pkey_from_pem.patch +Patch0572: 0572-openssl-add-rsa_pkey_new-rsa_pkey_from_n_e-rsa_pkey_.patch +Patch0573: 0573-openssl-add-ecc_pkey_new-ecc_pkey_from_curve_x_y-ecc.patch +Patch0574: 0574-test-add-DEFINE_HEX_PTR-helper-function.patch +Patch0575: 0575-openssl-add-test-openssl.patch +Patch0576: 0576-tpm2-add-functions-to-convert-TPM2B_PUBLIC-to-from-o.patch +Patch0577: 0577-tpm2-move-policy-calculation-out-of-tpm2_seal.patch +Patch0578: 0578-man-update-systemd-cryptenroll-man-page-with-details.patch +Patch0579: 0579-tpm2-update-TEST-70-TPM2-to-test-passing-PCR-value-t.patch +Patch0580: 0580-tpm2-change-alg_to_-functions-to-use-switch.patch +Patch0581: 0581-tpm2-lowercase-TPM2_PCR_VALUE-S-_VALID-functions.patch +Patch0582: 0582-tpm2-move-cast-from-lhs-to-rhs-in-uint16_t-int-compa.patch +Patch0583: 0583-tpm2-in-validator-functions-return-false-instead-of-.patch +Patch0584: 0584-tpm2-in-tpm2_pcr_values_valid-use-FOREACH_ARRAY.patch +Patch0585: 0585-tpm2-use-SIZE_MAX-instead-of-strlen-for-unhexmem.patch +Patch0586: 0586-tpm2-put-isempty-check-inside-previous-isempty-check.patch +Patch0587: 0587-tpm2-simplify-call-to-asprintf.patch +Patch0588: 0588-tpm2-check-pcr-value-hash-0-before-looking-up-hash-a.patch +Patch0589: 0589-tpm2-use-strempty.patch +Patch0590: 0590-tpm2-split-TPM2_PCR_VALUE_MAKE-over-multiple-lines.patch +Patch0591: 0591-tpm2-remove-ret_-prefix-from-input-output-params.patch +Patch0592: 0592-tpm2-use-memcpy_safe-instead-of-memcpy.patch +Patch0593: 0593-openssl-use-new-char-size-instead-of-malloc-size.patch +Patch0594: 0594-tpm2-use-table-for-openssl-tpm2-ecc-curve-id-mapping.patch +Patch0595: 0595-tpm2-use-switch-instead-of-if-else.patch +Patch0596: 0596-tpm2-make-logging-level-consistent-at-debug-for-some.patch +Patch0597: 0597-tpm2-remove-unnecessary-void-cast.patch +Patch0598: 0598-tpm2-add-tpm2_pcr_values_has_-any-all-_values-functi.patch +Patch0599: 0599-tpm2-wrap-7-in-UINT32_C.patch +Patch0600: 0600-cryptenroll-change-man-page-example-to-remove-leadin.patch +Patch0601: 0601-openssl-add-log_openssl_errors.patch +Patch0602: 0602-openssl-add-openssl_digest_size.patch +Patch0603: 0603-openssl-add-openssl_digest_many.patch +Patch0604: 0604-openssl-replace-openssl_hash-with-openssl_digest.patch +Patch0605: 0605-openssl-add-openssl_hmac_many.patch +Patch0606: 0606-openssl-add-rsa_oaep_encrypt_bytes.patch +Patch0607: 0607-openssl-add-kdf_kb_hmac_derive.patch +Patch0608: 0608-openssl-add-openssl_cipher_many.patch +Patch0609: 0609-openssl-add-ecc_edch.patch +Patch0610: 0610-openssl-add-kdf_ss_derive.patch +Patch0611: 0611-dlfcn-util-add-static-asserts-ensuring-our-sym_xyz-f.patch +Patch0612: 0612-tpm2-add-tpm2_marshal_blob-and-tpm2_unmarshal_blob.patch +Patch0613: 0613-tpm2-add-tpm2_serialize-and-tpm2_deserialize.patch +Patch0614: 0614-tpm2-add-tpm2_index_to_handle-and-tpm2_index_from_ha.patch +Patch0615: 0615-tpm2-fix-build-failure-without-openssl.patch +Patch0616: 0616-tpm2-util-look-for-tpm2-pcr-signature.json-directly-.patch +Patch0617: 0617-tpm2-downgrade-most-log-functions-from-error-to-debu.patch +Patch0618: 0618-tpm2-handle-older-tpm-enrollments-without-a-saved-pc.patch +Patch0619: 0619-tpm2-allow-tpm2_make_encryption_session-without-bind.patch +Patch0620: 0620-tpm2-update-tpm2-test-for-supported-commands.patch +Patch0621: 0621-tpm2-use-GREEDY_REALLOC_APPEND-in-tpm2_get_capabilit.patch +Patch0622: 0622-tpm2-change-tpm2_unseal-to-accept-Tpm2Context-instea.patch +Patch0623: 0623-tpm2-cache-TPM-s-supported-ECC-curves.patch +Patch0624: 0624-tpm2-util-make-tpm2_marshal_blob-tpm2_unmarshal_blob.patch +Patch0625: 0625-tpm2-util-make-tpm2_read_public-static-as-we-use-it-.patch +Patch0626: 0626-cryptenroll-allow-specifying-handle-index-of-key-to-.patch +Patch0627: 0627-test-add-tests-for-systemd-cryptenroll-tpm2-seal-key.patch +Patch0628: 0628-tpm2-do-not-call-Esys_TR_Close.patch +Patch0629: 0629-tpm2-don-t-use-GetCapability-to-check-transient-hand.patch +Patch0630: 0630-tpm2-util-pick-up-a-few-new-symbols-from-tpm2-tss.patch +Patch0631: 0631-tpm2-add-tpm2_get_pin_auth.patch +Patch0632: 0632-tpm2-instead-of-adjusting-authValue-trailing-0-s-tri.patch +Patch0633: 0633-tpm2-util-rename-tpm2_calculate_name-tpm2_calculate_.patch +Patch0634: 0634-cryptenroll-do-not-implicitly-verify-with-default-tp.patch +Patch0635: 0635-cryptenroll-drop-deadcode.patch +Patch0636: 0636-tpm2-allow-using-tpm2_get_srk_template-without-tpm.patch +Patch0637: 0637-tpm2-add-test-to-verify-srk-templates.patch +Patch0638: 0638-tpm2-add-tpm2_sym_alg_-_string-and-tpm2_sym_mode_-_s.patch +Patch0639: 0639-tpm2-add-tpm2_calculate_seal-and-helper-functions.patch +Patch0640: 0640-tpm2-update-test-tpm2-for-tpm2_calculate_seal.patch +Patch0641: 0641-cryptenroll-add-support-for-calculated-TPM2-enrollme.patch +Patch0642: 0642-test-update-TEST-70-with-systemd-cryptenroll-calcula.patch +Patch0643: 0643-openssl-util-avoid-freeing-invalid-pointer.patch +Patch0644: 0644-creds-util-check-for-CAP_DAC_READ_SEARCH.patch +Patch0645: 0645-creds-util-do-not-try-TPM2-if-there-is-not-support.patch +Patch0646: 0646-creds-util-merge-the-TPM2-detection-for-initrd.patch +Patch0647: 0647-cryptenroll-fix-a-memory-leak.patch +Patch0648: 0648-sd-journal-introduce-sd_journal_step_one.patch +Patch0649: 0649-test-modernize-test-journal-flush.patch +Patch0650: 0650-journal-file-util-do-not-fail-when-journal_file_set_.patch +Patch0651: 0651-journal-file-util-Prefer-punching-holes-instead-of-t.patch +Patch0652: 0652-test-add-reproducer-for-SIGBUS-issue-caused-by-journ.patch # Downstream-only patches (9000–9999) @@ -497,6 +785,8 @@ BuildRequires: tree BuildRequires: hostname BuildRequires: python3dist(lxml) BuildRequires: python3dist(jinja2) +BuildRequires: python3dist(pefile) +BuildRequires: python3dist(cryptography) BuildRequires: firewalld-filesystem BuildRequires: libseccomp-devel BuildRequires: meson >= 0.43 @@ -659,6 +949,21 @@ This package contains systemd-udev and the rules and hardware database needed to manage device nodes. This package is necessary on physical machines and in virtual machines, but not in containers. +%package ukify +Summary: Tool to build Unified Kernel Images +Requires: %{name} = %{version}-%{release} + +Requires: python3dist(cryptography) +Requires: python3dist(pefile) + +BuildArch: noarch + +%description ukify +This package provides ukify, a script that combines a kernel image, an initrd, +with a command line, and possibly PCR measurements and other metadata, into a +Unified Kernel Image (UKI). + + %if 0%{?have_gnu_efi} %package boot-unsigned Summary: UEFI boot manager (unsigned version) @@ -760,8 +1065,18 @@ Standalone sysusers binary with no dependencies on the systemd-shared library or other libraries from systemd-libs. This package conflicts with the main systemd package and is meant for use in non-systemd systems. +%package -n rhel-net-naming-sysattrs +Summary: RHEL-specific network naming sysattrs +BuildRequires: pkgconfig(dracut) +BuildArch: noarch + +%description -n rhel-net-naming-sysattrs +rhel-net-naming-sysattrs package provides hwdb and udev rule needed for stable +network naming scheme across RHEL releases. + %prep %autosetup -n %{?commit:%{name}%{?stable:-stable}-%{commit}}%{!?commit:%{name}%{?stable:-stable}-%{version_no_tilde}} -S git_am -p1 +%setup -T -D -a 26 %build %define ntpvendor %(source /etc/os-release; echo ${ID}) @@ -854,7 +1169,10 @@ CONFIGURE_OPTS=( -Dportabled=false -Dnetworkd=false -Dsupport-url=https://access.redhat.com/support + # https://issues.redhat.com/browse/RHEL-16810 + -Dsbat-distro-url=mailto:secalert@redhat.com -Ddefault-net-naming-scheme=rhel-9.0 + -Dukify=true ) %if %{without lto} @@ -1020,6 +1338,9 @@ python3 %{SOURCE2} %buildroot </dev/null mv %{_localstatedir}/lib/backlight %{_localstatedir}/lib/systemd/backlight &>/dev/null -udevadm hwdb --update &>/dev/null +systemd-hwdb update &>/dev/null || : %systemd_post %udev_services @@ -1217,6 +1538,9 @@ getent passwd systemd-oom &>/dev/null || useradd -r -l -g systemd-oom -d / -s /s %postun oomd %systemd_postun_with_restart systemd-oomd.service +%post -n rhel-net-naming-sysattrs +systemd-hwdb update &>/dev/null || : + %global _docdir_fmt %{name} %files -f %{name}.lang -f .file-list-main @@ -1251,6 +1575,8 @@ getent passwd systemd-oom &>/dev/null || useradd -r -l -g systemd-oom -d / -s /s %files udev -f .file-list-udev +%files ukify -f .file-list-ukify + %if 0%{?have_gnu_efi} %files boot-unsigned -f .file-list-boot %endif @@ -1267,7 +1593,316 @@ getent passwd systemd-oom &>/dev/null || useradd -r -l -g systemd-oom -d / -s /s %files standalone-sysusers -f .file-list-standalone-sysusers +%files -n rhel-net-naming-sysattrs +%{_udevrulesdir}/70-rhel-net-naming-sysattrs.rules +%{_udevhwdbdir}/50-net-naming-sysattr-allowlist.hwdb +%dir %{_prefix}/lib/dracut/modules.d/70rhel-net-naming-sysattrs +%{_prefix}/lib/dracut/modules.d/70rhel-net-naming-sysattrs/* + %changelog +* Thu Feb 15 2024 systemd maintenance team - 252-27 +- test: merge TEST-20-MAINPIDGAMES into TEST-07-PID1 (fixup) (RHEL-1086) +- test: use the default nsec3-iterations value (RHEL-1086) +- test: explicitly set nsec3-iterations to 0 (RHEL-1086) +- core: mount namespaces: Remove auxiliary bind mounts directory after unit termination (RHEL-19483) +- ci: deploy systemd man to GitHub Pages (RHEL-1086) +- doc: add missing `` to `systemd.net-naming-scheme.xml` (RHEL-7026) +- man: reorder the list of supported naming schemes (RHEL-7026) +- tree-wide: fix return value handling of base64mem() (RHEL-16182) +- Consolidate various TAKE_* into TAKE_GENERIC(), add TAKE_STRUCT() (RHEL-16182) +- pcrphase: add $SYSTEMD_PCRPHASE_STUB_VERIFY env var for overriding stub check (RHEL-16182) +- pcrphase: gracefully exit if TPM2 support is incomplete (RHEL-16182) +- tpm2-util: split out code that derives "good" TPM2 banks into an strv from pcrphase and generalize it in tpm2-util.c (RHEL-16182) +- tpm2-util: split out code that extends a PCR from pcrphase (RHEL-16182) +- tpm2-util: optionally do HMAC in tpm2_extend_bytes() in case we process sensitive data (RHEL-16182) +- cryptsetup: add tpm2-measure-pcr= and tpm2-measure-bank= crypttab options (RHEL-16182) +- man: document the new crypttab measurement options (RHEL-16182) +- gpt-auto-generator: automatically measure root/var volume keys into PCR 15 (RHEL-16182) +- blkid-util: define enum for blkid_do_safeprobe() return values (RHEL-16182) +- pcrphase: make tool more generic, reuse for measuring machine id/fs uuids (RHEL-16182) +- units: measure /etc/machine-id into PCR 15 during early boot (RHEL-16182) +- generators: optionally, measure file systems at boot (RHEL-16182) +- tpm2: add common helper for checking if we are running on UKI with TPM measurements (RHEL-16182) +- man: document new machine-id/fs measurement options (RHEL-16182) +- test: add simple integration test for checking PCR extension works as it should (RHEL-16182) +- update TODO (RHEL-16182) +- cryptsetup: retry TPM2 unseal operation if it fails with TPM2_RC_PCR_CHANGED (RHEL-16182) +- boot: Simplify object erasure (RHEL-16182) +- tree-wide: use CLEANUP_ERASE() at various places (RHEL-16182) +- dlfcn: add new safe_dclose() helper (RHEL-16182) +- tpm2: rename tpm2 alg id<->string functions (RHEL-16182) +- tpm2: rename struct tpm2_context to Tpm2Context (RHEL-16182) +- tpm2: use ref counter for Tpm2Context (RHEL-16182) +- tpm2: use Tpm2Context* instead of ESYS_CONTEXT* (RHEL-16182) +- tpm2: add Tpm2Handle with automatic cleanup (RHEL-16182) +- tpm2: simplify tpm2_seal() blob creation (RHEL-16182) +- tpm2: add salt to pin (RHEL-16182) +- basic/macro: add macro to iterate variadic args (RHEL-16182) +- test/test-macro: add tests for FOREACH_VA_ARGS() (RHEL-16182) +- basic/bitfield: add bitfield operations (RHEL-16182) +- test/test-bitfield: add tests for bitfield macros (RHEL-16182) +- tpm2: add tpm2_get_policy_digest() (RHEL-16182) +- tpm2: add TPM2_PCR_VALID() (RHEL-16182) +- tpm2: add/rename functions to manage pcr selections (RHEL-16182) +- test/test-tpm2: add tests for pcr selection functions (RHEL-16182) +- tpm2: add tpm2_pcr_read() (RHEL-16182) +- tpm2: move openssl-required ifdef code out of policy-building function (RHEL-16182) +- tpm2: add tpm2_is_encryption_session() (RHEL-16182) +- tpm2: move policy building out of policy session creation (RHEL-16182) +- tpm2: add support for a trusted SRK (RHEL-16182) +- tpm2: fix nits from PR #26185 (RHEL-16182) +- tpm2: replace magic number (RHEL-16182) +- tpm2: add tpm2_digest_*() functions (RHEL-16182) +- tpm2: replace hash_pin() with tpm2_digest_*() functions (RHEL-16182) +- tpm2: add tpm2_set_auth() (RHEL-16182) +- tpm2: add tpm2_get_name() (RHEL-16182) +- tpm2: rename pcr_values_size vars to n_pcr_values (RHEL-16182) +- tpm2: add tpm2_policy_pcr() (RHEL-16182) +- tpm2: add tpm2_policy_auth_value() (RHEL-16182) +- tpm2: add tpm2_policy_authorize() (RHEL-16182) +- tpm2: use tpm2_policy_authorize() (RHEL-16182) +- tpm2: add tpm2_calculate_sealing_policy() (RHEL-16182) +- tpm: remove external calls to dlopen_tpm2() (RHEL-16182) +- tpm2: remove all extern tpm2-tss symbols (RHEL-16182) +- tpm2: add tpm2_get_capability(), tpm2_cache_capabilities(), tpm2_capability_pcrs() (RHEL-16182) +- tpm2: verify symmetric parms in tpm2_context_new() (RHEL-16182) +- tpm2: replace _cleanup_tpm2_* macros with _cleanup_() (RHEL-16182) +- tpm2-util: use compound initialization when allocating tpm2 objects (RHEL-16182) +- tpm2: add tpm2_get_capability_handle(), tpm2_esys_handle_from_tpm_handle() (RHEL-16182) +- tpm2: add tpm2_read_public() (RHEL-16182) +- tpm2: add tpm2_get_legacy_template() and tpm2_get_srk_template() (RHEL-16182) +- tpm2: add tpm2_load() (RHEL-16182) +- tpm2: add tpm2_load_external() (RHEL-16182) +- tpm2: move local vars in tpm2_seal() to point of use (RHEL-16182) +- tpm2: replace magic number in hmac_sensitive initialization (RHEL-16182) +- tpm2: add tpm2_create() (RHEL-16182) +- tpm2: replace tpm2_capability_pcrs() macro with direct c->capaiblity_pcrs use (RHEL-16182) +- basic/alloc-util: add greedy_realloc_append() (RHEL-16182) +- tpm2: cache the TPM supported commands, add tpm2_supports_command() (RHEL-16182) +- tpm2: cache TPM algorithms (RHEL-16182) +- tpm2: add tpm2_persist_handle() (RHEL-16182) +- tpm2: add tpm2_get_or_create_srk() (RHEL-16182) +- tpm2: move local vars in tpm2_unseal() to point of use (RHEL-16182) +- tpm2: remove tpm2_make_primary() (RHEL-16182) +- tpm2: use CreatePrimary() to create primary keys instead of Create() (RHEL-16182) +- cryptsetup: downgrade a bunch of log messages that to LOG_WARNING (RHEL-16182) +- boot/measure: replace TPM PolicyPCR session with calculation (RHEL-16182) +- core: imply DeviceAllow=/dev/tpmrm0 with LoadCredentialEncrypted (RHEL-16182) +- added more test cases (RHEL-16182) +- test: fixed negative checks in TEST-70-TPM2. Use in-line error handling rather than redirections. Follow up on #27020 (RHEL-16182) +- systemd-cryptenroll: add string aliases for tpm2 PCRs Fixes #26697. RFE. (RHEL-16182) +- cryptenroll: fix an assertion with weak passwords (RHEL-16182) +- man/systemd-cryptenroll: update list of PCRs, link to uapi docs (RHEL-16182) +- tpm2: add debug logging to functions converting hash or asym algs to/from strings or ids (RHEL-16182) +- tpm2: add tpm2_hash_alg_to_size() (RHEL-16182) +- tpm2: change tpm2_tpm*_pcr_selection_to_mask() to return mask (RHEL-16182) +- tpm2: add more helper functions for managing TPML_PCR_SELECTION and TPMS_PCR_SELECTION (RHEL-16182) +- tpm2: add Tpm2PCRValue struct and associated functions (RHEL-16182) +- tpm2: move declared functions in header lower down (RHEL-16182) +- tpm2: declare tpm2_log_debug_*() functions in tpm2_util.h (RHEL-16182) +- tpm2: change tpm2_calculate_policy_pcr(), tpm2_calculate_sealing_policy() to use Tpm2PCRValue array (RHEL-16182) +- tpm2: change tpm2_parse_pcr_argument() parameters to parse to Tpm2PCRValue array (RHEL-16182) +- tpm2: add TPM2B_*_MAKE(), TPM2B_*_CHECK_SIZE() macros (RHEL-16182) +- tpm2: add tpm2_pcr_read_missing_values() (RHEL-16182) +- openssl: add openssl_pkey_from_pem() (RHEL-16182) +- openssl: add rsa_pkey_new(), rsa_pkey_from_n_e(), rsa_pkey_to_n_e() (RHEL-16182) +- openssl: add ecc_pkey_new(), ecc_pkey_from_curve_x_y(), ecc_pkey_to_curve_x_y() (RHEL-16182) +- test: add DEFINE_HEX_PTR() helper function (RHEL-16182) +- openssl: add test-openssl (RHEL-16182) +- tpm2: add functions to convert TPM2B_PUBLIC to/from openssl pkey or PEM (RHEL-16182) +- tpm2: move policy calculation out of tpm2_seal() (RHEL-16182) +- man: update systemd-cryptenroll man page with details on --tpm2-pcrs format change (RHEL-16182) +- tpm2: update TEST-70-TPM2 to test passing PCR value to systemd-cryptenroll (RHEL-16182) +- tpm2: change *alg_to_* functions to use switch() (RHEL-16182) +- tpm2: lowercase TPM2_PCR_VALUE[S]_VALID functions (RHEL-16182) +- tpm2: move cast from lhs to rhs in uint16_t/int comparison (RHEL-16182) +- tpm2: in validator functions, return false instead of assert failure (RHEL-16182) +- tpm2: in tpm2_pcr_values_valid() use FOREACH_ARRAY() (RHEL-16182) +- tpm2: use SIZE_MAX instead of strlen() for unhexmem() (RHEL-16182) +- tpm2: put !isempty() check inside previous !isempty() check (RHEL-16182) +- tpm2: simplify call to asprintf() (RHEL-16182) +- tpm2: check pcr value hash != 0 before looking up hash algorithm name (RHEL-16182) +- tpm2: use strempty() (RHEL-16182) +- tpm2: split TPM2_PCR_VALUE_MAKE() over multiple lines (RHEL-16182) +- tpm2: remove ret_ prefix from input/output params (RHEL-16182) +- tpm2: use memcpy_safe() instead of memcpy() (RHEL-16182) +- openssl: use new(char, size) instead of malloc(size) (RHEL-16182) +- tpm2: use table for openssl<->tpm2 ecc curve id mappings (RHEL-16182) +- tpm2: use switch() instead of if-else (RHEL-16182) +- tpm2: make logging level consistent at debug for some functions (RHEL-16182) +- tpm2: remove unnecessary void* cast (RHEL-16182) +- tpm2: add tpm2_pcr_values_has_(any|all)_values() functions (RHEL-16182) +- tpm2: wrap (7) in UINT32_C() (RHEL-16182) +- cryptenroll: change man page example to remove leading 0x and lowercase hex (RHEL-16182) +- openssl: add log_openssl_errors() (RHEL-16182) +- openssl: add openssl_digest_size() (RHEL-16182) +- openssl: add openssl_digest_many() (RHEL-16182) +- openssl: replace openssl_hash() with openssl_digest() (RHEL-16182) +- openssl: add openssl_hmac_many() (RHEL-16182) +- openssl: add rsa_oaep_encrypt_bytes() (RHEL-16182) +- openssl: add kdf_kb_hmac_derive() (RHEL-16182) +- openssl: add openssl_cipher_many() (RHEL-16182) +- openssl: add ecc_edch() (RHEL-16182) +- openssl: add kdf_ss_derive() (RHEL-16182) +- dlfcn-util: add static asserts ensuring our sym_xyz() func ptrs match the types from the official headers (RHEL-16182) +- tpm2: add tpm2_marshal_blob() and tpm2_unmarshal_blob() (RHEL-16182) +- tpm2: add tpm2_serialize() and tpm2_deserialize() (RHEL-16182) +- tpm2: add tpm2_index_to_handle() and tpm2_index_from_handle() (RHEL-16182) +- tpm2: fix build failure without openssl (RHEL-16182) +- tpm2-util: look for tpm2-pcr-signature.json directly in /.extra/ (RHEL-16182) +- tpm2: downgrade most log functions from error to debug (RHEL-16182) +- tpm2: handle older tpm enrollments without a saved pcr bank (RHEL-16182) +- tpm2: allow tpm2_make_encryption_session() without bind key (RHEL-16182) +- tpm2: update tpm2 test for supported commands (RHEL-16182) +- tpm2: use GREEDY_REALLOC_APPEND() in tpm2_get_capability_handles(), cap max value (RHEL-16182) +- tpm2: change tpm2_unseal() to accept Tpm2Context instead of device string (RHEL-16182) +- tpm2: cache TPM's supported ECC curves (RHEL-16182) +- tpm2-util: make tpm2_marshal_blob()/tpm2_unmarshal_blob() static (RHEL-16182) +- tpm2-util: make tpm2_read_public() static, as we use it only internally in tpm2-util.c (RHEL-16182) +- cryptenroll: allow specifying handle index of key to use for sealing (RHEL-16182) +- test: add tests for systemd-cryptenroll --tpm2-seal-key-handle (RHEL-16182) +- tpm2: do not call Esys_TR_Close() (RHEL-16182) +- tpm2: don't use GetCapability() to check transient handles (RHEL-16182) +- tpm2-util: pick up a few new symbols from tpm2-tss (RHEL-16182) +- tpm2: add tpm2_get_pin_auth() (RHEL-16182) +- tpm2: instead of adjusting authValue trailing 0(s), trim them as required by tpm spec (RHEL-16182) +- tpm2-util: rename tpm2_calculate_name() → tpm2_calculate_pubkey_name() (RHEL-16182) +- cryptenroll: do not implicitly verify with default tpm policy signature (RHEL-16182) +- cryptenroll: drop deadcode (RHEL-16182) +- tpm2: allow using tpm2_get_srk_template() without tpm (RHEL-16182) +- tpm2: add test to verify srk templates (RHEL-16182) +- tpm2: add tpm2_sym_alg_*_string() and tpm2_sym_mode_*_string() (RHEL-16182) +- tpm2: add tpm2_calculate_seal() and helper functions (RHEL-16182) +- tpm2: update test-tpm2 for tpm2_calculate_seal() (RHEL-16182) +- cryptenroll: add support for calculated TPM2 enrollment (RHEL-16182) +- test: update TEST-70 with systemd-cryptenroll calculated TPM2 enrollment (RHEL-16182) +- openssl-util: avoid freeing invalid pointer (RHEL-16182) +- creds-util: check for CAP_DAC_READ_SEARCH (RHEL-16182) +- creds-util: do not try TPM2 if there is not support (RHEL-16182) +- creds-util: merge the TPM2 detection for initrd (RHEL-16182) +- cryptenroll: fix a memory leak (RHEL-16182) +- sd-journal: introduce sd_journal_step_one() (RHEL-11591) +- test: modernize test-journal-flush (RHEL-11591) +- journal-file-util: do not fail when journal_file_set_offline() called more than once (RHEL-11591) +- journal-file-util: Prefer punching holes instead of truncating (RHEL-11591) +- test: add reproducer for SIGBUS issue caused by journal truncation (RHEL-11591) + +* Wed Jan 31 2024 systemd maintenance team - 252-26 +- spec: update rhel-net-naming-sysattrs to v0.4 (RHEL-22278) + +* Tue Jan 30 2024 systemd maintenance team - 252-25 +- spec: add new package with RHEL-specific network naming sysattrs (RHEL-22278) + +* Wed Jan 24 2024 systemd maintenance team - 252-24 +- ci: use source-git-automation composite Action (RHEL-1086) +- ci: increase the cron interval to 45 minutes (RHEL-1086) +- ci: add all Z-Stream versions to array of allowed versions (RHEL-1086) +- udev/net_id: introduce naming scheme for RHEL-9.4 (RHEL-22427) +- basic/errno-util: add wrappers which only accept negative errno (RHEL-22443) +- errno-util: allow ERRNO_IS_* to accept types wider than int (RHEL-22443) +- udev: add new builtin net_driver (RHEL-22443) +- udev/net_id: introduce naming scheme for RHEL-8.10 (RHEL-22427) + +* Fri Jan 12 2024 systemd maintenance team - 252-23 +- logind: don't setup idle session watch for lock-screen and greeter (RHEL-20757) +- logind: don't make idle action timer accuracy more coarse than timeout (RHEL-20757) +- logind: do TTY idle logic only for sessions marked as "tty" (RHEL-20757) +- meson: Properly install 90-uki-copy.install (RHEL-16354) + +* Mon Jan 08 2024 systemd maintenance team - 252-22 +- Revert "man: mention System Administrator's Guide in systemctl manpage" (RHEL-19436) +- man: mention RHEL documentation in systemctl's man page (RHEL-19436) +- resolved: actually check authenticated flag of SOA transaction (RHEL-6216) +- udev: allow/denylist for reading sysfs attributes when composing a NIC name (RHEL-1317) +- man: environment value -> udev property (RHEL-1317) + +* Mon Dec 11 2023 systemd maintenance team - 252-21 +- meson: fix installation of ukify (RHEL-13199) +- sd-id128: introduce id128_hash_ops_free (RHEL-5988) +- udevadm-trigger: allow to fallback without synthetic UUID only first time (RHEL-5988) +- udevadm-trigger: settle with synthetic UUID if the kernel support it (RHEL-5988) +- udevadm-trigger: also check with the original syspath if device is renamed (RHEL-5988) +- test: use 'udevadm trigger --settle' even if device is renamed (RHEL-5988) +- sd-event: don't mistake USEC_INFINITY passed in for overflow (RHEL-6090) +- pid1: rework service_arm_timer() to optionally take a relative time value (RHEL-6090) +- manager: add one more assert() (RHEL-6090) +- pid1: add new Type=notify-reload service type (RHEL-6090) +- man: document Type=notify-reload (RHEL-6090) +- pid1: make sure we send our calling service manager RELOADING=1 when reloading (RHEL-6090) +- networkd: implement Type=notify-reload protocol (RHEL-6090) +- udevd: implement the full Type=notify-reload protocol (RHEL-6090) +- logind: implement Type=notify-reload protocol properly (RHEL-6090) +- notify: add --stopping + --reloading switches (RHEL-6090) +- test: add Type=notify-reload testcase (RHEL-6090) +- update TODO (RHEL-6090) +- core: check for SERVICE_RELOAD_NOTIFY in manager_dbus_is_running (RHEL-6090) + +* Fri Dec 08 2023 systemd maintenance team - 252-20 +- udev/net: allow new link name as an altname before renaming happens (RHEL-5988) +- sd-netlink: do not swap old name and alternative name (RHEL-5988) +- sd-netlink: restore altname on error in rtnl_set_link_name (RHEL-5988) +- udev: attempt device rename even if interface is up (RHEL-5988) +- sd-netlink: add a test for rtnl_set_link_name() (RHEL-5988) +- test-network: add a test for renaming device to current altname (RHEL-5988) +- udev: align table (RHEL-5988) +- sd-device: make device_set_syspath() clear sysname and sysnum (RHEL-5988) +- sd-device: do not directly access entry in sd-device object (RHEL-5988) +- udev: move device_rename() from device-private.c (RHEL-5988) +- udev: restore syspath and properties on failure (RHEL-5988) +- sd-device: introduce device_get_property_int() (RHEL-5988) +- core/device: downgrade log level for ignored errors (RHEL-5988) +- core/device: ignore failed uevents (RHEL-5988) +- test: add tests for failure in renaming network interface (RHEL-5988) +- test: modernize test-netlink.c (RHEL-5988) +- test-netlink: use dummy interface to test assigning new interface name (RHEL-5988) +- udev: use SYNTHETIC_ERRNO() at one more place (RHEL-5988) +- udev: make udev_builtin_run() take UdevEvent* (RHEL-5988) +- udev/net: verify ID_NET_XYZ before trying to assign it as an alternative name (RHEL-5988) +- udev/net: generate new network interface name only on add uevent (RHEL-5988) +- sd-netlink: make rtnl_set_link_name() optionally append alternative names (RHEL-5988) +- udev/net: assign alternative names only on add uevent (RHEL-5988) +- test: add tests for renaming network interface (RHEL-5988) +- Backport ukify from upstream (RHEL-13199) +- bootctl: make --json output normal json (RHEL-13199) +- test: replace readfp() with read_file() (RHEL-13199) +- stub/measure: document and measure .uname UKI section (RHEL-13199) +- boot: measure .sbat section (RHEL-13199) +- Revert "test_ukify: no stinky root needed for signing" (RHEL-13199) +- ukify: move to /usr/bin and mark as non non-experimental (RHEL-13199) +- kernel-install: Add uki layout (RHEL-16354) +- kernel-install: remove math slang from man page (RHEL-16354) +- kernel-install: handle uki installs automatically (RHEL-16354) +- 90-uki-copy.install: create $BOOT/EFI/Linux directory if needed (RHEL-16354) +- kernel-install: Log location that uki is installed in (RHEL-16354) +- bootctl: fix errno logging (RHEL-16354) +- bootctl: add kernel-identity command (RHEL-16354) +- bootctl: add kernel-inspect command (RHEL-16354) +- bootctl: add kernel-inspect to --help text (RHEL-16354) +- bootctl: drop full stop at end of --help texts (RHEL-16354) +- bootctl: change section title for kernel image commands (RHEL-16354) +- bootctl: remove space that should not be there (RHEL-16354) +- bootctl: kernel-inspect: print os info (RHEL-16354) +- bootctl-uki: several coding style fixlets (RHEL-16354) +- tree-wide: unify how we pick OS pretty name to display (RHEL-16354) +- bootctl-uki: several follow-ups for inspect_osrel() (RHEL-16354) +- bootctl: Add missing %m (RHEL-16354) +- bootctl: tweak DOS header magic check (RHEL-16354) + +* Mon Nov 13 2023 systemd maintenance team - 252-19 +- ci: Extend source-git-automation (RHEL-1086) +- netif-naming-scheme: let's also include rhel8 schemes (RHEL-7026) +- systemd-analyze: Add table and JSON output implementation to plot (RHEL-5070) +- systemd-analyze: Update man/systemd-analyze.xml with Plot JSON and table (RHEL-5070) +- systemd-analyze: Add tab complete logic for plot (RHEL-5070) +- systemd-analyze: Add --json=, --table and -no-legend tests for plot (RHEL-5070) +- ci: enable source-git automation to validate reviews and ci results (RHEL-1086) +- ci: remove Mergify config - replaced by Pull Request Validator (RHEL-1086) +- ci: enable auto-merge GH Action (RHEL-1086) +- ci: add missing permissions (RHEL-1086) +- ci: `permissions: write-all` (RHEL-1086) +- ci(lint): exclude `.in` files from ShellCheck lint (RHEL-1086) +- udev: raise RLIMIT_NOFILE as high as we can (RHEL-11040) + * Tue Aug 22 2023 systemd maintenance team - 252-18 - doc: add downstream CONTRIBUTING document (#2170883) - doc: improve CONTRIBUTING document (#2170883)