Skip to content

Commit f68355e

Browse files
committed
update documentation and tests
1 parent e6920ae commit f68355e

3 files changed

Lines changed: 137 additions & 15 deletions

File tree

CMakeLists.txt

Lines changed: 8 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -793,8 +793,11 @@ set(HDF5_PLUGIN_KEYSTORE_DIR "" CACHE PATH
793793
"Directory containing trusted public keys (.pem files) for plugin verification")
794794
# Security Note: For production deployments where environment variables may be
795795
# controllable by untrusted users (e.g., HPC clusters, multi-tenant systems),
796-
# consider enabling HDF5_LOCK_PLUGIN_KEYSTORE to prevent runtime override of
797-
# the keystore directory via the HDF5_PLUGIN_KEYSTORE environment variable.
796+
# you can prevent runtime override of the keystore directory via:
797+
# 1. Compile-time: Enable HDF5_LOCK_PLUGIN_KEYSTORE (requires rebuild)
798+
# 2. Runtime: Create lock file (works with pre-built binaries):
799+
# - Unix/Linux: /etc/hdf5/lock_keystore
800+
# - Windows: C:\ProgramData\HDF_Group\HDF5\lock_keystore
798801

799802
# Path to public key file for plugin signature verification (backward compatibility)
800803
set(HDF5_PLUGIN_PUBLIC_KEY_FILE "" CACHE FILEPATH
@@ -804,6 +807,9 @@ set(HDF5_PLUGIN_PUBLIC_KEY_FILE "" CACHE FILEPATH
804807
option(HDF5_PLUGIN_KEYSTORE_DEBUG "Enable debug output for plugin KeyStore initialization" OFF)
805808

806809
# Security: Disable environment variable override (prevents runtime keystore redirection)
810+
# Note: Sysadmins can also lock the keystore at runtime without recompiling by
811+
# creating a lock file (/etc/hdf5/lock_keystore on Unix, or
812+
# C:\ProgramData\HDF_Group\HDF5\lock_keystore on Windows).
807813
option(HDF5_LOCK_PLUGIN_KEYSTORE "Disable HDF5_PLUGIN_KEYSTORE environment variable override (security hardening)" OFF)
808814
mark_as_advanced(HDF5_LOCK_PLUGIN_KEYSTORE)
809815

release_docs/PLUGIN_SIGNATURE_README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1067,6 +1067,52 @@ The current system makes conscious trade-offs:
10671067
- Ensure keystore directory is not world-writable
10681068
- Use NTFS permissions to restrict write access
10691069

1070+
6. **Environment Variable Security (Production Deployments)**
1071+
1072+
In multi-tenant or HPC environments where untrusted users can control environment variables, you can lock the keystore location to prevent them from overriding `HDF5_PLUGIN_KEYSTORE` with a malicious keystore.
1073+
1074+
**Runtime Lock (No Recompilation Required):**
1075+
```bash
1076+
# Unix/Linux: Create lock file to disable environment variable override
1077+
sudo mkdir -p /etc/hdf5
1078+
sudo touch /etc/hdf5/lock_keystore
1079+
1080+
# Windows: Create lock file
1081+
mkdir "C:\ProgramData\HDF_Group\HDF5"
1082+
type nul > "C:\ProgramData\HDF_Group\HDF5\lock_keystore"
1083+
```
1084+
1085+
**Compile-Time Lock (Requires Rebuild):**
1086+
```bash
1087+
# Configure HDF5 with locked keystore (completely disables env var)
1088+
cmake -DHDF5_LOCK_PLUGIN_KEYSTORE=ON \
1089+
-DHDF5_PLUGIN_KEYSTORE_DIR=/etc/hdf5/keystore \
1090+
/path/to/hdf5/source
1091+
```
1092+
1093+
**When to Use:**
1094+
- ✅ HPC clusters with untrusted users
1095+
- ✅ Multi-tenant systems
1096+
- ✅ Production servers with strict security requirements
1097+
- ✅ Pre-built binaries distributed to security-critical environments
1098+
1099+
**How It Works:**
1100+
1. If lock file exists, `HDF5_PLUGIN_KEYSTORE` environment variable is ignored
1101+
2. HDF5 will only use the compile-time configured keystore (`HDF5_PLUGIN_KEYSTORE_DIR`)
1102+
3. Prevents privilege escalation via keystore override attacks
1103+
4. System administrators can apply this to pre-built HDF5 libraries without recompilation
1104+
1105+
**Verification:**
1106+
```bash
1107+
# Test that environment variable is ignored after locking
1108+
export HDF5_PLUGIN_KEYSTORE=/tmp/fake_keystore
1109+
1110+
# Enable debug output to see which keystore is used
1111+
HDF5_PLUGIN_KEYSTORE_DEBUG=1 h5dump test_file.h5
1112+
1113+
# Expected output: "Skipping HDF5_PLUGIN_KEYSTORE environment variable (locked by sysadmin)"
1114+
```
1115+
10701116
---
10711117

10721118
## Troubleshooting

src/H5PLsig.c

Lines changed: 83 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,15 @@
6161
*
6262
* If plugin loading is ever made to bypass the global lock, these data structures will
6363
* require explicit mutex protection or atomic operations.
64+
*
65+
* TODO: If H5PL__load is ever refactored to support fine-grained locking or lock-free
66+
* concurrent plugin loading, wrap these static globals in a struct protected by
67+
* a dedicated mutex (e.g., H5PL_sig_lock_g). This affects:
68+
* - H5PL_keystore_g and related counters (lines 73-76)
69+
* - H5PL_revoked_sigs_g and related counters (lines 84-87)
70+
* - H5PL_sig_cache_g and related counters (lines 97-99)
71+
* All read/write operations on these variables must be synchronized if the
72+
* global library lock is removed or bypassed for plugin operations.
6473
*/
6574

6675
/* KeyStore entry for storing multiple trusted public keys */
@@ -69,7 +78,9 @@ typedef struct H5PL_keystore_entry_t {
6978
char *source; /* Key source (filename or "embedded") for debugging */
7079
} H5PL_keystore_entry_t;
7180

72-
/* KeyStore for signature verification */
81+
/* KeyStore for signature verification
82+
* TODO (Thread Safety): Requires mutex protection if global lock is removed
83+
*/
7384
static H5PL_keystore_entry_t *H5PL_keystore_g = NULL;
7485
static size_t H5PL_keystore_count_g = 0;
7586
static size_t H5PL_keystore_capacity_g = 0;
@@ -81,6 +92,7 @@ typedef struct H5PL_revoked_signature_t {
8192
unsigned char hash[H5PL_SIGNATURE_HASH_SIZE]; /* SHA-256 hash of signature */
8293
} H5PL_revoked_signature_t;
8394

95+
/* TODO (Thread Safety): Requires mutex protection if global lock is removed */
8496
static H5PL_revoked_signature_t *H5PL_revoked_sigs_g = NULL;
8597
static size_t H5PL_revoked_sigs_count_g = 0;
8698
static size_t H5PL_revoked_sigs_capacity_g = 0;
@@ -93,7 +105,9 @@ typedef struct H5PL_signature_cache_entry_t {
93105
bool verified; /* Verification status (true=success, false=failure) */
94106
} H5PL_signature_cache_entry_t;
95107

96-
/* Signature verification cache */
108+
/* Signature verification cache
109+
* TODO (Thread Safety): Requires mutex protection if global lock is removed
110+
*/
97111
static H5PL_signature_cache_entry_t *H5PL_sig_cache_g = NULL;
98112
static size_t H5PL_sig_cache_count_g = 0;
99113
static size_t H5PL_sig_cache_capacity_g = 0;
@@ -110,8 +124,8 @@ static size_t H5PL_sig_cache_capacity_g = 0;
110124
/* Maximum plugin file size (1GB - prevents unreasonable allocations) */
111125
#define H5PL_MAX_PLUGIN_SIZE ((HDoff_t)(1024 * 1024 * 1024))
112126

113-
/* I/O chunk size for verification (64KB - matches h5sign) */
114-
#define H5PL_VERIFY_CHUNK_SIZE ((size_t)(64 * 1024))
127+
/* I/O chunk size for verification (1MB - optimized for modern I/O subsystems) */
128+
#define H5PL_VERIFY_CHUNK_SIZE ((size_t)(1024 * 1024))
115129

116130
/* Memory threshold for multi-key optimization (16MB) */
117131
#define H5PL_MEMORY_THRESHOLD ((size_t)(16 * 1024 * 1024))
@@ -736,6 +750,54 @@ H5PL__load_keys_from_directory(const char *dir_path)
736750
FUNC_LEAVE_NOAPI(ret_value)
737751
} /* end H5PL__load_keys_from_directory() */
738752

753+
/*-------------------------------------------------------------------------
754+
* Function: H5PL__is_keystore_locked
755+
*
756+
* Purpose: Check if keystore environment variable override is locked
757+
* by presence of system lock file
758+
*
759+
* Lock file locations:
760+
* - Unix/Linux: /etc/hdf5/lock_keystore
761+
* - Windows: C:\ProgramData\HDF_Group\HDF5\lock_keystore
762+
*
763+
* This allows system administrators to disable the
764+
* HDF5_PLUGIN_KEYSTORE environment variable on pre-built
765+
* binaries without recompiling HDF5.
766+
*
767+
* Return: true if locked, false otherwise
768+
*-------------------------------------------------------------------------
769+
*/
770+
static bool
771+
H5PL__is_keystore_locked(void)
772+
{
773+
h5_stat_t st;
774+
bool ret_value = false;
775+
776+
FUNC_ENTER_PACKAGE_NOERR
777+
778+
#ifndef H5_HAVE_WIN32_API
779+
/* Unix/Linux: Check for /etc/hdf5/lock_keystore */
780+
if (HDstat("/etc/hdf5/lock_keystore", &st) == 0) {
781+
ret_value = true;
782+
#ifdef H5PL_DEBUG_KEYSTORE
783+
fprintf(stderr, "HDF5 KeyStore: Environment variable override disabled by /etc/hdf5/lock_keystore\n");
784+
#endif
785+
}
786+
#else
787+
/* Windows: Check for C:\ProgramData\HDF_Group\HDF5\lock_keystore */
788+
if (HDstat("C:\\ProgramData\\HDF_Group\\HDF5\\lock_keystore", &st) == 0) {
789+
ret_value = true;
790+
#ifdef H5PL_DEBUG_KEYSTORE
791+
fprintf(stderr,
792+
"HDF5 KeyStore: Environment variable override disabled by "
793+
"C:\\ProgramData\\HDF_Group\\HDF5\\lock_keystore\n");
794+
#endif
795+
}
796+
#endif
797+
798+
FUNC_LEAVE_NOAPI(ret_value)
799+
} /* end H5PL__is_keystore_locked() */
800+
739801
/*-------------------------------------------------------------------------
740802
* Function: H5PL__init_keystore
741803
*
@@ -771,17 +833,25 @@ H5PL__init_keystore(void)
771833

772834
/* 1. Check environment variable (highest priority) */
773835
#ifndef H5PL_DISABLE_ENV_KEYSTORE
774-
if (NULL != (env_keystore = getenv("HDF5_PLUGIN_KEYSTORE"))) {
775-
if (H5PL__load_keys_from_directory(env_keystore) < 0)
776-
HGOTO_ERROR(H5E_PLUGIN, H5E_CANTLOAD, FAIL, "failed to load keys from HDF5_PLUGIN_KEYSTORE: %s",
777-
env_keystore);
778-
keys_loaded = true;
779-
780-
/* Load revoked signatures from same directory */
781-
if (H5PL__load_revoked_signatures(env_keystore) < 0) {
782-
/* Non-fatal - continue even if revoked signatures fail to load */
836+
/* Check if environment variable override is locked by runtime lock file */
837+
if (!H5PL__is_keystore_locked()) {
838+
if (NULL != (env_keystore = getenv("HDF5_PLUGIN_KEYSTORE"))) {
839+
if (H5PL__load_keys_from_directory(env_keystore) < 0)
840+
HGOTO_ERROR(H5E_PLUGIN, H5E_CANTLOAD, FAIL, "failed to load keys from HDF5_PLUGIN_KEYSTORE: %s",
841+
env_keystore);
842+
keys_loaded = true;
843+
844+
/* Load revoked signatures from same directory */
845+
if (H5PL__load_revoked_signatures(env_keystore) < 0) {
846+
/* Non-fatal - continue even if revoked signatures fail to load */
847+
}
783848
}
784849
}
850+
#ifdef H5PL_DEBUG_KEYSTORE
851+
else {
852+
fprintf(stderr, "HDF5 KeyStore: Skipping HDF5_PLUGIN_KEYSTORE environment variable (locked by sysadmin)\n");
853+
}
854+
#endif
785855
#else
786856
/* Environment variable override disabled at compile time (security hardening) */
787857
env_keystore = NULL; /* Suppress unused variable warning */

0 commit comments

Comments
 (0)