public inbox for u-boot@lists.denx.de
 help / color / mirror / Atom feed
* [PATCH v4 0/6] UEFI Capsule - PKCS11 Support
@ 2026-01-20  8:11 Wojciech Dubowik
  2026-01-20  8:11 ` [PATCH v4 1/6] tools: mkeficapsule: Add support for pkcs11 Wojciech Dubowik
                   ` (5 more replies)
  0 siblings, 6 replies; 20+ messages in thread
From: Wojciech Dubowik @ 2026-01-20  8:11 UTC (permalink / raw)
  To: u-boot; +Cc: Wojciech Dubowik, trini, simon.glass, quentin.schulz

Add support for pkcs11 URI's when generating UEFI capsules and
accept URI's for certificate in dts capsule nodes.
Example:
export PKCS11_MODULE_PATH=<pkcs11 provider path>/libsofthsm2.so
tools/mkeficapsule --monotonic-count 1 \
 --private-key "pkcs11:token=EX;object=capsule;type=private;pin-source=pin.txt" \
 --certificate "pkcs11:token=EX;object=capsule;type=cert;pin-source=pin.txt" \
 --index 1 \
 --guid XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX \
 "capsule-payload" \
 "capsule.cap
Signed-off-by: Wojciech Dubowik <Wojciech.Dubowik@mt.com>
---
Changes in v4:
* adapt mkeficapsule python support to dump detached signature
  for authenticated capsules
* verify detached capsule signature with openssl after generation
* use p11-kit to figure out location of softhsm2 library
* fix missing long option for dumping signatures in mkeficapsule
Changes in v3:
* fix write file encoding, env setting and extra line in binman test
  after review
Changes in v2:
* allow mixed file/pkcs11 URI as key specification in mkeficapsule
* fix logic for accepting pkcs11 URI in binman device tree sections
* add binman test for UEFI capsule signature where private key comes
  from softHSM
---
Wojciech Dubowik (6):
  tools: mkeficapsule: Add support for pkcs11
  binman: Accept pkcs11 URI tokens for capsule updates
  tools: Fix long option --dump_sig in mkeficapsule
  binman: Add dump signarture option to mkeficapsule
  binman: DTS: Add dump-signature option for capsules
  test: binman: Add test for pkcs11 signed capsule

 tools/binman/btool/mkeficapsule.py            |   6 +-
 tools/binman/entries.rst                      |   2 +
 tools/binman/etype/efi_capsule.py             |  13 +-
 tools/binman/ftest.py                         |  53 +++++++++
 .../binman/test/351_capsule_signed_pkcs11.dts |  22 ++++
 tools/mkeficapsule.c                          | 111 ++++++++++++++----
 6 files changed, 177 insertions(+), 30 deletions(-)
 create mode 100644 tools/binman/test/351_capsule_signed_pkcs11.dts

-- 
2.47.3


^ permalink raw reply	[flat|nested] 20+ messages in thread

* [PATCH v4 1/6] tools: mkeficapsule: Add support for pkcs11
  2026-01-20  8:11 [PATCH v4 0/6] UEFI Capsule - PKCS11 Support Wojciech Dubowik
@ 2026-01-20  8:11 ` Wojciech Dubowik
  2026-01-20  8:11 ` [PATCH v4 2/6] binman: Accept pkcs11 URI tokens for capsule updates Wojciech Dubowik
                   ` (4 subsequent siblings)
  5 siblings, 0 replies; 20+ messages in thread
From: Wojciech Dubowik @ 2026-01-20  8:11 UTC (permalink / raw)
  To: u-boot; +Cc: Wojciech Dubowik, trini, simon.glass, quentin.schulz

With pkcs11 support it's now possible to specify keys
with URI format. To use this feature the filename must
begin "pkcs11:.." and have valid URI pointing to certificate
and private key in HSM.

The environment variable PKCS11_MODULE_PATH must point to the
right pkcs11 provider i.e. with softhsm:
export PKCS11_MODULE_PATH=<path>/libsofthsm2.so

Example command line:
tools/mkeficapsule --monotonic-count 1 \
 --private-key "pkcs11:token=EX;object=capsule;type=private;pin-source=pin.txt" \
 --certificate "pkcs11:token=EX;object=capsule;type=cert;pin-source=pin.txt" \
 --index 1 \
 --guid XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXX \
 "capsule-payload" \
 "capsule.cap"

Signed-off-by: Wojciech Dubowik <Wojciech.Dubowik@mt.com>
---
 tools/mkeficapsule.c | 110 +++++++++++++++++++++++++++++++++----------
 1 file changed, 84 insertions(+), 26 deletions(-)

diff --git a/tools/mkeficapsule.c b/tools/mkeficapsule.c
index 0f41cdb64f54..a61557b73bef 100644
--- a/tools/mkeficapsule.c
+++ b/tools/mkeficapsule.c
@@ -228,21 +228,54 @@ static int create_auth_data(struct auth_context *ctx)
 	gnutls_pkcs7_t pkcs7;
 	gnutls_datum_t data;
 	gnutls_datum_t signature;
+	gnutls_pkcs11_obj_t *obj_list;
+	unsigned int obj_list_size = 0;
+	const char *lib;
 	int ret;
+	bool pkcs11_cert = false;
+	bool pkcs11_key = false;
 
-	ret = read_bin_file(ctx->cert_file, &cert.data, &file_size);
-	if (ret < 0)
-		return -1;
-	if (file_size > UINT_MAX)
-		return -1;
-	cert.size = file_size;
+	if (!strncmp(ctx->cert_file, "pkcs11:", 7))
+		pkcs11_cert = true;
 
-	ret = read_bin_file(ctx->key_file, &key.data, &file_size);
-	if (ret < 0)
-		return -1;
-	if (file_size > UINT_MAX)
-		return -1;
-	key.size = file_size;
+	if (!strncmp(ctx->key_file, "pkcs11:", 7))
+		pkcs11_key = true;
+
+	if (pkcs11_cert || pkcs11_key) {
+		lib = getenv("PKCS11_MODULE_PATH");
+		if (!lib) {
+			fprintf(stdout,
+				"PKCS11_MODULE_PATH not set in the environment\n");
+			return -1;
+		}
+
+		gnutls_pkcs11_init(GNUTLS_PKCS11_FLAG_MANUAL, NULL);
+		gnutls_global_init();
+
+		ret = gnutls_pkcs11_add_provider(lib, "trusted");
+		if (ret < 0) {
+			fprintf(stdout, "Failed to add pkcs11 provider\n");
+			return -1;
+		}
+	}
+
+	if (!pkcs11_cert) {
+		ret = read_bin_file(ctx->cert_file, &cert.data, &file_size);
+		if (ret < 0)
+			return -1;
+		if (file_size > UINT_MAX)
+			return -1;
+		cert.size = file_size;
+	}
+
+	if (!pkcs11_key) {
+		ret = read_bin_file(ctx->key_file, &key.data, &file_size);
+		if (ret < 0)
+			return -1;
+		if (file_size > UINT_MAX)
+			return -1;
+		key.size = file_size;
+	}
 
 	/*
 	 * For debugging,
@@ -265,22 +298,42 @@ static int create_auth_data(struct auth_context *ctx)
 		return -1;
 	}
 
-	/* load a private key */
-	ret = gnutls_privkey_import_x509_raw(pkey, &key, GNUTLS_X509_FMT_PEM,
-					     0, 0);
-	if (ret < 0) {
-		fprintf(stderr,
-			"error in gnutls_privkey_import_x509_raw(): %s\n",
-			gnutls_strerror(ret));
-		return -1;
+	/* load x509 certificate */
+	if (pkcs11_cert) {
+		ret = gnutls_pkcs11_obj_list_import_url4(&obj_list, &obj_list_size,
+							 ctx->cert_file, 0);
+		if (ret < 0 || obj_list_size == 0) {
+			fprintf(stdout, "Failed to import crt_file URI objects\n");
+			return -1;
+		}
+
+		gnutls_x509_crt_import_pkcs11(x509, obj_list[0]);
+	} else {
+		ret = gnutls_x509_crt_import(x509, &cert, GNUTLS_X509_FMT_PEM);
+		if (ret < 0) {
+			fprintf(stderr, "error in gnutls_x509_crt_import(): %s\n",
+				gnutls_strerror(ret));
+			return -1;
+		}
 	}
 
-	/* load x509 certificate */
-	ret = gnutls_x509_crt_import(x509, &cert, GNUTLS_X509_FMT_PEM);
-	if (ret < 0) {
-		fprintf(stderr, "error in gnutls_x509_crt_import(): %s\n",
-			gnutls_strerror(ret));
-		return -1;
+	/* load a private key */
+	if (pkcs11_key) {
+		ret = gnutls_privkey_import_pkcs11_url(pkey, ctx->key_file);
+		if (ret < 0) {
+			fprintf(stderr, "error in %d: %s\n", __LINE__,
+				gnutls_strerror(ret));
+			return -1;
+		}
+	} else {
+		ret = gnutls_privkey_import_x509_raw(pkey, &key, GNUTLS_X509_FMT_PEM,
+						     0, 0);
+		if (ret < 0) {
+			fprintf(stderr,
+				"error in gnutls_privkey_import_x509_raw(): %s\n",
+				gnutls_strerror(ret));
+			return -1;
+		}
 	}
 
 	/* generate a PKCS #7 structure */
@@ -349,6 +402,11 @@ static int create_auth_data(struct auth_context *ctx)
 	 *   gnutls_free(signature.data);
 	 */
 
+	if (pkcs11_cert || pkcs11_key) {
+		gnutls_global_deinit();
+		gnutls_pkcs11_deinit();
+	}
+
 	return 0;
 }
 
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v4 2/6] binman: Accept pkcs11 URI tokens for capsule updates
  2026-01-20  8:11 [PATCH v4 0/6] UEFI Capsule - PKCS11 Support Wojciech Dubowik
  2026-01-20  8:11 ` [PATCH v4 1/6] tools: mkeficapsule: Add support for pkcs11 Wojciech Dubowik
