readRaw_service_default parsed the IndexRange of each node to read, but
never freed the dimensions of the parsed range. Every raw history read with
an IndexRange leaked them. They are now freed after the backend call.
Port keyless certificate validation into the GDS receiver driver instead
of restoring the removed server implementation. Preserve master's timer
batching, PubSub lifecycle and client HTTP configuration handling.
Adapt Publish cancellation to response tokens and the HEL/fuzz harnesses
to the explicit TCP driver. Keep SKS policy history and custom datatype
ownership intact, including failed replacement configuration cleanup.
Retain TPM error cleanup while enforcing key-handle and nonce lengths.
Validation:
- Full mbedTLS unit-test build with full namespace, GDS, SKS and Query.
- 21 affected suites passed with ASan/UBSan, plus 9 isolated SKS ownership
and lifecycle checks.
- OpenSSL certificate update, GDS information model and client encryption
suites passed with sanitizers.
- OPN, MSG and binary-message fuzzers each completed 100 sanitizer runs.
- Both TPM PubSub policy sources passed compilation checks.
The full SKS client suite timed out after multicast UDP sends reported
"No route to host" on this machine; its affected ownership and lifecycle
cases passed separately. No live TPM hardware validation was performed.
Fix misspellings found with codespell across headers, sources, examples,
tests and the documentation, including "propably" in the nodeset
compiler docs. Identifiers, companion spec names and generated code are
left untouched.
OpenSSL 4 makes ASN1_STRING opaque, returns const pointers from the X509_NAME, X509_CRL and X509_EXTENSION getters, and deprecates the X509_cmp_*time family and X509_NAME_get_text_by_NID. The plugin no longer built: the ASN1_IA5STRING access is a hard error, and -Werror turns the rest into errors too.
Read ASN1 strings through ASN1_STRING_length and ASN1_STRING_get0_data, keep the getter results const, compare the validity period with ASN1_TIME_cmp_time_t and read the common name through its name entry. The self-signed certificate gets its subject from a fresh X509_NAME instead of editing the one X509_get_subject_name returns, and the CSR duplicates the subjectAltName extension instead of pushing one it does not own. All of this exists since OpenSSL 1.1.1, which the plugin already requires.
Add a CI job that builds OpenSSL 4.0.2 from source like the LibreSSL job does. The runner ships OpenSSL 3, so the job sets OPENSSL_ROOT_DIR to make sure it cannot quietly fall back to the system library.
Refs #8493
The TPM AES-CTR policies copied sizeof(pointer) bytes regardless of the key length,
and setKeys read 8 bytes from the 4-byte key nonce, past the end of the SKS key
buffer. Check the lengths and copy the size of the destination.
newContext of the TPM AES-CTR policies leaked the group context on errors, and
the AES128 variant also returned with initLock128_g still held, deadlocking the
next call. Route all error paths through one cleanup.
On a C_EncryptUpdate/Final error the TPM AES-CTR policies returned GOOD with the
buffer unchanged, so the NetworkMessage was sent in plaintext. Return the error,
also for decryption, and check the allocation before the operation is initialized.
certificategroup.h declares it in every configuration, but only the crypto backends defined
it, so a caller failed to link without encryption. Return BadNotSupported like the others.
The `securityPolicy` context is still in use, so it should not be
cleared. This commit stores changes in intermediate keys and
certificates and on failure clears these instead.
asyncOperationTimeout and maxAsyncOperationQueueSize exist in every configuration, but the
JSON config parsed them only with UA_MULTITHREADING >= 100. Without it UInt64Field_parseJson
had no caller and -Werror builds failed.
Re-initializing the MAC context with the same digest for every message
is unnecessary work. The digest is set once at key import and later
initializations pass NULL.
Mirrors the mbedTLS policy (Tables 155-157, HMAC-SHA256 over the whole
NetworkMessage). A group context caches the AES and the MAC context. The
MAC is re-keyed per message as OpenSSL 3.0 cannot restart it otherwise.
The P_SHA256/P_SHA1 key derivation left the derived key stream, the
secret, the seed and the A(n) chain in freed heap memory. The SKS
derives every group key this way. Buffer and context are now cleansed.
UA_ClientConfig_setDefaultEncryption reallocates config->securityPolicies.
A SecureChannel that is already open holds a raw pointer into that array
(e.g. to the "None" policy), so reallocating it -- which may move the
array -- left that pointer dangling and crashed on the next use.
UA_ClientConfig_setDefault now reserves enough headroom for the policies
UA_ClientConfig_setDefaultEncryption adds, so the common case of calling
it once needs no reallocation at all: the array's address never changes,
so an already-open channel's pointer stays valid. This also preserves
in-place updates to a policy already in use (e.g. UA_Server_updateCertificate
mutating a policy's certificate), which a previous per-channel-copy
approach would have hidden from already-open channels.
Fixes#6943
Compare the selected discovery endpoint with its authenticated counterpart
from CreateSession using only the seven fields specified by Part 4. Ignore
optional metadata and the duplicated endpoint certificate, which servers may
omit. Keep the separate certificate validation unchanged.
Reject mismatches by default, with configurable warning and acceptance modes.
Skip the comparison for directly configured endpoints. Cover omitted metadata,
endpoint reordering, mismatch handling and trusted endpoint configuration.
Search the certificate input for the next PEM header with the haystack and needle in the correct order. Use a length-bounded helper so partial headers at the end cannot read past the input.
The exact-comparison change in f4c2dc2aa made an absent URI compare equal to an empty ApplicationUri. Require a non-empty URI SAN before accepting the certificate.
The mbedTLS RSA-PSS verifier consumes one complete RSA modulus but its low-level API does not take an input length. Reject malformed ByteStrings before invoking it so a short application signature cannot cause an out-of-bounds read.
Reject malformed raw ECDSA signatures before converting their r and s coordinates. Short signatures could otherwise cause out-of-bounds reads in the OpenSSL backend and an underflowing bounds check in the mbedTLS backend; oversized mbedTLS signatures could overflow the fixed DER buffer.
Require serialized PKCS#11 handles and the specified four-byte key nonce before copying key material into the TPM channel context. This rejects incompatible raw SKS keys and prevents out-of-bounds reads from undersized inputs.
encrypt_pubsub_aes128ctr_tpm/decrypt_pubsub_aes128ctr_tpm and their
AES-256-CTR counterparts computed the block-aligned processing size
(sizeToEncrypt/sizeToDecrypt) by rounding data->length up to the next
16-byte boundary, but stored the result in a CK_BYTE. Two distinct bugs
followed from this:
- For a data->length whose rounded-up value exceeds 255, the 8-bit value
wraps around, so far fewer bytes than intended are processed while the
function still returns UA_STATUSCODE_GOOD.
- For any data->length that is not itself a multiple of 16, the rounded-up
size is larger than the caller's actual buffer. The PKCS#11 update calls
then read block-sized chunks directly from data->data past its end, and
the final loop that copies the result back writes that same rounded-up
size into data->data, overflowing the caller's allocation.
AES-CTR is a stream cipher and never needs block-aligned input. Fix this
by processing exactly data->length bytes throughout, using size_t/CK_ULONG
for all lengths instead of CK_BYTE: each PKCS#11 update call is chunked at
MAX_ENCRYPTION_SIZE bytes with a shorter final chunk when data->length is
not itself a multiple of it, operating on the caller's buffer directly
with no rounding, no padding, and no risk of the output exceeding the
caller's allocation.
Co-authored-by: Luigi Colluto <gigioneggiando@users.noreply.github.com>
Internal Vulnerability Advisory: open62541-SA-2026-0033
The OpenSSL HMAC-SHA1/SHA256 verify helpers compared the recomputed
MAC against the peer-supplied signature with UA_ByteString_equal(),
which reduces to a plain memcmp(). memcmp() carries no constant-time
guarantee and, on a byte-at-a-time implementation (the norm on the
small-footprint libcs open62541 is routinely built against), returns
as soon as it finds a differing byte -- a timing side channel a
remote peer can use to recover a valid MAC byte-by-byte.
Switch to UA_constantTimeEqual(), already used correctly by every
other symmetric SecurityPolicy (mbedTLS Basic128Rsa15, Basic256,
Basic256Sha256, Aes128Sha256RsaOaep). Since it takes a single length
and reads that many bytes from both buffers, first validate
signature->length against the expected MAC length, mirroring the
pattern used by the project's mbedTLS RSA policy Verify functions.
The digest length is fixed by the algorithm and policy, not attacker
data, so this early-return introduces no new timing side channel.
Internal Vulnerability Advisory: open62541-SA-2026-0038
Co-authored-by: Luigi Colluto <gigioneggiando@users.noreply.github.com>
encrypt_sp_pubsub_aes128ctr_tpm/decrypt_sp_pubsub_aes128ctr_tpm and their
AES-256-CTR counterparts computed the block-aligned processing size
(sizeToEncrypt/sizeToDecrypt) by rounding data->length up to the next
16-byte boundary, but stored the result in a CK_BYTE. Two distinct bugs
followed from this:
- For a data->length whose rounded-up value exceeds 255, the 8-bit value
wraps around, so far fewer bytes than intended are processed while the
function still returns UA_STATUSCODE_GOOD.
- For any data->length that is not itself a multiple of 16, the rounded-up
size is larger than the caller's actual buffer. The PKCS#11 update calls
then read block-sized chunks directly from data->data past its end, and
the final loop that copies the result back writes that same rounded-up
size into data->data, overflowing the caller's allocation.
AES-CTR is a stream cipher and never needs block-aligned input. Fix this
by processing exactly data->length bytes throughout, using size_t/CK_ULONG
for all lengths instead of CK_BYTE: each PKCS#11 update call is chunked at
MAX_ENCRYPTION_SIZE bytes with a shorter final chunk when data->length is
not itself a multiple of it, operating on the caller's buffer directly
with no rounding, no padding, and no risk of the output exceeding the
caller's allocation.
Co-authored-by: Luigi Colluto <gigioneggiando@users.noreply.github.com>
Internal Vulnerability Advisory: open62541-SA-2026-0033
Reject circular HistoryRead continuation points whose skip exceeds the available result count, and cap the backend copy to the allocated response capacity. A forged skip could otherwise underflow the copy length and cause a heap-buffer-overflow when the response size was capped.
The default HistoryDatabase checked only the Variable node AccessLevel. Apply the session-specific AccessControl getUserAccessLevel result as well so clients denied HISTORYREAD or HISTORYWRITE cannot read, insert, replace, update or delete stored values.
Return BadOutOfMemory before invoking the read backend or writing HistoryUpdate operation results when their per-result allocations fail. This prevents null pointers from reaching the backend and avoids a null write under memory pressure.
gmtime and inet_ntoa return a pointer to a static buffer shared by all threads. The mbedTLS certificate creation now derives its validity dates from UA_DateTime_now/UA_DateTime_toStruct, which also keeps the expiry date in UTC (mktime interpreted the gmtime result as local time). Zephyr uses gmtime_r and lwIP ip4addr_ntoa_r.
Fixes#7997
UA_DataValue_backend_copyRange() performed a shallow memcpy of the
source UA_DataValue before attempting the range copy. Because the
destination's UA_Variant fields were not reinitialized first, a
failing UA_Variant_copyRange() (for example when the client-supplied
indexRange exceeds UA_MAX_ARRAY_DIMS) left the destination value as an
uninitialized alias of the backend-owned storage instead of an
independent copy.
copyDataValues_backend_memory() also discarded the status code from
both UA_DataValue_backend_copyRange() and UA_DataValue_copy(), so the
aliased, partially-copied value was still counted as a successful
result and forwarded to the HistoryRead response. Freeing that
response later released the backend's live storage, leaving the
backend with a dangling pointer that was dereferenced on a subsequent
HistoryRead.
Reinitialize the destination variant before the range copy and clear
it again on failure, mirroring the safe pattern already used by
UA_DataValue_copyRange(). Propagate copy failures out of
copyDataValues_backend_memory() immediately instead of counting a
failed entry as provided.
Internal Vulnerability Advisory: open62541-SA-2026-0021
Co-authored-by: gff-cw <gff-cw@users.noreply.github.com>
Use the common name when available and fall back to the subject for CRLs and
certificates without a common name.
Check path formatting, propagate storage errors, and clean up trust-list data
on every exit path.