@ 2026-01-20  8:11 ` Wojciech Dubowik
  2026-01-20  8:12 ` [PATCH v4 3/6] tools: Fix long option --dump_sig in mkeficapsule Wojciech Dubowik
                   ` (3 subsequent siblings)
  5 siblings, 0 replies; 20+ messages in thread
From: Wojciech Dubowik @ 2026-01-20  8:11 UTC (permalink / raw)
  To: u-boot; +Cc: Wojciech Dubowik, trini, simon.glass, quentin.schulz

With pkcs11 support in mkeficapsule we can now accept URI
tokens and not only files.

Signed-off-by: Wojciech Dubowik <Wojciech.Dubowik@mt.com>
Reviewed-by: Simon Glass <simon.glass@canonical.com>
---
 tools/binman/etype/efi_capsule.py | 8 ++++++--
 1 file changed, 6 insertions(+), 2 deletions(-)

diff --git a/tools/binman/etype/efi_capsule.py b/tools/binman/etype/efi_capsule.py
index 9f06cc88e6e5..3b30c12ea514 100644
--- a/tools/binman/etype/efi_capsule.py
+++ b/tools/binman/etype/efi_capsule.py
@@ -125,10 +125,14 @@ class Entry_efi_capsule(Entry_section):
         private_key = ''
         public_key_cert = ''
         if self.auth:
-            if not os.path.isabs(self.private_key):
+            if not os.path.isabs(self.private_key) and not 'pkcs11:' in self.private_key:
                 private_key =  tools.get_input_filename(self.private_key)
-            if not os.path.isabs(self.public_key_cert):
+            if not os.path.isabs(self.public_key_cert) and not 'pkcs11:' in self.public_key_cert:
                 public_key_cert = tools.get_input_filename(self.public_key_cert)
+            if 'pkcs11:' in self.private_key:
+                private_key = self.private_key
+            if 'pkcs11:' in self.public_key_cert:
+                public_key_cert = self.public_key_cert
         data, payload, uniq = self.collect_contents_to_file(
             self._entries.values(), 'capsule_in')
         outfile = self._filename if self._filename else 'capsule.%s' % uniq
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v4 3/6] tools: Fix long option --dump_sig in mkeficapsule
  2026-01-20  8:11 [PATCH v4 0/6] UEFI Capsule - PKCS11 Support Wojciech Dubowik
  2026-01-20  8:11 ` [PATCH v4 1/6] tools: mkeficapsule: Add support for pkcs11 Wojciech Dubowik
  2026-01-20  8:11 ` [PATCH v4 2/6] binman: Accept pkcs11 URI tokens for capsule updates Wojciech Dubowik
@ 2026-01-20  8:12 ` Wojciech Dubowik
  2026-01-20 15:11   ` Quentin Schulz
  2026-01-20  8:12 ` [PATCH v4 4/6] binman: Add dump signarture option to mkeficapsule Wojciech Dubowik
                   ` (2 subsequent siblings)
  5 siblings, 1 reply; 20+ messages in thread
From: Wojciech Dubowik @ 2026-01-20  8:12 UTC (permalink / raw)
  To: u-boot; +Cc: Wojciech Dubowik, trini, simon.glass, quentin.schulz

Only short option has been present.

Signed-off-by: Wojciech Dubowik <Wojciech.Dubowik@mt.com>
---
 tools/mkeficapsule.c | 1 +
 1 file changed, 1 insertion(+)

diff --git a/tools/mkeficapsule.c b/tools/mkeficapsule.c
index a61557b73bef..a89f11713d67 100644
--- a/tools/mkeficapsule.c
+++ b/tools/mkeficapsule.c
@@ -56,6 +56,7 @@ static struct option options[] = {
 	{"fw-revert", no_argument, NULL, 'R'},
 	{"capoemflag", required_argument, NULL, 'o'},
 	{"dump-capsule", no_argument, NULL, 'D'},
+	{"dump_sig", no_argument, NULL, 'd'},
 	{"help", no_argument, NULL, 'h'},
 	{NULL, 0, NULL, 0},
 };
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v4 4/6] binman: Add dump signarture option to mkeficapsule
  2026-01-20  8:11 [PATCH v4 0/6] UEFI Capsule - PKCS11 Support Wojciech Dubowik
                   ` (2 preceding siblings ...)
  2026-01-20  8:12 ` [PATCH v4 3/6] tools: Fix long option --dump_sig in mkeficapsule Wojciech Dubowik
@ 2026-01-20  8:12 ` Wojciech Dubowik
  2026-01-20 15:06   ` Quentin Schulz
  2026-01-20  8:12 ` [PATCH v4 5/6] binman: DTS: Add dump-signature option for capsules Wojciech Dubowik
  2026-01-20  8:12 ` [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule Wojciech Dubowik
  5 siblings, 1 reply; 20+ messages in thread
From: Wojciech Dubowik @ 2026-01-20  8:12 UTC (permalink / raw)
  To: u-boot; +Cc: Wojciech Dubowik, trini, simon.glass, quentin.schulz

It will be used to capsule signature verification.

Signed-off-by: Wojciech Dubowik <Wojciech.Dubowik@mt.com>
---
 tools/binman/btool/mkeficapsule.py | 6 +++++-
 1 file changed, 5 insertions(+), 1 deletion(-)

diff --git a/tools/binman/btool/mkeficapsule.py b/tools/binman/btool/mkeficapsule.py
index f7e5a8868495..f4b594b3faef 100644
--- a/tools/binman/btool/mkeficapsule.py
+++ b/tools/binman/btool/mkeficapsule.py
@@ -38,7 +38,8 @@ class Bintoolmkeficapsule(bintool.Bintool):
 
     def generate_capsule(self, image_index, image_guid, hardware_instance,
                          payload, output_fname, priv_key, pub_key,
-                         monotonic_count=0, version=0, oemflags=0):
+                         monotonic_count=0, version=0, oemflags=0,
+                         dump_sig=0):
         """Generate a capsule through commandline-provided parameters
 
         Args:
@@ -53,6 +54,7 @@ class Bintoolmkeficapsule(bintool.Bintool):
             monotonic_count (int): Count used when signing an image
             version (int): Image version (Optional)
             oemflags (int): Optional 16 bit OEM flags
+            dump_sig (int): Dump signature to a file (Optional)
 
         Returns:
             str: Tool output
@@ -73,6 +75,8 @@ class Bintoolmkeficapsule(bintool.Bintool):
                 f'--private-key={priv_key}',
                 f'--certificate={pub_key}'
             ]
+        if dump_sig:
+            args += [f'--dump_sig']
 
         args += [
             payload,
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v4 5/6] binman: DTS: Add dump-signature option for capsules
  2026-01-20  8:11 [PATCH v4 0/6] UEFI Capsule - PKCS11 Support Wojciech Dubowik
                   ` (3 preceding siblings ...)
  2026-01-20  8:12 ` [PATCH v4 4/6] binman: Add dump signarture option to mkeficapsule Wojciech Dubowik
@ 2026-01-20  8:12 ` Wojciech Dubowik
  2026-01-20 15:02   ` Quentin Schulz
  2026-01-20  8:12 ` [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule Wojciech Dubowik
  5 siblings, 1 reply; 20+ messages in thread
From: Wojciech Dubowik @ 2026-01-20  8:12 UTC (permalink / raw)
  To: u-boot; +Cc: Wojciech Dubowik, trini, simon.glass, quentin.schulz

Mkeficapsule can dump signature for signed capsules. It can
be used in test to validate signature i.e. with openssl.
Add an entry for device tree node.

Signed-off-by: Wojciech Dubowik <Wojciech.Dubowik@mt.com>
---
 tools/binman/entries.rst          | 2 ++
 tools/binman/etype/efi_capsule.py | 5 ++++-
 2 files changed, 6 insertions(+), 1 deletion(-)

diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst
index a81fcbd3891f..1dace2087a2a 100644
--- a/tools/binman/entries.rst
+++ b/tools/binman/entries.rst
@@ -552,6 +552,8 @@ Properties / Entry arguments:
     - public-key-cert: Path to PEM formatted .crt public key certificate
       file. Mandatory property for generating signed capsules.
     - oem-flags - OEM flags to be passed through capsule header.
+    - dump-signature: Instruct mkeficapsule to write signature data to
+      a separete file. It might be used to verify capsule authentication.
 
 Since this is a subclass of Entry_section, all properties of the parent
 class also apply here. Except for the properties stated as mandatory, the
diff --git a/tools/binman/etype/efi_capsule.py b/tools/binman/etype/efi_capsule.py
index 3b30c12ea514..01d56723b98c 100644
--- a/tools/binman/etype/efi_capsule.py
+++ b/tools/binman/etype/efi_capsule.py
@@ -101,6 +101,7 @@ class Entry_efi_capsule(Entry_section):
         self.private_key = ''
         self.public_key_cert = ''
         self.auth = 0
+        self.dump_signature = False
 
     def ReadNode(self):
         super().ReadNode()
@@ -111,6 +112,7 @@ class Entry_efi_capsule(Entry_section):
         self.hardware_instance = fdt_util.GetInt(self._node, 'hardware-instance')
         self.monotonic_count = fdt_util.GetInt(self._node, 'monotonic-count')
         self.oem_flags = fdt_util.GetInt(self._node, 'oem-flags')
+        self.dump_signature = fdt_util.GetBool(self._node, 'dump-signature')
 
         self.private_key = fdt_util.GetString(self._node, 'private-key')
         self.public_key_cert = fdt_util.GetString(self._node, 'public-key-cert')
@@ -150,7 +152,8 @@ class Entry_efi_capsule(Entry_section):
                                                  public_key_cert,
                                                  self.monotonic_count,
                                                  self.fw_version,
-                                                 self.oem_flags)
+                                                 self.oem_flags,
+                                                 self.dump_signature)
         if ret is not None:
             return tools.read_file(capsule_fname)
         else:
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule
  2026-01-20  8:11 [PATCH v4 0/6] UEFI Capsule - PKCS11 Support Wojciech Dubowik
                   ` (4 preceding siblings ...)
  2026-01-20  8:12 ` [PATCH v4 5/6] binman: DTS: Add dump-signature option for capsules Wojciech Dubowik
@ 2026-01-20  8:12 ` Wojciech Dubowik
  2026-01-20 15:53   ` Quentin Schulz
  5 siblings, 1 reply; 20+ messages in thread
From: Wojciech Dubowik @ 2026-01-20  8:12 UTC (permalink / raw)
  To: u-boot; +Cc: Wojciech Dubowik, trini, simon.glass, quentin.schulz

Test pkcs11 URI support for UEFI capsule generation. For
simplicity only private key is defined in binman section
as softhsm tool doesn't support certificate import (yet).

Signed-off-by: Wojciech Dubowik <Wojciech.Dubowik@mt.com>
Reviewed-by: Simon Glass <simon.glass@canonical.com>
---
 tools/binman/ftest.py                         | 53 +++++++++++++++++++
 .../binman/test/351_capsule_signed_pkcs11.dts | 22 ++++++++
 2 files changed, 75 insertions(+)
 create mode 100644 tools/binman/test/351_capsule_signed_pkcs11.dts

diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
index 21ec48d86fd1..a005a167e414 100644
--- a/tools/binman/ftest.py
+++ b/tools/binman/ftest.py
@@ -7,6 +7,7 @@
 #    python -m unittest func_test.TestFunctional.testHelp
 
 import collections
+import configparser
 import glob
 import gzip
 import hashlib
@@ -7532,6 +7533,58 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
 
         self._CheckCapsule(data, signed_capsule=True)
 
+    def testPkcs11SignedCapsuleGen(self):
+        """Test generation of EFI capsule (with PKCS11)"""
+        data = tools.read_file(self.TestFile("key.key"))
+        private_key = self._MakeInputFile("key.key", data)
+        data = tools.read_file(self.TestFile("key.pem"))
+        cert_file = self._MakeInputFile("key.crt", data)
+
+        softhsm2_util = bintool.Bintool.create('softhsm2_util')
+        self._CheckBintool(softhsm2_util)
+
+        prefix = "testPkcs11SignedCapsuleGen."
+        # Configure SoftHSMv2
+        data = tools.read_file(self.TestFile('340_softhsm2.conf'))
+        softhsm2_conf = self._MakeInputFile(f'{prefix}softhsm2.conf', data)
+        softhsm2_tokens_dir = self._MakeInputDir(f'{prefix}softhsm2.tokens')
+        tools.write_file(softhsm2_conf, data +
+                         f'\ndirectories.tokendir = \
+                         {softhsm2_tokens_dir}\n'.encode("utf-8"))
+
+        p11_kit_config = configparser.ConfigParser()
+        out = tools.run('p11-kit', 'print-config')
+        p11_kit_config.read_string(out)
+        softhsm2_lib = p11_kit_config['softhsm2']['module']
+
+        os.environ['SOFTHSM2_CONF'] = softhsm2_conf
+        tools.run('softhsm2-util', '--init-token', '--free', '--label',
+                  'U-Boot token', '--pin', '1111', '--so-pin',
+                  '222222')
+        tools.run('softhsm2-util', '--import', private_key, '--token',
+                  'U-Boot token', '--label', 'test_key', '--id', '999999',
+                  '--pin', '1111')
+
+        os.environ['PKCS11_MODULE_PATH'] = softhsm2_lib
+        data = self._DoReadFile('351_capsule_signed_pkcs11.dts')
+
+        self._CheckCapsule(data, signed_capsule=True)
+
+        # Verify signed capsule
+        hdr = self._GetCapsuleHeaders(data)
+        monotonic_count = hdr['EFI_FIRMWARE_IMAGE_AUTH.MONOTONIC_COUNT']
+
+        with open(self._indir + '/capsule_input.bin', 'ab') as f:
+            f.write(struct.pack('<Q', int(monotonic_count, 16)))
+
+        try:
+            tools.run('openssl', 'smime', '-verify', '-inform', 'DER',
+                      '-in', tools.get_output_dir() + '/capsule.efi-capsule.p7',
+                      '-content', self._indir + '/capsule_input.bin',
+                      '-CAfile', cert_file, '-no_check_time')
+        except ValueError:
+            self.assertIn('UEFI Capsule verification failed')
+
     def testCapsuleGenVersionSupport(self):
         """Test generation of EFI capsule with version support"""
         data = self._DoReadFile('313_capsule_version.dts')
diff --git a/tools/binman/test/351_capsule_signed_pkcs11.dts b/tools/binman/test/351_capsule_signed_pkcs11.dts
new file mode 100644
index 000000000000..ae93bf83936f
--- /dev/null
+++ b/tools/binman/test/351_capsule_signed_pkcs11.dts
@@ -0,0 +1,22 @@
+// SPDX-License-Identifier: GPL-2.0+
+
+/dts-v1/;
+
+/ {
+	binman {
+		efi-capsule {
+			image-index = <0x1>;
+			/* Image GUID for testing capsule update */
+			image-guid = "binman-test";
+			hardware-instance = <0x0>;
+			monotonic-count = <0x1>;
+			dump-signature;
+			private-key = "pkcs11:token=U-Boot%20token;object=test_key;type=private;pin-value=1111";
+			public-key-cert = "key.crt";
+
+			blob {
+				filename = "capsule_input.bin";
+			};
+		};
+	};
+};
-- 
2.47.3


^ permalink raw reply related	[flat|nested] 20+ messages in thread

* Re: [PATCH v4 5/6] binman: DTS: Add dump-signature option for capsules
  2026-01-20  8:12 ` [PATCH v4 5/6] binman: DTS: Add dump-signature option for capsules Wojciech Dubowik
@ 2026-01-20 15:02   ` Quentin Schulz
  0 siblings, 0 replies; 20+ messages in thread
From: Quentin Schulz @ 2026-01-20 15:02 UTC (permalink / raw)
  To: Wojciech Dubowik, u-boot; +Cc: trini, simon.glass

Hi Wojciech,

On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
> Mkeficapsule can dump signature for signed capsules. It can
> be used in test to validate signature i.e. with openssl.
> Add an entry for device tree node.
> 
> Signed-off-by: Wojciech Dubowik <Wojciech.Dubowik@mt.com>
> ---
>   tools/binman/entries.rst          | 2 ++
>   tools/binman/etype/efi_capsule.py | 5 ++++-
>   2 files changed, 6 insertions(+), 1 deletion(-)
> 
> diff --git a/tools/binman/entries.rst b/tools/binman/entries.rst
> index a81fcbd3891f..1dace2087a2a 100644
> --- a/tools/binman/entries.rst
> +++ b/tools/binman/entries.rst
> @@ -552,6 +552,8 @@ Properties / Entry arguments:
>       - public-key-cert: Path to PEM formatted .crt public key certificate
>         file. Mandatory property for generating signed capsules.
>       - oem-flags - OEM flags to be passed through capsule header.
> +    - dump-signature: Instruct mkeficapsule to write signature data to

S/Instruct/Optional boolean (default: false). Instruct

> +      a separete file. It might be used to verify capsule authentication.
>   

s/separete/separate/

Can we predict where the file will be located and how it'll be named?

>   Since this is a subclass of Entry_section, all properties of the parent
>   class also apply here. Except for the properties stated as mandatory, the
> diff --git a/tools/binman/etype/efi_capsule.py b/tools/binman/etype/efi_capsule.py
> index 3b30c12ea514..01d56723b98c 100644
> --- a/tools/binman/etype/efi_capsule.py
> +++ b/tools/binman/etype/efi_capsule.py
> @@ -101,6 +101,7 @@ class Entry_efi_capsule(Entry_section):

Please update the docstring so it's in sync with tools/binman/entries.rst.

Cheers,
Quentin

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v4 4/6] binman: Add dump signarture option to mkeficapsule
  2026-01-20  8:12 ` [PATCH v4 4/6] binman: Add dump signarture option to mkeficapsule Wojciech Dubowik
@ 2026-01-20 15:06   ` Quentin Schulz
  0 siblings, 0 replies; 20+ messages in thread
From: Quentin Schulz @ 2026-01-20 15:06 UTC (permalink / raw)
  To: Wojciech Dubowik, u-boot; +Cc: trini, simon.glass

Hi Wojciech,

On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
> It will be used to capsule signature verification.
> 
> Signed-off-by: Wojciech Dubowik <Wojciech.Dubowik@mt.com>
> ---
>   tools/binman/btool/mkeficapsule.py | 6 +++++-
>   1 file changed, 5 insertions(+), 1 deletion(-)
> 
> diff --git a/tools/binman/btool/mkeficapsule.py b/tools/binman/btool/mkeficapsule.py
> index f7e5a8868495..f4b594b3faef 100644
> --- a/tools/binman/btool/mkeficapsule.py
> +++ b/tools/binman/btool/mkeficapsule.py
> @@ -38,7 +38,8 @@ class Bintoolmkeficapsule(bintool.Bintool):
>   
>       def generate_capsule(self, image_index, image_guid, hardware_instance,
>                            payload, output_fname, priv_key, pub_key,
> -                         monotonic_count=0, version=0, oemflags=0):
> +                         monotonic_count=0, version=0, oemflags=0,
> +                         dump_sig=0):

This is clearly a boolean flag both in tools/mkeficapsule.c, the 
dump-signature boolean property added in the next patch and how it's 
stored in Entry_efi_capsule object, so please make this a bool.

>           """Generate a capsule through commandline-provided parameters
>   
>           Args:
> @@ -53,6 +54,7 @@ class Bintoolmkeficapsule(bintool.Bintool):
>               monotonic_count (int): Count used when signing an image
>               version (int): Image version (Optional)
>               oemflags (int): Optional 16 bit OEM flags
> +            dump_sig (int): Dump signature to a file (Optional)

Ditto.

Also, specify the default value (false).

Cheers,
Quentin

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v4 3/6] tools: Fix long option --dump_sig in mkeficapsule
  2026-01-20  8:12 ` [PATCH v4 3/6] tools: Fix long option --dump_sig in mkeficapsule Wojciech Dubowik
@ 2026-01-20 15:11   ` Quentin Schulz
  0 siblings, 0 replies; 20+ messages in thread
From: Quentin Schulz @ 2026-01-20 15:11 UTC (permalink / raw)
  To: Wojciech Dubowik, u-boot; +Cc: trini, simon.glass

Hi Wojciech,

On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
> Only short option has been present.
> 

Actually.... it's been there, just under dump-sig and not dump_sig.

So we have the choice of either fixing the usage string and the manpage 
to use dump-sig instead of dump_sig or support both.

I would personally be in favor of patching the usage string and manpage 
so we don't have long options with an underscore.. it's odd to me.

Regardless of your choice, we need

Fixes: 16abff246b40 ("tools: mkeficapsule: add firmware image signing")

in the commit log.

If you keep dump_sig, I would really recommend to put it next to 
dump-sig and explain why we have both (we need to keep dump-sig for free 
backward compatibility).

Cheers,
Quentin

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule
  2026-01-20  8:12 ` [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule Wojciech Dubowik
@ 2026-01-20 15:53   ` Quentin Schulz
  2026-01-21 12:43     ` EXTERNAL - " Wojciech Dubowik
  0 siblings, 1 reply; 20+ messages in thread
From: Quentin Schulz @ 2026-01-20 15:53 UTC (permalink / raw)
  To: Wojciech Dubowik, u-boot; +Cc: trini, simon.glass

Hi Wojciech,

On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
> Test pkcs11 URI support for UEFI capsule generation. For
> simplicity only private key is defined in binman section
> as softhsm tool doesn't support certificate import (yet).
> 
> Signed-off-by: Wojciech Dubowik <Wojciech.Dubowik@mt.com>
> Reviewed-by: Simon Glass <simon.glass@canonical.com>
> ---
>   tools/binman/ftest.py                         | 53 +++++++++++++++++++
>   .../binman/test/351_capsule_signed_pkcs11.dts | 22 ++++++++
>   2 files changed, 75 insertions(+)
>   create mode 100644 tools/binman/test/351_capsule_signed_pkcs11.dts
> 
> diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
> index 21ec48d86fd1..a005a167e414 100644
> --- a/tools/binman/ftest.py
> +++ b/tools/binman/ftest.py
> @@ -7,6 +7,7 @@
>   #    python -m unittest func_test.TestFunctional.testHelp
>   
>   import collections
> +import configparser
>   import glob
>   import gzip
>   import hashlib
> @@ -7532,6 +7533,58 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
>   
>           self._CheckCapsule(data, signed_capsule=True)
>   
> +    def testPkcs11SignedCapsuleGen(self):
> +        """Test generation of EFI capsule (with PKCS11)"""
> +        data = tools.read_file(self.TestFile("key.key"))
> +        private_key = self._MakeInputFile("key.key", data)
> +        data = tools.read_file(self.TestFile("key.pem"))
> +        cert_file = self._MakeInputFile("key.crt", data)
> +
> +        softhsm2_util = bintool.Bintool.create('softhsm2_util')
> +        self._CheckBintool(softhsm2_util)
> +
> +        prefix = "testPkcs11SignedCapsuleGen."
> +        # Configure SoftHSMv2
> +        data = tools.read_file(self.TestFile('340_softhsm2.conf'))
> +        softhsm2_conf = self._MakeInputFile(f'{prefix}softhsm2.conf', data)
> +        softhsm2_tokens_dir = self._MakeInputDir(f'{prefix}softhsm2.tokens')
> +        tools.write_file(softhsm2_conf, data +
> +                         f'\ndirectories.tokendir = \
> +                         {softhsm2_tokens_dir}\n'.encode("utf-8"))
> +

data is already in softhsm2_conf due to calling
self._MakeInputFile(f'{prefix}softhsm2.conf', data)
you can simply do the same I did in testFitSignPKCS11Simple and append 
to the file. Or you can append to data before you call _MakeInputFile().

> +        p11_kit_config = configparser.ConfigParser()
> +        out = tools.run('p11-kit', 'print-config')

Please create a bintool and call CheckBintool so we can skip the test if 
p11-kit isn't installed. It'll be a bit awkward because there's no 
--version, --help, to print the help text but not have a non-zero exit code.

> +        p11_kit_config.read_string(out)
> +        softhsm2_lib = p11_kit_config['softhsm2']['module']
> +

We probably want to have some try..except here, or a more forgiving 
.get('softhsm2', {}).get('module')
and fail the test if we don't get a path?

> +        os.environ['SOFTHSM2_CONF'] = softhsm2_conf

This is wrong, you'll be messing up with the environment of all tests 
being run in the same thread. You must use the "with 
unittest.mock.patch.dict('os.environ'," implementation I used in 
testFitSignPKCS11Simple.

> +        tools.run('softhsm2-util', '--init-token', '--free', '--label',
> +                  'U-Boot token', '--pin', '1111', '--so-pin',
> +                  '222222')
> +        tools.run('softhsm2-util', '--import', private_key, '--token',
> +                  'U-Boot token', '--label', 'test_key', '--id', '999999',
> +                  '--pin', '1111')
> +
> +        os.environ['PKCS11_MODULE_PATH'] = softhsm2_lib

Same issue as with SOFTHSM2_CONF, you must mock the environment and not 
write to it.

> +        data = self._DoReadFile('351_capsule_signed_pkcs11.dts')
> +
> +        self._CheckCapsule(data, signed_capsule=True)
> +
> +        # Verify signed capsule
> +        hdr = self._GetCapsuleHeaders(data)
> +        monotonic_count = hdr['EFI_FIRMWARE_IMAGE_AUTH.MONOTONIC_COUNT']
> +
> +        with open(self._indir + '/capsule_input.bin', 'ab') as f:

Please use self._MakeInputFile() and prefix it with the name of the 
method so there's no possible name clash.

> +            f.write(struct.pack('<Q', int(monotonic_count, 16)))
> +
> +        try:
> +            tools.run('openssl', 'smime', '-verify', '-inform', 'DER',

This means you depend on openssl being present. Should we maybe do 
something like

openssl = bintool.Bintool.create('openssl')
self._CheckBintool(openssl)
[...]
openssl.run_cmd(['smime', ...])

to skip the test if it isn't installed?

> +                      '-in', tools.get_output_dir() + '/capsule.efi-capsule.p7',

tools.get_output_filename('capsule.efi-capsule.p7')

> +                      '-content', self._indir + '/capsule_input.bin',

Reuse the path created by self._MakeInputFile().

> +                      '-CAfile', cert_file, '-no_check_time')
> +        except ValueError:
> +            self.assertIn('UEFI Capsule verification failed')
> +

I don't think this is valid. 
https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIn 
states you need 2 (or three) arguments, only one's provided here.

I'm assuming we don't need the try..except since the raised Exception 
will just stop (and fail) the execution of the test but continue with 
the other ones? Can you check (locally) by purposefully using the wrong 
key for example?

Cheers,
Quentin

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: EXTERNAL - [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule
  2026-01-20 15:53   ` Quentin Schulz
@ 2026-01-21 12:43     ` Wojciech Dubowik
  2026-01-21 13:06       ` Quentin Schulz
  0 siblings, 1 reply; 20+ messages in thread
From: Wojciech Dubowik @ 2026-01-21 12:43 UTC (permalink / raw)
  To: Quentin Schulz; +Cc: u-boot, trini, simon.glass

On Tue, Jan 20, 2026 at 04:53:04PM +0100, Quentin Schulz wrote:
Hello Quentin,
> Hi Wojciech,
> 
> On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
> > Test pkcs11 URI support for UEFI capsule generation. For
> > simplicity only private key is defined in binman section
> > as softhsm tool doesn't support certificate import (yet).
> > 
> > Signed-off-by: Wojciech Dubowik <Wojciech.Dubowik@mt.com>
> > Reviewed-by: Simon Glass <simon.glass@canonical.com>
> > ---
> >   tools/binman/ftest.py                         | 53 +++++++++++++++++++
> >   .../binman/test/351_capsule_signed_pkcs11.dts | 22 ++++++++
> >   2 files changed, 75 insertions(+)
> >   create mode 100644 tools/binman/test/351_capsule_signed_pkcs11.dts
> > 
> > diff --git a/tools/binman/ftest.py b/tools/binman/ftest.py
> > index 21ec48d86fd1..a005a167e414 100644
> > --- a/tools/binman/ftest.py
> > +++ b/tools/binman/ftest.py
> > @@ -7,6 +7,7 @@
> >   #    python -m unittest func_test.TestFunctional.testHelp
> >   import collections
> > +import configparser
> >   import glob
> >   import gzip
> >   import hashlib
> > @@ -7532,6 +7533,58 @@ fdt         fdtmap                Extract the devicetree blob from the fdtmap
> >           self._CheckCapsule(data, signed_capsule=True)
> > +    def testPkcs11SignedCapsuleGen(self):
> > +        """Test generation of EFI capsule (with PKCS11)"""
> > +        data = tools.read_file(self.TestFile("key.key"))
> > +        private_key = self._MakeInputFile("key.key", data)
> > +        data = tools.read_file(self.TestFile("key.pem"))
> > +        cert_file = self._MakeInputFile("key.crt", data)
> > +
> > +        softhsm2_util = bintool.Bintool.create('softhsm2_util')
> > +        self._CheckBintool(softhsm2_util)
> > +
> > +        prefix = "testPkcs11SignedCapsuleGen."
> > +        # Configure SoftHSMv2
> > +        data = tools.read_file(self.TestFile('340_softhsm2.conf'))
> > +        softhsm2_conf = self._MakeInputFile(f'{prefix}softhsm2.conf', data)
> > +        softhsm2_tokens_dir = self._MakeInputDir(f'{prefix}softhsm2.tokens')
> > +        tools.write_file(softhsm2_conf, data +
> > +                         f'\ndirectories.tokendir = \
> > +                         {softhsm2_tokens_dir}\n'.encode("utf-8"))
> > +
> 
> data is already in softhsm2_conf due to calling
> self._MakeInputFile(f'{prefix}softhsm2.conf', data)
> you can simply do the same I did in testFitSignPKCS11Simple and append to
> the file. Or you can append to data before you call _MakeInputFile().
> 
> > +        p11_kit_config = configparser.ConfigParser()
> > +        out = tools.run('p11-kit', 'print-config')
> 
> Please create a bintool and call CheckBintool so we can skip the test if
> p11-kit isn't installed. It'll be a bit awkward because there's no
> --version, --help, to print the help text but not have a non-zero exit code.
> 
> > +        p11_kit_config.read_string(out)
> > +        softhsm2_lib = p11_kit_config['softhsm2']['module']
> > +
> 
> We probably want to have some try..except here, or a more forgiving
> .get('softhsm2', {}).get('module')
> and fail the test if we don't get a path?
> 
> > +        os.environ['SOFTHSM2_CONF'] = softhsm2_conf
> 
> This is wrong, you'll be messing up with the environment of all tests being
> run in the same thread. You must use the "with
> unittest.mock.patch.dict('os.environ'," implementation I used in
> testFitSignPKCS11Simple.

Well, I have done so in my V2 but has been commented as wrong by the
first reviewer. I will restore it back.

> 
> > +        tools.run('softhsm2-util', '--init-token', '--free', '--label',
> > +                  'U-Boot token', '--pin', '1111', '--so-pin',
> > +                  '222222')
> > +        tools.run('softhsm2-util', '--import', private_key, '--token',
> > +                  'U-Boot token', '--label', 'test_key', '--id', '999999',
> > +                  '--pin', '1111')
> > +
> > +        os.environ['PKCS11_MODULE_PATH'] = softhsm2_lib
> 
> Same issue as with SOFTHSM2_CONF, you must mock the environment and not
> write to it.
> 
> > +        data = self._DoReadFile('351_capsule_signed_pkcs11.dts')
> > +
> > +        self._CheckCapsule(data, signed_capsule=True)
> > +
> > +        # Verify signed capsule
> > +        hdr = self._GetCapsuleHeaders(data)
> > +        monotonic_count = hdr['EFI_FIRMWARE_IMAGE_AUTH.MONOTONIC_COUNT']
> > +
> > +        with open(self._indir + '/capsule_input.bin', 'ab') as f:
> 
> Please use self._MakeInputFile() and prefix it with the name of the method
> so there's no possible name clash.
> 
> > +            f.write(struct.pack('<Q', int(monotonic_count, 16)))
> > +
> > +        try:
> > +            tools.run('openssl', 'smime', '-verify', '-inform', 'DER',
> 
> This means you depend on openssl being present. Should we maybe do something
> like
> 
> openssl = bintool.Bintool.create('openssl')
> self._CheckBintool(openssl)
> [...]
> openssl.run_cmd(['smime', ...])
> 
> to skip the test if it isn't installed?
> 
> > +                      '-in', tools.get_output_dir() + '/capsule.efi-capsule.p7',
> 
> tools.get_output_filename('capsule.efi-capsule.p7')
> 
> > +                      '-content', self._indir + '/capsule_input.bin',
> 
> Reuse the path created by self._MakeInputFile().
> 
> > +                      '-CAfile', cert_file, '-no_check_time')
> > +        except ValueError:
> > +            self.assertIn('UEFI Capsule verification failed')
> > +
> 
> I don't think this is valid.
> https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIn
> states you need 2 (or three) argumentis, only one's provided here.
> 
> I'm assuming we don't need the try..except since the raised Exception will
> just stop (and fail) the execution of the test but continue with the other
> ones? Can you check (locally) by purposefully using the wrong key for
> example?

It has worked (failed) with try/except. I had to add no time check as the certificate
used for uefi capsules tests has expired in 2024. Maybe somebody can refresh
it one day. I will try other methods to see if we still get meaningful error
messages.

I will try to handle issues you mentioned also in other threads this/next week.

Cheers,
Wojtek
> 
> Cheers,
> Quentin

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: EXTERNAL - [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule
  2026-01-21 12:43     ` EXTERNAL - " Wojciech Dubowik
@ 2026-01-21 13:06       ` Quentin Schulz
  2026-01-22 22:46         ` Simon Glass
  0 siblings, 1 reply; 20+ messages in thread
From: Quentin Schulz @ 2026-01-21 13:06 UTC (permalink / raw)
  To: Wojciech Dubowik; +Cc: u-boot, trini, simon.glass

Hi Wojciech,

On 1/21/26 1:43 PM, Wojciech Dubowik wrote:
> On Tue, Jan 20, 2026 at 04:53:04PM +0100, Quentin Schulz wrote:
> Hello Quentin,
>> Hi Wojciech,
>>
>> On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
[...]
>>> +        os.environ['SOFTHSM2_CONF'] = softhsm2_conf
>>
>> This is wrong, you'll be messing up with the environment of all tests being
>> run in the same thread. You must use the "with
>> unittest.mock.patch.dict('os.environ'," implementation I used in
>> testFitSignPKCS11Simple.
> 
> Well, I have done so in my V2 but has been commented as wrong by the
> first reviewer. I will restore it back.
> 

Indeed, I see Simon asked you to do this in v2 and I missed it. It isn't 
how we should be doing it.

This is likely fine on its own because there's only one test that is now 
modifying os.environ's SOFTHSM2_CONF but this will be a problem next 
time a test wants to modify it. I actually hit this issue when 
developing the PKCS11 fit signing tests as I had two tests modifying the 
environment.

The only trace of it left is the changelog in 
https://lore.kernel.org/u-boot/20251121-binman-engine-v3-0-b80180aaa783@cherry.de/

"""
- fixed issues due to modification of the environment in tests failing
   other tests, by using unittest.mock.patch.dict() on os.environ as
   suggested by the unittest.mock doc,
"""

and you can check the diff between the v2 and v3 to check I used to 
modify the env directly but now mock it instead.

Sorry for not catching this, should have answered to Simon in the v2.

[...]

>>> +                      '-CAfile', cert_file, '-no_check_time')
>>> +        except ValueError:
>>> +            self.assertIn('UEFI Capsule verification failed')
>>> +
>>
>> I don't think this is valid.
>> https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIn
>> states you need 2 (or three) argumentis, only one's provided here.
>>
>> I'm assuming we don't need the try..except since the raised Exception will
>> just stop (and fail) the execution of the test but continue with the other
>> ones? Can you check (locally) by purposefully using the wrong key for
>> example?
> 
> It has worked (failed) with try/except. I had to add no time check as the certificate
Thanks for the heads up, @Simon any chance you can update the 
certificate so we're fine for the decades to come?

Cheers,
Quentin

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: EXTERNAL - [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule
  2026-01-21 13:06       ` Quentin Schulz
@ 2026-01-22 22:46         ` Simon Glass
  2026-01-26 11:42           ` Quentin Schulz
  0 siblings, 1 reply; 20+ messages in thread
From: Simon Glass @ 2026-01-22 22:46 UTC (permalink / raw)
  To: Quentin Schulz; +Cc: Wojciech Dubowik, u-boot, trini, Simon Glass

Hi,

On Thu, 22 Jan 2026 at 02:06, Quentin Schulz <quentin.schulz@cherry.de> wrote:
>
> Hi Wojciech,
>
> On 1/21/26 1:43 PM, Wojciech Dubowik wrote:
> > On Tue, Jan 20, 2026 at 04:53:04PM +0100, Quentin Schulz wrote:
> > Hello Quentin,
> >> Hi Wojciech,
> >>
> >> On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
> [...]
> >>> +        os.environ['SOFTHSM2_CONF'] = softhsm2_conf
> >>
> >> This is wrong, you'll be messing up with the environment of all tests being
> >> run in the same thread. You must use the "with
> >> unittest.mock.patch.dict('os.environ'," implementation I used in
> >> testFitSignPKCS11Simple.
> >
> > Well, I have done so in my V2 but has been commented as wrong by the
> > first reviewer. I will restore it back.
> >
>
> Indeed, I see Simon asked you to do this in v2 and I missed it. It isn't
> how we should be doing it.
>
> This is likely fine on its own because there's only one test that is now
> modifying os.environ's SOFTHSM2_CONF but this will be a problem next
> time a test wants to modify it. I actually hit this issue when
> developing the PKCS11 fit signing tests as I had two tests modifying the
> environment.
>
> The only trace of it left is the changelog in
> https://lore.kernel.org/u-boot/20251121-binman-engine-v3-0-b80180aaa783@cherry.de/
>
> """
> - fixed issues due to modification of the environment in tests failing
>    other tests, by using unittest.mock.patch.dict() on os.environ as
>    suggested by the unittest.mock doc,
> """
>
> and you can check the diff between the v2 and v3 to check I used to
> modify the env directly but now mock it instead.
>
> Sorry for not catching this, should have answered to Simon in the v2.

In practice we try to set values for various which are important, so
future tests should explicitly delete the var if needed. But I am OK
with mocking, instead.

>
> [...]
>
> >>> +                      '-CAfile', cert_file, '-no_check_time')
> >>> +        except ValueError:
> >>> +            self.assertIn('UEFI Capsule verification failed')
> >>> +
> >>
> >> I don't think this is valid.
> >> https://docs.python.org/3/library/unittest.html#unittest.TestCase.assertIn
> >> states you need 2 (or three) argumentis, only one's provided here.
> >>
> >> I'm assuming we don't need the try..except since the raised Exception will
> >> just stop (and fail) the execution of the test but continue with the other
> >> ones? Can you check (locally) by purposefully using the wrong key for
> >> example?
> >
> > It has worked (failed) with try/except. I had to add no time check as the certificate
> Thanks for the heads up, @Simon any chance you can update the
> certificate so we're fine for the decades to come?

OK I sent a patch.

Regards,
Simon

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: EXTERNAL - [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule
  2026-01-22 22:46         ` Simon Glass
@ 2026-01-26 11:42           ` Quentin Schulz
  2026-01-27  3:00             ` Simon Glass
  0 siblings, 1 reply; 20+ messages in thread
From: Quentin Schulz @ 2026-01-26 11:42 UTC (permalink / raw)
  To: Simon Glass; +Cc: Wojciech Dubowik, u-boot, trini, Simon Glass

Hi Simon,

On 1/22/26 11:46 PM, Simon Glass wrote:
> Hi,
> 
> On Thu, 22 Jan 2026 at 02:06, Quentin Schulz <quentin.schulz@cherry.de> wrote:
>>
>> Hi Wojciech,
>>
>> On 1/21/26 1:43 PM, Wojciech Dubowik wrote:
>>> On Tue, Jan 20, 2026 at 04:53:04PM +0100, Quentin Schulz wrote:
>>> Hello Quentin,
>>>> Hi Wojciech,
>>>>
>>>> On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
>> [...]
>>>>> +        os.environ['SOFTHSM2_CONF'] = softhsm2_conf
>>>>
>>>> This is wrong, you'll be messing up with the environment of all tests being
>>>> run in the same thread. You must use the "with
>>>> unittest.mock.patch.dict('os.environ'," implementation I used in
>>>> testFitSignPKCS11Simple.
>>>
>>> Well, I have done so in my V2 but has been commented as wrong by the
>>> first reviewer. I will restore it back.
>>>
>>
>> Indeed, I see Simon asked you to do this in v2 and I missed it. It isn't
>> how we should be doing it.
>>
>> This is likely fine on its own because there's only one test that is now
>> modifying os.environ's SOFTHSM2_CONF but this will be a problem next
>> time a test wants to modify it. I actually hit this issue when
>> developing the PKCS11 fit signing tests as I had two tests modifying the
>> environment.
>>
>> The only trace of it left is the changelog in
>> https://lore.kernel.org/u-boot/20251121-binman-engine-v3-0-b80180aaa783@cherry.de/
>>
>> """
>> - fixed issues due to modification of the environment in tests failing
>>     other tests, by using unittest.mock.patch.dict() on os.environ as
>>     suggested by the unittest.mock doc,
>> """
>>
>> and you can check the diff between the v2 and v3 to check I used to
>> modify the env directly but now mock it instead.
>>
>> Sorry for not catching this, should have answered to Simon in the v2.
> 
> In practice we try to set values for various which are important, so
> future tests should explicitly delete the var if needed. But I am OK

This is not working. See this very simple example (too lazy to use 
threading.Lock so synchronization done via time.sleep instead):

"""
#!/usr/bin/env python3

import os
import time
import threading


def thread_func(n):
     if n == 1:
         time.sleep(1)
     print(f'Thread {n} read environ var FOO={os.environ["FOO"]}')
     if n == 1:
         time.sleep(1)
     print(f'Thread {n} set environ var FOO to foo{n}')
     os.environ['FOO'] = f'foo{n}'
     if n == 0:
         time.sleep(5)
     print(f'Thread {n} read environ var FOO={os.environ["FOO"]}')
     if n == 0:
         print(f'Thread {n} removes environ var FOO')
         del os.environ["FOO"]
     else:
         time.sleep(10)
         print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')


threads = []

os.environ["FOO"] = "foo"

for i in range(0, 2):
     t = threading.Thread(target=thread_func, args=(i,))
     threads.append(t)

for t in threads:
     t.start()

for t in threads:
     t.join()

"""

This results in:

"""
Thread 0 read environ var FOO=foo
Thread 0 set environ var FOO to foo0
Thread 1 read environ var FOO=foo0
Thread 1 set environ var FOO to foo1
Thread 1 read environ var FOO=foo1
Thread 0 read environ var FOO=foo1
Thread 0 removes environ var FOO
Thread 1 read environ var FOO=None
"""

You see that modification made to os.environ in a different thread 
impacts the other threads. A test should definitely NOT modify anything 
for another test, especially not when it's already running.

So now, I implemented mocking instead (like in my tests for PKCS11 in 
tools/binman/ftest.py) because I know it works.

See:

"""
#!/usr/bin/env python3

import os
import time
import threading
import unittest.mock


def thread_func(n):
     if n == 1:
         time.sleep(1)
     print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
     if n == 1:
         time.sleep(1)
     with unittest.mock.patch.dict('os.environ',
                                   {'FOO': f'foo{n}'}):
         print(f'Thread {n} set environ var FOO to foo{n}')
         if n == 0:
             time.sleep(5)
         print(f'Thread {n} read mocked environ var 
FOO={os.environ.get("FOO")}')
         if n == 1:
             time.sleep(6)
     print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')


threads = []

for i in range(0, 2):
     t = threading.Thread(target=thread_func, args=(i,))
     threads.append(t)

for t in threads:
     t.start()

for t in threads:
     t.join()
"""

Lo and behold, it.... does NOT work???????

I get:

"""
Thread 0 read environ var FOO=None
Thread 0 set environ var FOO to foo0
Thread 1 read environ var FOO=foo0
Thread 1 set environ var FOO to foo1
Thread 1 read mocked environ var FOO=foo1
Thread 0 read mocked environ var FOO=foo1
Thread 0 read environ var FOO=None
Thread 1 read environ var FOO=foo0
"""

I've read that os.environ isn't thread-safe, due to setenv() in glibc 
not being thread-safe: 
https://sourceware.org/glibc/manual/latest/html_node/Environment-Access.html#Environment-Access-1

"""
Modifications of environment variables are not allowed in multi-threaded 
programs.
"""

I'm not sure if this applies to any other Python implementation than 
CPython? But that is likely the one that most people are using.

So... In short, I'm at a loss, no clue how to fix this (if it is even 
fixable). The obvious answer is "spawn multiple processes instead of 
multiple threads" but I can guarantee you, you don't want to be going 
this route as multiprocessing is a lot of headaches in Python. We could 
have the Python thread spawn a subprocess which has a different 
environment if we wanted to (via the `env` command for example), but 
that means not using binman Python API, rather its CLI. We could have 
bintools accept an environment dict that needs to be passed via the 
`env` command or the `env` kwargs of subprocess.Popen().

Headaches, headaches.

Quentin

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: EXTERNAL - [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule
  2026-01-26 11:42           ` Quentin Schulz
@ 2026-01-27  3:00             ` Simon Glass
  2026-02-03  8:17               ` Wojciech Dubowik
  0 siblings, 1 reply; 20+ messages in thread
From: Simon Glass @ 2026-01-27  3:00 UTC (permalink / raw)
  To: Quentin Schulz; +Cc: Simon Glass, Wojciech Dubowik, u-boot, trini

Hi Quentin,

On Tue, 27 Jan 2026 at 00:43, Quentin Schulz <quentin.schulz@cherry.de> wrote:
>
> Hi Simon,
>
> On 1/22/26 11:46 PM, Simon Glass wrote:
> > Hi,
> >
> > On Thu, 22 Jan 2026 at 02:06, Quentin Schulz <quentin.schulz@cherry.de> wrote:
> >>
> >> Hi Wojciech,
> >>
> >> On 1/21/26 1:43 PM, Wojciech Dubowik wrote:
> >>> On Tue, Jan 20, 2026 at 04:53:04PM +0100, Quentin Schulz wrote:
> >>> Hello Quentin,
> >>>> Hi Wojciech,
> >>>>
> >>>> On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
> >> [...]
> >>>>> +        os.environ['SOFTHSM2_CONF'] = softhsm2_conf
> >>>>
> >>>> This is wrong, you'll be messing up with the environment of all tests being
> >>>> run in the same thread. You must use the "with
> >>>> unittest.mock.patch.dict('os.environ'," implementation I used in
> >>>> testFitSignPKCS11Simple.
> >>>
> >>> Well, I have done so in my V2 but has been commented as wrong by the
> >>> first reviewer. I will restore it back.
> >>>
> >>
> >> Indeed, I see Simon asked you to do this in v2 and I missed it. It isn't
> >> how we should be doing it.
> >>
> >> This is likely fine on its own because there's only one test that is now
> >> modifying os.environ's SOFTHSM2_CONF but this will be a problem next
> >> time a test wants to modify it. I actually hit this issue when
> >> developing the PKCS11 fit signing tests as I had two tests modifying the
> >> environment.
> >>
> >> The only trace of it left is the changelog in
> >> https://lore.kernel.org/u-boot/20251121-binman-engine-v3-0-b80180aaa783@cherry.de/
> >>
> >> """
> >> - fixed issues due to modification of the environment in tests failing
> >>     other tests, by using unittest.mock.patch.dict() on os.environ as
> >>     suggested by the unittest.mock doc,
> >> """
> >>
> >> and you can check the diff between the v2 and v3 to check I used to
> >> modify the env directly but now mock it instead.
> >>
> >> Sorry for not catching this, should have answered to Simon in the v2.
> >
> > In practice we try to set values for various which are important, so
> > future tests should explicitly delete the var if needed. But I am OK
>
> This is not working. See this very simple example (too lazy to use
> threading.Lock so synchronization done via time.sleep instead):
>
> """
> #!/usr/bin/env python3
>
> import os
> import time
> import threading
>
>
> def thread_func(n):
>      if n == 1:
>          time.sleep(1)
>      print(f'Thread {n} read environ var FOO={os.environ["FOO"]}')
>      if n == 1:
>          time.sleep(1)
>      print(f'Thread {n} set environ var FOO to foo{n}')
>      os.environ['FOO'] = f'foo{n}'
>      if n == 0:
>          time.sleep(5)
>      print(f'Thread {n} read environ var FOO={os.environ["FOO"]}')
>      if n == 0:
>          print(f'Thread {n} removes environ var FOO')
>          del os.environ["FOO"]
>      else:
>          time.sleep(10)
>          print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
>
>
> threads = []
>
> os.environ["FOO"] = "foo"
>
> for i in range(0, 2):
>      t = threading.Thread(target=thread_func, args=(i,))
>      threads.append(t)
>
> for t in threads:
>      t.start()
>
> for t in threads:
>      t.join()
>
> """
>
> This results in:
>
> """
> Thread 0 read environ var FOO=foo
> Thread 0 set environ var FOO to foo0
> Thread 1 read environ var FOO=foo0
> Thread 1 set environ var FOO to foo1
> Thread 1 read environ var FOO=foo1
> Thread 0 read environ var FOO=foo1
> Thread 0 removes environ var FOO
> Thread 1 read environ var FOO=None
> """
>
> You see that modification made to os.environ in a different thread
> impacts the other threads. A test should definitely NOT modify anything
> for another test, especially not when it's already running.
>
> So now, I implemented mocking instead (like in my tests for PKCS11 in
> tools/binman/ftest.py) because I know it works.
>
> See:
>
> """
> #!/usr/bin/env python3
>
> import os
> import time
> import threading
> import unittest.mock
>
>
> def thread_func(n):
>      if n == 1:
>          time.sleep(1)
>      print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
>      if n == 1:
>          time.sleep(1)
>      with unittest.mock.patch.dict('os.environ',
>                                    {'FOO': f'foo{n}'}):
>          print(f'Thread {n} set environ var FOO to foo{n}')
>          if n == 0:
>              time.sleep(5)
>          print(f'Thread {n} read mocked environ var
> FOO={os.environ.get("FOO")}')
>          if n == 1:
>              time.sleep(6)
>      print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
>
>
> threads = []
>
> for i in range(0, 2):
>      t = threading.Thread(target=thread_func, args=(i,))
>      threads.append(t)
>
> for t in threads:
>      t.start()
>
> for t in threads:
>      t.join()
> """
>
> Lo and behold, it.... does NOT work???????
>
> I get:
>
> """
> Thread 0 read environ var FOO=None
> Thread 0 set environ var FOO to foo0
> Thread 1 read environ var FOO=foo0
> Thread 1 set environ var FOO to foo1
> Thread 1 read mocked environ var FOO=foo1
> Thread 0 read mocked environ var FOO=foo1
> Thread 0 read environ var FOO=None
> Thread 1 read environ var FOO=foo0
> """
>
> I've read that os.environ isn't thread-safe, due to setenv() in glibc
> not being thread-safe:
> https://sourceware.org/glibc/manual/latest/html_node/Environment-Access.html#Environment-Access-1
>
> """
> Modifications of environment variables are not allowed in multi-threaded
> programs.
> """
>
> I'm not sure if this applies to any other Python implementation than
> CPython? But that is likely the one that most people are using.
>
> So... In short, I'm at a loss, no clue how to fix this (if it is even
> fixable). The obvious answer is "spawn multiple processes instead of
> multiple threads" but I can guarantee you, you don't want to be going
> this route as multiprocessing is a lot of headaches in Python. We could
> have the Python thread spawn a subprocess which has a different
> environment if we wanted to (via the `env` command for example), but
> that means not using binman Python API, rather its CLI. We could have
> bintools accept an environment dict that needs to be passed via the
> `env` command or the `env` kwargs of subprocess.Popen().
>
> Headaches, headaches.

I would argue that requiring a particular environment var for a test
is not a great idea. Better to pass an argument through the call stack
and have the external program run with a new environment containing
what is needed.

Regards,
Simon

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: EXTERNAL - [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule
  2026-01-27  3:00             ` Simon Glass
@ 2026-02-03  8:17               ` Wojciech Dubowik
  2026-02-04  0:22                 ` Simon Glass
  2026-02-04  9:49                 ` Quentin Schulz
  0 siblings, 2 replies; 20+ messages in thread
From: Wojciech Dubowik @ 2026-02-03  8:17 UTC (permalink / raw)
  To: Simon Glass; +Cc: Quentin Schulz, Simon Glass, u-boot, trini

On Tue, Jan 27, 2026 at 04:00:44PM +1300, Simon Glass wrote:
Hi Simon,
> Hi Quentin,
> 
> On Tue, 27 Jan 2026 at 00:43, Quentin Schulz <quentin.schulz@cherry.de> wrote:
> >
> > Hi Simon,
> >
> > On 1/22/26 11:46 PM, Simon Glass wrote:
> > > Hi,
> > >
> > > On Thu, 22 Jan 2026 at 02:06, Quentin Schulz <quentin.schulz@cherry.de> wrote:
> > >>
> > >> Hi Wojciech,
> > >>
> > >> On 1/21/26 1:43 PM, Wojciech Dubowik wrote:
> > >>> On Tue, Jan 20, 2026 at 04:53:04PM +0100, Quentin Schulz wrote:
> > >>> Hello Quentin,
> > >>>> Hi Wojciech,
> > >>>>
> > >>>> On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
> > >> [...]
> > >>>>> +        os.environ['SOFTHSM2_CONF'] = softhsm2_conf
> > >>>>
> > >>>> This is wrong, you'll be messing up with the environment of all tests being
> > >>>> run in the same thread. You must use the "with
> > >>>> unittest.mock.patch.dict('os.environ'," implementation I used in
> > >>>> testFitSignPKCS11Simple.
> > >>>
> > >>> Well, I have done so in my V2 but has been commented as wrong by the
> > >>> first reviewer. I will restore it back.
> > >>>
> > >>
> > >> Indeed, I see Simon asked you to do this in v2 and I missed it. It isn't
> > >> how we should be doing it.
> > >>
> > >> This is likely fine on its own because there's only one test that is now
> > >> modifying os.environ's SOFTHSM2_CONF but this will be a problem next
> > >> time a test wants to modify it. I actually hit this issue when
> > >> developing the PKCS11 fit signing tests as I had two tests modifying the
> > >> environment.
> > >>
> > >> The only trace of it left is the changelog in
> > >> https://lore.kernel.org/u-boot/20251121-binman-engine-v3-0-b80180aaa783@cherry.de/
> > >>
> > >> """
> > >> - fixed issues due to modification of the environment in tests failing
> > >>     other tests, by using unittest.mock.patch.dict() on os.environ as
> > >>     suggested by the unittest.mock doc,
> > >> """
> > >>
> > >> and you can check the diff between the v2 and v3 to check I used to
> > >> modify the env directly but now mock it instead.
> > >>
> > >> Sorry for not catching this, should have answered to Simon in the v2.
> > >
> > > In practice we try to set values for various which are important, so
> > > future tests should explicitly delete the var if needed. But I am OK
> >
> > This is not working. See this very simple example (too lazy to use
> > threading.Lock so synchronization done via time.sleep instead):
> >
> > """
> > #!/usr/bin/env python3
> >
> > import os
> > import time
> > import threading
> >
> >
> > def thread_func(n):
> >      if n == 1:
> >          time.sleep(1)
> >      print(f'Thread {n} read environ var FOO={os.environ["FOO"]}')
> >      if n == 1:
> >          time.sleep(1)
> >      print(f'Thread {n} set environ var FOO to foo{n}')
> >      os.environ['FOO'] = f'foo{n}'
> >      if n == 0:
> >          time.sleep(5)
> >      print(f'Thread {n} read environ var FOO={os.environ["FOO"]}')
> >      if n == 0:
> >          print(f'Thread {n} removes environ var FOO')
> >          del os.environ["FOO"]
> >      else:
> >          time.sleep(10)
> >          print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
> >
> >
> > threads = []
> >
> > os.environ["FOO"] = "foo"
> >
> > for i in range(0, 2):
> >      t = threading.Thread(target=thread_func, args=(i,))
> >      threads.append(t)
> >
> > for t in threads:
> >      t.start()
> >
> > for t in threads:
> >      t.join()
> >
> > """
> >
> > This results in:
> >
> > """
> > Thread 0 read environ var FOO=foo
> > Thread 0 set environ var FOO to foo0
> > Thread 1 read environ var FOO=foo0
> > Thread 1 set environ var FOO to foo1
> > Thread 1 read environ var FOO=foo1
> > Thread 0 read environ var FOO=foo1
> > Thread 0 removes environ var FOO
> > Thread 1 read environ var FOO=None
> > """
> >
> > You see that modification made to os.environ in a different thread
> > impacts the other threads. A test should definitely NOT modify anything
> > for another test, especially not when it's already running.
> >
> > So now, I implemented mocking instead (like in my tests for PKCS11 in
> > tools/binman/ftest.py) because I know it works.
> >
> > See:
> >
> > """
> > #!/usr/bin/env python3
> >
> > import os
> > import time
> > import threading
> > import unittest.mock
> >
> >
> > def thread_func(n):
> >      if n == 1:
> >          time.sleep(1)
> >      print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
> >      if n == 1:
> >          time.sleep(1)
> >      with unittest.mock.patch.dict('os.environ',
> >                                    {'FOO': f'foo{n}'}):
> >          print(f'Thread {n} set environ var FOO to foo{n}')
> >          if n == 0:
> >              time.sleep(5)
> >          print(f'Thread {n} read mocked environ var
> > FOO={os.environ.get("FOO")}')
> >          if n == 1:
> >              time.sleep(6)
> >      print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
> >
> >
> > threads = []
> >
> > for i in range(0, 2):
> >      t = threading.Thread(target=thread_func, args=(i,))
> >      threads.append(t)
> >
> > for t in threads:
> >      t.start()
> >
> > for t in threads:
> >      t.join()
> > """
> >
> > Lo and behold, it.... does NOT work???????
> >
> > I get:
> >
> > """
> > Thread 0 read environ var FOO=None
> > Thread 0 set environ var FOO to foo0
> > Thread 1 read environ var FOO=foo0
> > Thread 1 set environ var FOO to foo1
> > Thread 1 read mocked environ var FOO=foo1
> > Thread 0 read mocked environ var FOO=foo1
> > Thread 0 read environ var FOO=None
> > Thread 1 read environ var FOO=foo0
> > """
> >
> > I've read that os.environ isn't thread-safe, due to setenv() in glibc
> > not being thread-safe:
> > https://sourceware.org/glibc/manual/latest/html_node/Environment-Access.html#Environment-Access-1
> >
> > """
> > Modifications of environment variables are not allowed in multi-threaded
> > programs.
> > """
> >
> > I'm not sure if this applies to any other Python implementation than
> > CPython? But that is likely the one that most people are using.
> >
> > So... In short, I'm at a loss, no clue how to fix this (if it is even
> > fixable). The obvious answer is "spawn multiple processes instead of
> > multiple threads" but I can guarantee you, you don't want to be going
> > this route as multiprocessing is a lot of headaches in Python. We could
> > have the Python thread spawn a subprocess which has a different
> > environment if we wanted to (via the `env` command for example), but
> > that means not using binman Python API, rather its CLI. We could have
> > bintools accept an environment dict that needs to be passed via the
> > `env` command or the `env` kwargs of subprocess.Popen().
> >
> > Headaches, headaches.
> 
> I would argue that requiring a particular environment var for a test
> is not a great idea. Better to pass an argument through the call stack
> and have the external program run with a new environment containing
> what is needed.
Would it be ok to call softhsm utils with softhsm_conf like
SOFTHSM2_CONF=... softhsm2-util? Then we don't have to mock environment.
I would still have to set SOFTHSM install dir location with the env variable
for mkeficapsule in binman generation. I guess it doesn't affect other
tests as we don't need to change it.

Regards,
Wojtek

> 
> Regards,
> Simon

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: EXTERNAL - [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule
  2026-02-03  8:17               ` Wojciech Dubowik
@ 2026-02-04  0:22                 ` Simon Glass
  2026-02-04  9:49                 ` Quentin Schulz
  1 sibling, 0 replies; 20+ messages in thread
From: Simon Glass @ 2026-02-04  0:22 UTC (permalink / raw)
  To: Wojciech Dubowik; +Cc: Quentin Schulz, Simon Glass, u-boot, trini

Hi Wojciech,

On Tue, 3 Feb 2026 at 21:18, Wojciech Dubowik <Wojciech.Dubowik@mt.com> wrote:
>
> On Tue, Jan 27, 2026 at 04:00:44PM +1300, Simon Glass wrote:
> Hi Simon,
> > Hi Quentin,
> >
> > On Tue, 27 Jan 2026 at 00:43, Quentin Schulz <quentin.schulz@cherry.de> wrote:
> > >
> > > Hi Simon,
> > >
> > > On 1/22/26 11:46 PM, Simon Glass wrote:
> > > > Hi,
> > > >
> > > > On Thu, 22 Jan 2026 at 02:06, Quentin Schulz <quentin.schulz@cherry.de> wrote:
> > > >>
> > > >> Hi Wojciech,
> > > >>
> > > >> On 1/21/26 1:43 PM, Wojciech Dubowik wrote:
> > > >>> On Tue, Jan 20, 2026 at 04:53:04PM +0100, Quentin Schulz wrote:
> > > >>> Hello Quentin,
> > > >>>> Hi Wojciech,
> > > >>>>
> > > >>>> On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
> > > >> [...]
> > > >>>>> +        os.environ['SOFTHSM2_CONF'] = softhsm2_conf
> > > >>>>
> > > >>>> This is wrong, you'll be messing up with the environment of all tests being
> > > >>>> run in the same thread. You must use the "with
> > > >>>> unittest.mock.patch.dict('os.environ'," implementation I used in
> > > >>>> testFitSignPKCS11Simple.
> > > >>>
> > > >>> Well, I have done so in my V2 but has been commented as wrong by the
> > > >>> first reviewer. I will restore it back.
> > > >>>
> > > >>
> > > >> Indeed, I see Simon asked you to do this in v2 and I missed it. It isn't
> > > >> how we should be doing it.
> > > >>
> > > >> This is likely fine on its own because there's only one test that is now
> > > >> modifying os.environ's SOFTHSM2_CONF but this will be a problem next
> > > >> time a test wants to modify it. I actually hit this issue when
> > > >> developing the PKCS11 fit signing tests as I had two tests modifying the
> > > >> environment.
> > > >>
> > > >> The only trace of it left is the changelog in
> > > >> https://lore.kernel.org/u-boot/20251121-binman-engine-v3-0-b80180aaa783@cherry.de/
> > > >>
> > > >> """
> > > >> - fixed issues due to modification of the environment in tests failing
> > > >>     other tests, by using unittest.mock.patch.dict() on os.environ as
> > > >>     suggested by the unittest.mock doc,
> > > >> """
> > > >>
> > > >> and you can check the diff between the v2 and v3 to check I used to
> > > >> modify the env directly but now mock it instead.
> > > >>
> > > >> Sorry for not catching this, should have answered to Simon in the v2.
> > > >
> > > > In practice we try to set values for various which are important, so
> > > > future tests should explicitly delete the var if needed. But I am OK
> > >
> > > This is not working. See this very simple example (too lazy to use
> > > threading.Lock so synchronization done via time.sleep instead):
> > >
> > > """
> > > #!/usr/bin/env python3
> > >
> > > import os
> > > import time
> > > import threading
> > >
> > >
> > > def thread_func(n):
> > >      if n == 1:
> > >          time.sleep(1)
> > >      print(f'Thread {n} read environ var FOO={os.environ["FOO"]}')
> > >      if n == 1:
> > >          time.sleep(1)
> > >      print(f'Thread {n} set environ var FOO to foo{n}')
> > >      os.environ['FOO'] = f'foo{n}'
> > >      if n == 0:
> > >          time.sleep(5)
> > >      print(f'Thread {n} read environ var FOO={os.environ["FOO"]}')
> > >      if n == 0:
> > >          print(f'Thread {n} removes environ var FOO')
> > >          del os.environ["FOO"]
> > >      else:
> > >          time.sleep(10)
> > >          print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
> > >
> > >
> > > threads = []
> > >
> > > os.environ["FOO"] = "foo"
> > >
> > > for i in range(0, 2):
> > >      t = threading.Thread(target=thread_func, args=(i,))
> > >      threads.append(t)
> > >
> > > for t in threads:
> > >      t.start()
> > >
> > > for t in threads:
> > >      t.join()
> > >
> > > """
> > >
> > > This results in:
> > >
> > > """
> > > Thread 0 read environ var FOO=foo
> > > Thread 0 set environ var FOO to foo0
> > > Thread 1 read environ var FOO=foo0
> > > Thread 1 set environ var FOO to foo1
> > > Thread 1 read environ var FOO=foo1
> > > Thread 0 read environ var FOO=foo1
> > > Thread 0 removes environ var FOO
> > > Thread 1 read environ var FOO=None
> > > """
> > >
> > > You see that modification made to os.environ in a different thread
> > > impacts the other threads. A test should definitely NOT modify anything
> > > for another test, especially not when it's already running.
> > >
> > > So now, I implemented mocking instead (like in my tests for PKCS11 in
> > > tools/binman/ftest.py) because I know it works.
> > >
> > > See:
> > >
> > > """
> > > #!/usr/bin/env python3
> > >
> > > import os
> > > import time
> > > import threading
> > > import unittest.mock
> > >
> > >
> > > def thread_func(n):
> > >      if n == 1:
> > >          time.sleep(1)
> > >      print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
> > >      if n == 1:
> > >          time.sleep(1)
> > >      with unittest.mock.patch.dict('os.environ',
> > >                                    {'FOO': f'foo{n}'}):
> > >          print(f'Thread {n} set environ var FOO to foo{n}')
> > >          if n == 0:
> > >              time.sleep(5)
> > >          print(f'Thread {n} read mocked environ var
> > > FOO={os.environ.get("FOO")}')
> > >          if n == 1:
> > >              time.sleep(6)
> > >      print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
> > >
> > >
> > > threads = []
> > >
> > > for i in range(0, 2):
> > >      t = threading.Thread(target=thread_func, args=(i,))
> > >      threads.append(t)
> > >
> > > for t in threads:
> > >      t.start()
> > >
> > > for t in threads:
> > >      t.join()
> > > """
> > >
> > > Lo and behold, it.... does NOT work???????
> > >
> > > I get:
> > >
> > > """
> > > Thread 0 read environ var FOO=None
> > > Thread 0 set environ var FOO to foo0
> > > Thread 1 read environ var FOO=foo0
> > > Thread 1 set environ var FOO to foo1
> > > Thread 1 read mocked environ var FOO=foo1
> > > Thread 0 read mocked environ var FOO=foo1
> > > Thread 0 read environ var FOO=None
> > > Thread 1 read environ var FOO=foo0
> > > """
> > >
> > > I've read that os.environ isn't thread-safe, due to setenv() in glibc
> > > not being thread-safe:
> > > https://sourceware.org/glibc/manual/latest/html_node/Environment-Access.html#Environment-Access-1
> > >
> > > """
> > > Modifications of environment variables are not allowed in multi-threaded
> > > programs.
> > > """
> > >
> > > I'm not sure if this applies to any other Python implementation than
> > > CPython? But that is likely the one that most people are using.
> > >
> > > So... In short, I'm at a loss, no clue how to fix this (if it is even
> > > fixable). The obvious answer is "spawn multiple processes instead of
> > > multiple threads" but I can guarantee you, you don't want to be going
> > > this route as multiprocessing is a lot of headaches in Python. We could
> > > have the Python thread spawn a subprocess which has a different
> > > environment if we wanted to (via the `env` command for example), but
> > > that means not using binman Python API, rather its CLI. We could have
> > > bintools accept an environment dict that needs to be passed via the
> > > `env` command or the `env` kwargs of subprocess.Popen().
> > >
> > > Headaches, headaches.
> >
> > I would argue that requiring a particular environment var for a test
> > is not a great idea. Better to pass an argument through the call stack
> > and have the external program run with a new environment containing
> > what is needed.
> Would it be ok to call softhsm utils with softhsm_conf like
> SOFTHSM2_CONF=... softhsm2-util? Then we don't have to mock environment.
> I would still have to set SOFTHSM install dir location with the env variable
> for mkeficapsule in binman generation. I guess it doesn't affect other
> tests as we don't need to change it.
>

So long as it works it seems fine to me.

Regards,
Simon

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: EXTERNAL - [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule
  2026-02-03  8:17               ` Wojciech Dubowik
  2026-02-04  0:22                 ` Simon Glass
@ 2026-02-04  9:49                 ` Quentin Schulz
  2026-02-11 16:09                   ` Quentin Schulz
  1 sibling, 1 reply; 20+ messages in thread
From: Quentin Schulz @ 2026-02-04  9:49 UTC (permalink / raw)
  To: Wojciech Dubowik, Simon Glass; +Cc: Simon Glass, u-boot, trini

Hi Wojciech,

On 2/3/26 9:17 AM, Wojciech Dubowik wrote:
> On Tue, Jan 27, 2026 at 04:00:44PM +1300, Simon Glass wrote:
> Hi Simon,
>> Hi Quentin,
>>
>> On Tue, 27 Jan 2026 at 00:43, Quentin Schulz <quentin.schulz@cherry.de> wrote:
>>>
>>> Hi Simon,
>>>
>>> On 1/22/26 11:46 PM, Simon Glass wrote:
>>>> Hi,
>>>>
>>>> On Thu, 22 Jan 2026 at 02:06, Quentin Schulz <quentin.schulz@cherry.de> wrote:
>>>>>
>>>>> Hi Wojciech,
>>>>>
>>>>> On 1/21/26 1:43 PM, Wojciech Dubowik wrote:
>>>>>> On Tue, Jan 20, 2026 at 04:53:04PM +0100, Quentin Schulz wrote:
>>>>>> Hello Quentin,
>>>>>>> Hi Wojciech,
>>>>>>>
>>>>>>> On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
>>>>> [...]
>>>>>>>> +        os.environ['SOFTHSM2_CONF'] = softhsm2_conf
>>>>>>>
>>>>>>> This is wrong, you'll be messing up with the environment of all tests being
>>>>>>> run in the same thread. You must use the "with
>>>>>>> unittest.mock.patch.dict('os.environ'," implementation I used in
>>>>>>> testFitSignPKCS11Simple.
>>>>>>
>>>>>> Well, I have done so in my V2 but has been commented as wrong by the
>>>>>> first reviewer. I will restore it back.
>>>>>>
>>>>>
>>>>> Indeed, I see Simon asked you to do this in v2 and I missed it. It isn't
>>>>> how we should be doing it.
>>>>>
>>>>> This is likely fine on its own because there's only one test that is now
>>>>> modifying os.environ's SOFTHSM2_CONF but this will be a problem next
>>>>> time a test wants to modify it. I actually hit this issue when
>>>>> developing the PKCS11 fit signing tests as I had two tests modifying the
>>>>> environment.
>>>>>
>>>>> The only trace of it left is the changelog in
>>>>> https://lore.kernel.org/u-boot/20251121-binman-engine-v3-0-b80180aaa783@cherry.de/
>>>>>
>>>>> """
>>>>> - fixed issues due to modification of the environment in tests failing
>>>>>      other tests, by using unittest.mock.patch.dict() on os.environ as
>>>>>      suggested by the unittest.mock doc,
>>>>> """
>>>>>
>>>>> and you can check the diff between the v2 and v3 to check I used to
>>>>> modify the env directly but now mock it instead.
>>>>>
>>>>> Sorry for not catching this, should have answered to Simon in the v2.
>>>>
>>>> In practice we try to set values for various which are important, so
>>>> future tests should explicitly delete the var if needed. But I am OK
>>>
>>> This is not working. See this very simple example (too lazy to use
>>> threading.Lock so synchronization done via time.sleep instead):
>>>
>>> """
>>> #!/usr/bin/env python3
>>>
>>> import os
>>> import time
>>> import threading
>>>
>>>
>>> def thread_func(n):
>>>       if n == 1:
>>>           time.sleep(1)
>>>       print(f'Thread {n} read environ var FOO={os.environ["FOO"]}')
>>>       if n == 1:
>>>           time.sleep(1)
>>>       print(f'Thread {n} set environ var FOO to foo{n}')
>>>       os.environ['FOO'] = f'foo{n}'
>>>       if n == 0:
>>>           time.sleep(5)
>>>       print(f'Thread {n} read environ var FOO={os.environ["FOO"]}')
>>>       if n == 0:
>>>           print(f'Thread {n} removes environ var FOO')
>>>           del os.environ["FOO"]
>>>       else:
>>>           time.sleep(10)
>>>           print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
>>>
>>>
>>> threads = []
>>>
>>> os.environ["FOO"] = "foo"
>>>
>>> for i in range(0, 2):
>>>       t = threading.Thread(target=thread_func, args=(i,))
>>>       threads.append(t)
>>>
>>> for t in threads:
>>>       t.start()
>>>
>>> for t in threads:
>>>       t.join()
>>>
>>> """
>>>
>>> This results in:
>>>
>>> """
>>> Thread 0 read environ var FOO=foo
>>> Thread 0 set environ var FOO to foo0
>>> Thread 1 read environ var FOO=foo0
>>> Thread 1 set environ var FOO to foo1
>>> Thread 1 read environ var FOO=foo1
>>> Thread 0 read environ var FOO=foo1
>>> Thread 0 removes environ var FOO
>>> Thread 1 read environ var FOO=None
>>> """
>>>
>>> You see that modification made to os.environ in a different thread
>>> impacts the other threads. A test should definitely NOT modify anything
>>> for another test, especially not when it's already running.
>>>
>>> So now, I implemented mocking instead (like in my tests for PKCS11 in
>>> tools/binman/ftest.py) because I know it works.
>>>
>>> See:
>>>
>>> """
>>> #!/usr/bin/env python3
>>>
>>> import os
>>> import time
>>> import threading
>>> import unittest.mock
>>>
>>>
>>> def thread_func(n):
>>>       if n == 1:
>>>           time.sleep(1)
>>>       print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
>>>       if n == 1:
>>>           time.sleep(1)
>>>       with unittest.mock.patch.dict('os.environ',
>>>                                     {'FOO': f'foo{n}'}):
>>>           print(f'Thread {n} set environ var FOO to foo{n}')
>>>           if n == 0:
>>>               time.sleep(5)
>>>           print(f'Thread {n} read mocked environ var
>>> FOO={os.environ.get("FOO")}')
>>>           if n == 1:
>>>               time.sleep(6)
>>>       print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
>>>
>>>
>>> threads = []
>>>
>>> for i in range(0, 2):
>>>       t = threading.Thread(target=thread_func, args=(i,))
>>>       threads.append(t)
>>>
>>> for t in threads:
>>>       t.start()
>>>
>>> for t in threads:
>>>       t.join()
>>> """
>>>
>>> Lo and behold, it.... does NOT work???????
>>>
>>> I get:
>>>
>>> """
>>> Thread 0 read environ var FOO=None
>>> Thread 0 set environ var FOO to foo0
>>> Thread 1 read environ var FOO=foo0
>>> Thread 1 set environ var FOO to foo1
>>> Thread 1 read mocked environ var FOO=foo1
>>> Thread 0 read mocked environ var FOO=foo1
>>> Thread 0 read environ var FOO=None
>>> Thread 1 read environ var FOO=foo0
>>> """
>>>
>>> I've read that os.environ isn't thread-safe, due to setenv() in glibc
>>> not being thread-safe:
>>> https://sourceware.org/glibc/manual/latest/html_node/Environment-Access.html#Environment-Access-1
>>>
>>> """
>>> Modifications of environment variables are not allowed in multi-threaded
>>> programs.
>>> """
>>>
>>> I'm not sure if this applies to any other Python implementation than
>>> CPython? But that is likely the one that most people are using.
>>>
>>> So... In short, I'm at a loss, no clue how to fix this (if it is even
>>> fixable). The obvious answer is "spawn multiple processes instead of
>>> multiple threads" but I can guarantee you, you don't want to be going
>>> this route as multiprocessing is a lot of headaches in Python. We could
>>> have the Python thread spawn a subprocess which has a different
>>> environment if we wanted to (via the `env` command for example), but
>>> that means not using binman Python API, rather its CLI. We could have
>>> bintools accept an environment dict that needs to be passed via the
>>> `env` command or the `env` kwargs of subprocess.Popen().
>>>
>>> Headaches, headaches.
>>
>> I would argue that requiring a particular environment var for a test
>> is not a great idea. Better to pass an argument through the call stack
>> and have the external program run with a new environment containing
>> what is needed.
> Would it be ok to call softhsm utils with softhsm_conf like
> SOFTHSM2_CONF=... softhsm2-util? Then we don't have to mock environment.
> I would still have to set SOFTHSM install dir location with the env variable
> for mkeficapsule in binman generation. I guess it doesn't affect other
> tests as we don't need to change it.
> 

I would recommend to hook this to command.run_pipe(..., env=env, ...) in 
tools/binman/bintool.py:Bintool:run_cmd_result() if possible, such that 
it isn't bintool-specific.

We already override the env with the path to our tools, so we could 
simply extend it. I guess we could have a Bintool:set_env() or 
add_to_env() and store the result in some variable (have 
tools.get_env_with_path() in it by default in the object init?) and have 
run_cmd_result() use the bintool object's self.env dict insted of 
calling tools.get_env_with_path()? Then in ftest.py, after b = 
bintool.Bintool.create(), b.add_to_env({'SOFTHSM2_CONF': '...'})?

I'm not sure, maybe there's a reason tools.get_env_with_path() is called 
at the moment the command needs to be run and not when the bintool is 
created, in which case maybe store extra_env and update the env returned 
by tools.get_env_with_path() with that?

Cheers,
Quentin

^ permalink raw reply	[flat|nested] 20+ messages in thread

* Re: EXTERNAL - [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule
  2026-02-04  9:49                 ` Quentin Schulz
@ 2026-02-11 16:09                   ` Quentin Schulz
  0 siblings, 0 replies; 20+ messages in thread
From: Quentin Schulz @ 2026-02-11 16:09 UTC (permalink / raw)
  To: Wojciech Dubowik, Simon Glass; +Cc: Simon Glass, u-boot, trini

Hi Wojciech, Simon,

On 2/4/26 10:49 AM, Quentin Schulz wrote:
> Hi Wojciech,
> 
> On 2/3/26 9:17 AM, Wojciech Dubowik wrote:
>> On Tue, Jan 27, 2026 at 04:00:44PM +1300, Simon Glass wrote:
>> Hi Simon,
>>> Hi Quentin,
>>>
>>> On Tue, 27 Jan 2026 at 00:43, Quentin Schulz 
>>> <quentin.schulz@cherry.de> wrote:
>>>>
>>>> Hi Simon,
>>>>
>>>> On 1/22/26 11:46 PM, Simon Glass wrote:
>>>>> Hi,
>>>>>
>>>>> On Thu, 22 Jan 2026 at 02:06, Quentin Schulz 
>>>>> <quentin.schulz@cherry.de> wrote:
>>>>>>
>>>>>> Hi Wojciech,
>>>>>>
>>>>>> On 1/21/26 1:43 PM, Wojciech Dubowik wrote:
>>>>>>> On Tue, Jan 20, 2026 at 04:53:04PM +0100, Quentin Schulz wrote:
>>>>>>> Hello Quentin,
>>>>>>>> Hi Wojciech,
>>>>>>>>
>>>>>>>> On 1/20/26 9:12 AM, Wojciech Dubowik wrote:
>>>>>> [...]
>>>>>>>>> +        os.environ['SOFTHSM2_CONF'] = softhsm2_conf
>>>>>>>>
>>>>>>>> This is wrong, you'll be messing up with the environment of all 
>>>>>>>> tests being
>>>>>>>> run in the same thread. You must use the "with
>>>>>>>> unittest.mock.patch.dict('os.environ'," implementation I used in
>>>>>>>> testFitSignPKCS11Simple.
>>>>>>>
>>>>>>> Well, I have done so in my V2 but has been commented as wrong by the
>>>>>>> first reviewer. I will restore it back.
>>>>>>>
>>>>>>
>>>>>> Indeed, I see Simon asked you to do this in v2 and I missed it. It 
>>>>>> isn't
>>>>>> how we should be doing it.
>>>>>>
>>>>>> This is likely fine on its own because there's only one test that 
>>>>>> is now
>>>>>> modifying os.environ's SOFTHSM2_CONF but this will be a problem next
>>>>>> time a test wants to modify it. I actually hit this issue when
>>>>>> developing the PKCS11 fit signing tests as I had two tests 
>>>>>> modifying the
>>>>>> environment.
>>>>>>
>>>>>> The only trace of it left is the changelog in
>>>>>> https://eur02.safelinks.protection.outlook.com/? 
>>>>>> url=https%3A%2F%2Flore.kernel.org%2Fu-boot%2F20251121-binman- 
>>>>>> engine-v3-0- 
>>>>>> b80180aaa783%40cherry.de%2F&data=05%7C02%7Cquentin.schulz%40cherry.de%7C4be38001079e40572d5708de63d2c7a6%7C5e0e1b5221b54e7b83bb514ec460677e%7C0%7C0%7C639057954135737078%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=B5%2Bx5iUO54EpovAyDBUCwuq5NgcfcejIoD6Unhdoa%2FM%3D&reserved=0
>>>>>>
>>>>>> """
>>>>>> - fixed issues due to modification of the environment in tests 
>>>>>> failing
>>>>>>      other tests, by using unittest.mock.patch.dict() on 
>>>>>> os.environ as
>>>>>>      suggested by the unittest.mock doc,
>>>>>> """
>>>>>>
>>>>>> and you can check the diff between the v2 and v3 to check I used to
>>>>>> modify the env directly but now mock it instead.
>>>>>>
>>>>>> Sorry for not catching this, should have answered to Simon in the v2.
>>>>>
>>>>> In practice we try to set values for various which are important, so
>>>>> future tests should explicitly delete the var if needed. But I am OK
>>>>
>>>> This is not working. See this very simple example (too lazy to use
>>>> threading.Lock so synchronization done via time.sleep instead):
>>>>
>>>> """
>>>> #!/usr/bin/env python3
>>>>
>>>> import os
>>>> import time
>>>> import threading
>>>>
>>>>
>>>> def thread_func(n):
>>>>       if n == 1:
>>>>           time.sleep(1)
>>>>       print(f'Thread {n} read environ var FOO={os.environ["FOO"]}')
>>>>       if n == 1:
>>>>           time.sleep(1)
>>>>       print(f'Thread {n} set environ var FOO to foo{n}')
>>>>       os.environ['FOO'] = f'foo{n}'
>>>>       if n == 0:
>>>>           time.sleep(5)
>>>>       print(f'Thread {n} read environ var FOO={os.environ["FOO"]}')
>>>>       if n == 0:
>>>>           print(f'Thread {n} removes environ var FOO')
>>>>           del os.environ["FOO"]
>>>>       else:
>>>>           time.sleep(10)
>>>>           print(f'Thread {n} read environ var 
>>>> FOO={os.environ.get("FOO")}')
>>>>
>>>>
>>>> threads = []
>>>>
>>>> os.environ["FOO"] = "foo"
>>>>
>>>> for i in range(0, 2):
>>>>       t = threading.Thread(target=thread_func, args=(i,))
>>>>       threads.append(t)
>>>>
>>>> for t in threads:
>>>>       t.start()
>>>>
>>>> for t in threads:
>>>>       t.join()
>>>>
>>>> """
>>>>
>>>> This results in:
>>>>
>>>> """
>>>> Thread 0 read environ var FOO=foo
>>>> Thread 0 set environ var FOO to foo0
>>>> Thread 1 read environ var FOO=foo0
>>>> Thread 1 set environ var FOO to foo1
>>>> Thread 1 read environ var FOO=foo1
>>>> Thread 0 read environ var FOO=foo1
>>>> Thread 0 removes environ var FOO
>>>> Thread 1 read environ var FOO=None
>>>> """
>>>>
>>>> You see that modification made to os.environ in a different thread
>>>> impacts the other threads. A test should definitely NOT modify anything
>>>> for another test, especially not when it's already running.
>>>>
>>>> So now, I implemented mocking instead (like in my tests for PKCS11 in
>>>> tools/binman/ftest.py) because I know it works.
>>>>
>>>> See:
>>>>
>>>> """
>>>> #!/usr/bin/env python3
>>>>
>>>> import os
>>>> import time
>>>> import threading
>>>> import unittest.mock
>>>>
>>>>
>>>> def thread_func(n):
>>>>       if n == 1:
>>>>           time.sleep(1)
>>>>       print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
>>>>       if n == 1:
>>>>           time.sleep(1)
>>>>       with unittest.mock.patch.dict('os.environ',
>>>>                                     {'FOO': f'foo{n}'}):
>>>>           print(f'Thread {n} set environ var FOO to foo{n}')
>>>>           if n == 0:
>>>>               time.sleep(5)
>>>>           print(f'Thread {n} read mocked environ var
>>>> FOO={os.environ.get("FOO")}')
>>>>           if n == 1:
>>>>               time.sleep(6)
>>>>       print(f'Thread {n} read environ var FOO={os.environ.get("FOO")}')
>>>>
>>>>
>>>> threads = []
>>>>
>>>> for i in range(0, 2):
>>>>       t = threading.Thread(target=thread_func, args=(i,))
>>>>       threads.append(t)
>>>>
>>>> for t in threads:
>>>>       t.start()
>>>>
>>>> for t in threads:
>>>>       t.join()
>>>> """
>>>>
>>>> Lo and behold, it.... does NOT work???????
>>>>
>>>> I get:
>>>>
>>>> """
>>>> Thread 0 read environ var FOO=None
>>>> Thread 0 set environ var FOO to foo0
>>>> Thread 1 read environ var FOO=foo0
>>>> Thread 1 set environ var FOO to foo1
>>>> Thread 1 read mocked environ var FOO=foo1
>>>> Thread 0 read mocked environ var FOO=foo1
>>>> Thread 0 read environ var FOO=None
>>>> Thread 1 read environ var FOO=foo0
>>>> """
>>>>
>>>> I've read that os.environ isn't thread-safe, due to setenv() in glibc
>>>> not being thread-safe:
>>>> https://eur02.safelinks.protection.outlook.com/? 
>>>> url=https%3A%2F%2Fsourceware.org%2Fglibc%2Fmanual%2Flatest%2Fhtml_node%2FEnvironment-Access.html%23Environment-Access-1&data=05%7C02%7Cquentin.schulz%40cherry.de%7C4be38001079e40572d5708de63d2c7a6%7C5e0e1b5221b54e7b83bb514ec460677e%7C0%7C0%7C639057954135756872%7CUnknown%7CTWFpbGZsb3d8eyJFbXB0eU1hcGkiOnRydWUsIlYiOiIwLjAuMDAwMCIsIlAiOiJXaW4zMiIsIkFOIjoiTWFpbCIsIldUIjoyfQ%3D%3D%7C0%7C%7C%7C&sdata=K5LZPB7BkLnUsLU%2BFFK4Bmp1vOXqF8WiSryObldGBAY%3D&reserved=0
>>>>
>>>> """
>>>> Modifications of environment variables are not allowed in multi- 
>>>> threaded
>>>> programs.
>>>> """
>>>>
>>>> I'm not sure if this applies to any other Python implementation than
>>>> CPython? But that is likely the one that most people are using.
>>>>
>>>> So... In short, I'm at a loss, no clue how to fix this (if it is even
>>>> fixable). The obvious answer is "spawn multiple processes instead of
>>>> multiple threads" but I can guarantee you, you don't want to be going

We are already doing this actually and this is what I was missing.

When using binman in parallel (the default when not passing -P1), we 
create a ConcurrentTestSuite whose make_tests function "pointer" 
parameter is fork_for_tests() from concurrencytest. What this does is 
collect all tests to run from all test suites, divide them equally to 
the number of processes then fork the main process that many times. 
Before forking, concurrencytest creates a pipe for communication between 
the child process and main process. The main process keeps track of all 
those pipes and generate a TestCase list (one per child process) that 
ConcurrentTestSuite then consumes. This will then create a thread per 
TestCase. This essentially means that:

- tests are run in different processes,
- but each process runs multiple tests sequentially (not in parallel, 
not in separate threads),
- threads are used to get the output of tests from a different process 
but that's just implementation details,

As such, we don't have to care about thread-safety as that is only used 
by concurrencytest for communication purposes.

However, this does mean that we absolutely need to clean up after 
ourselves. This is the reason why modifications to os.environ were 
breaking stuff in other tests for me, not because of thread-unsafe code, 
but because sequential tests inherit the dirty laundry from the previous 
test and I didn't clean things up properly. This explains why using 
unittest.mock.patch() actually fixed things up, because it's handled by 
the context manager anyway.

So we have the option of using either a contextmanager 
(unittest.mock.patch) for setting the environment and not having to care 
about cleaning it up afterwards. Or have the code snippet setting the 
environment and what uses it in a big try:finally which then cleans up 
before leaving the test.

I don't think propagating the env through Binman (see _DoBinman() which 
runs binman like a Python module thus inheriting the current 
environment) will make things easier or more readable, so please ignore 
the rest of the mail I answer to.

The TL;DR is: no thread-safety issue in tools/binman because we don't 
use threads. Use unittest.mock.patch contextmanager, or write to 
os.environ and undo it before exiting the test (and this needs to be 
done in a try:finally block to avoid exiting early).

Cheers,
Quentin

^ permalink raw reply	[flat|nested] 20+ messages in thread

end of thread, other threads:[~2026-02-11 16:09 UTC | newest]

Thread overview: 20+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2026-01-20  8:11 [PATCH v4 0/6] UEFI Capsule - PKCS11 Support Wojciech Dubowik
2026-01-20  8:11 ` [PATCH v4 1/6] tools: mkeficapsule: Add support for pkcs11 Wojciech Dubowik
2026-01-20  8:11 ` [PATCH v4 2/6] binman: Accept pkcs11 URI tokens for capsule updates Wojciech Dubowik
2026-01-20  8:12 ` [PATCH v4 3/6] tools: Fix long option --dump_sig in mkeficapsule Wojciech Dubowik
2026-01-20 15:11   ` Quentin Schulz
2026-01-20  8:12 ` [PATCH v4 4/6] binman: Add dump signarture option to mkeficapsule Wojciech Dubowik
2026-01-20 15:06   ` Quentin Schulz
2026-01-20  8:12 ` [PATCH v4 5/6] binman: DTS: Add dump-signature option for capsules Wojciech Dubowik
2026-01-20 15:02   ` Quentin Schulz
2026-01-20  8:12 ` [PATCH v4 6/6] test: binman: Add test for pkcs11 signed capsule Wojciech Dubowik
2026-01-20 15:53   ` Quentin Schulz
2026-01-21 12:43     ` EXTERNAL - " Wojciech Dubowik
2026-01-21 13:06       ` Quentin Schulz
2026-01-22 22:46         ` Simon Glass
2026-01-26 11:42           ` Quentin Schulz
2026-01-27  3:00             ` Simon Glass
2026-02-03  8:17               ` Wojciech Dubowik
2026-02-04  0:22                 ` Simon Glass
2026-02-04  9:49                 ` Quentin Schulz
2026-02-11 16:09                   ` Quentin Schulz

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox