Commit Graph

13478 Commits

Author SHA1 Message Date
Niels Beier 32c514b4c7 test(server): check the StatusChange after a transfer without Publish 2026-10-05 23:30:06 +02:00
Niels Beier 0e3bc3b9bb fix(server): send the transfer StatusChange with the next Publish
Without a queued PublishRequest, another late Subscription could
answer the next Publish of the old Session first.
2026-10-05 23:30:06 +02:00
o6-jpfr 19f5a2f153 Merge pull request #8522 from o6-automation/merge_15_master
Merge 1.5 into master
2026-10-04 20:07:08 +02:00
Julius Pfrommer 7cc1abc1ce test: Use atomic stores in browse access-control regression
Match the server loop atomic reads when starting and stopping the test server. This fixes the ThreadSanitizer race introduced by the 1.5 merge.

Validation: reproduced the race locally before the fix; check_accesscontrol passes with ThreadSanitizer after the fix.
2026-10-04 18:45:43 +02:00
Julius Pfrommer 6901aedc07 Merge remote-tracking branch 'origin/1.5' into merge_15_master
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.
2026-10-04 16:49:17 +02:00
Julius Pfrommer f13a4eb686 Merge pull request #8521 from o6-automation/merge_14_15
Merge 1.4 to 1.5
2026-10-04 16:32:38 +02:00
Julius Pfrommer 2a70e31f17 Merge remote-tracking branch 'origin/1.4' into merge_14_15
Adapt the incoming fixes and regressions to the 1.5 encoding options,
nodestore, connection-manager, PubSub and session APIs. Preserve the
strict HEL EndpointUrl limit of less than 4096 bytes.

Cover cancellation of interleaved queued Publish requests, including
response handles, cancellation counts and a successful CancelResponse.
Extend the nodestore mask regression to 1.5's derived attributes and
invalid attribute identifiers.

Validation: full OpenSSL test build; 252 checks passed across the builtin
types, server, attribute services, client subscriptions and mbedTLS PubSub
decryption suites, with sanitizers enabled.
2026-10-04 15:02:49 +02:00
Julius Pfrommer 1cb019544a fix(types): normalize reserved Variant ids to ByteString
Decode wire type ids 26 through 31 with the existing ByteString handling for scalars and arrays. Preserve payload and dimensions while discarding the reserved tag; subsequent encoding uses ordinary ByteString id 15. No public API changes.

Add regression coverage in the existing datatype harness for all reserved ids, null and empty values, arrays and matrices, normalized re-encoding, truncated input, and rejection of ids 32 through 63.
2026-10-04 13:06:33 +02:00
Julius Pfrommer dcc3d80b81 fix(server): reject trailing data in HEL messages
Require decoding the HEL body to consume the entire message. Add handshake regressions for one-byte and eight-byte suffixes, checking BadDecodingError and connection closure.
2026-10-04 13:06:33 +02:00
Julius Pfrommer e74c32ef29 fix(server): enforce the HEL EndpointUrl length limit
Reject EndpointUrls longer than 4096 bytes with BadTcpEndpointUrlInvalid and propagate allocation errors when copying the accepted URL. Add handshake tests for the boundary, oversized URLs, connection closure, and negotiation of a higher requested protocol version down to zero.
2026-10-04 13:06:33 +02:00
Julius Pfrommer 304fb74b09 fix(types): reject array dimensions on scalar Variants
Reject the dimensions flag when the array flag is absent, including inputs without trailing bytes. Add malformed-input regression tests and a valid scalar control.
2026-10-04 13:06:33 +02:00
Julius Pfrommer 3fe49b7e63 fix(types): keep null binary encoding identifiers opaque
Do not resolve the null encoding identifier to Boolean or to a custom datatype. Preserve opaque ExtensionObject bodies in scalar and array Variants, while allowing numeric identifier zero in other namespaces. Add regression tests for these cases and ordinary null ExtensionObjects.
2026-10-04 13:06:33 +02:00
Julius Pfrommer 461c1ff7d6 fix(server): Use the AuditEntryId supplied by the client in the RequestHeader 2026-10-04 10:57:33 +02:00
Julius Pfrommer 038a2cf55d refactor(server): Keep a server-wide pointer to the RequestHeader of the current request 2026-10-04 10:57:33 +02:00
Christoffer Lind acad45bdfb fix(server): pass the mask of the read attribute to the nodestore
attr2mask is indexed by the AttributeId, but the table starts at index
0 while AttributeIds start at 1. Each Read therefore asks the
Nodestore for the attribute that follows the requested one, for
example DataType instead of Value.
2026-10-02 20:19:56 +02:00
StalderT e786522fd5 fix(pubsub): confine the PromotedFields and follow the declared DataSetMessage offsets
The UADP decoder read the PromotedFields length and the Sizes array of the PayloadHeader but held the decoding to neither. A PromotedField Variant could run past the declared length into the security header or the payload, and a DataSetMessage started wherever the previous one happened to stop rather than at the offset the sizes imply, so a subscriber could attribute fields to the wrong DataSetWriterId and accept a DataSetMessage that the declared layout does not contain. Every read stayed inside the receive buffer, so this is a conformance defect and not a memory safety one.

The PromotedFields now decode against an end position set to their declared length, and a DataSetMessage whose size came from the PayloadHeader is followed at the declared offset. The size inferred for a single DataSetMessage is deliberately left out of this: it is an upper bound that still holds the SecurityFooter, so moving to it would swallow the footer.

The loop over the DataSetMessages now stops at the first failure instead of accumulating statuses with |=, which merges two error codes into a third one that nothing ever returned.

Part 14 v1.05, 7.2.4.4.2 and 7.2.4.5.3. Refs #8509.
2026-10-02 20:17:56 +02:00
Andreas Ebner f8a962278d fix(types): UA_Variant_setArray with length zero sets an empty array
A variant with length zero and a data pointer is a scalar. With an
array of length zero, UA_Variant_setArray kept the pointer, so the
variant became a scalar. Writing it to an array variable failed, and
clearing it freed the array as a single element.
UA_Variant_setArrayCopy already uses UA_EMPTY_ARRAY_SENTINEL in this
case. UA_Variant_setArray now does the same and does not take over the
array. NULL with length zero remains a null array.

The GDS Receiver writes its CertificateTypes with UA_Variant_setArray.
Since #8506 the list is empty on a server without an RSA endpoint. The
write failed, the receiver did not start and its methods were unbound.
2026-10-02 20:16:50 +02:00
Andreas Ebner fc0bf5614e fix(drivers): let the owning session continue its transaction with Open
Open was refused while any transaction was pending, also for the session
that owns it, so UpdateCertificate followed by a TrustList write failed.
Part 12 §7.8.2.2 says a write-mode Open continues an existing transaction.
Allow that for the owner unless ApplyChanges is queued; OpenWithMasks and
other sessions still get BadTransactionPending. Close discards only the
written data and keeps the staged changes. ApplyChanges refuses to run
while any TrustList is open for writing (§7.10.9).
2026-10-01 16:09:59 +02:00
Andreas Ebner af94d97375 fix(drivers): advertise and accept only configured certificate types
CertificateTypes always listed RsaMin and RsaSha256. With no RsaMin policy
configured (the default), an RsaMin UpdateCertificate and ApplyChanges
answered Good without changing anything, and CreateSigningRequest returned
an empty CSR. Now only types with an endpoint policy in the
DefaultApplicationGroup are advertised. UpdateCertificate and
CreateSigningRequest refuse the others with BadInvalidArgument (Part 12
7.10.5, 7.10.10), and applying a type that no policy uses is an error.
2026-10-01 16:09:59 +02:00
Andreas Ebner 7395a9d1dd fix(drivers): read the SetPosition offset as UInt64
SetPosition read its UInt64 Position argument through a UInt32 pointer.
Offsets above 4 GiB were truncated, and big-endian targets read the wrong
half. Read the argument with its declared type.
2026-10-01 16:09:59 +02:00
Andreas Ebner e1a2e07cda fix(drivers): wipe staged private keys before freeing them
Private keys staged by UpdateCertificate were freed without zeroing, when
they were replaced, on allocation failures and when the transaction ended.
Zero them with UA_ByteString_memZero first, as the CSR path already does.
2026-10-01 16:09:59 +02:00
Andreas Ebner 128eb7868a fix(drivers): clear the transaction when RemoveCertificate fails
RemoveCertificate creates a transaction and applies it right away. When
the TrustList could not be copied into the transaction, it returned with
the transaction still pending and no session watchdog installed, so all
later TrustList changes were refused. Clear the transaction on that path.
2026-10-01 16:09:59 +02:00
Andreas Ebner b822aae1ff fix(drivers): refuse certificate updates while ApplyChanges is queued
ApplyChanges defers the completion of the transaction to a delayed
callback. An UpdateCertificate in between was accepted into the queued
transaction and then silently discarded with it. Refuse it with
BadTransactionPending until the transaction is completed.
2026-10-01 16:09:59 +02:00
Andreas Ebner 10fb09e051 fix(drivers): answer BadNothingToDo for ApplyChanges without a transaction
ApplyChanges compared the caller with the owner of the transaction before
checking its state. A fresh transaction has no owner, so every caller got
BadUserAccessDenied. Check the state first, as Part 12 §7.10.9 requires.
2026-10-01 16:09:59 +02:00
StalderT 228355a4fa fix(pubsub): clear the PromotedField that failed to decode
The error path of the PromotedFields loop deletes counter entries, but the Variant that just failed sits at index counter and keeps the members it had already allocated. Decoding the eight byte NetworkMessage 81 80 02 01 00 06 2a 00, where the Int32 Variant runs past the end of the receive buffer, leaks eight bytes, so a subscriber leaks memory for every malformed message carrying PromotedFields. The line dates from 6d923ff37. The array comes from a zeroing allocation and counter is always below the allocated space at that point, so clearing one more entry is safe even when the decoder wrote nothing into it.

The test asserts nothing about the allocation and the check suite stays green either way, with 42 checks passing before and after. It is the Valgrind and AddressSanitizer stages that turn red without the fix.

Found while working on #8514, which made this error path reachable from the byte sequences reported in #8509.
2026-10-01 15:27:03 +02:00
Andreas Ebner 461e46182c fix(pubsub): clean up failed writer creation and deferred deletion
Roll back writers whose information model representation fails. A deferred
connection or WriterGroup delete asks the lifecycle callback only once,
retries vetoed writers, rejects new ones and completes a manager stop.
2026-09-30 23:55:58 +02:00
Andreas Ebner 2810e1ec1b fix(pubsub): never free a PublishedDataSet with connected writers
The removal freed the PDS even when removing a connected writer failed and left
a dangling connectedDataSet. Check the writer groups first and stop on a failed
writer removal, so the PDS is only freed without writers.
2026-09-30 23:55:58 +02:00
Andreas Ebner 2ecedb825c fix(drivers): update the None endpoint certificate in UpdateCertificate
After a certificate push the SecurityPolicy#None endpoint kept presenting
the replaced certificate, while the encrypted endpoints showed the new one.
A None SecurityPolicy with a replaced certificate is now updated as well.
2026-09-29 23:11:55 +02:00
Andreas Ebner a34a68357e fix(drivers): accept a null CertificateGroupId in UpdateCertificate
Part 12 says a null CertificateGroupId selects the DefaultApplicationGroup.
The GDS receiver rejected it with Bad_NotSupported, so GDS pushes failed.
An unsupported group or type now returns Bad_InvalidArgument as specified.
2026-09-29 20:31:27 +02:00
StalderT ecbdf7c263 fix(server): reject a HEL whose EndpointUrl is 4096 bytes or longer
OPC UA Part 6, 7.1.2.3 requires the EndpointUrl of a Hello message to be less than 4096 bytes, and the server to answer Bad_TcpEndpointUrlInvalid and close the connection otherwise. processHEL copied the URL into the channel without any check, so any length up to the receive buffer got an ACK and stayed in the channel until it closed. The result of UA_String_copy was ignored as well.

Return BadTcpEndpointUrlInvalid for 4096 bytes or more, which the existing error path in processSecureChannelMessage turns into an ERR and a closed connection, and check the copy.

Refs #8499
2026-09-29 09:58:19 +02:00
Andreas Ebner 918f2cfd4b fix(docs): typos in comments, docs and messages
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.
2026-09-28 16:20:44 +02:00
ChiouYouJia 5ae7657e58 feat(server): add SourceNode to the write audit notification payload
The AUDIT_UPDATE_WRITE application notification carries the AttributeId,
IndexRange, NewValue and OldValue of a write, but not the NodeId of the written
node. That NodeId is only set as the SourceNode of the generated
AuditWriteUpdateEvent, so an auditNotificationCallback cannot tell which node
was written without additionally subscribing to the events.

Add /SourceNode (the BaseEventType field name) to the payload. The generated
events are unchanged: the event-field lookup already resolves /SourceNode to
the same NodeId by default, and the RBAC check uses the EventDescription.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
2026-09-27 22:41:58 +02:00
StalderT e23ad2f4ee feat(plugins): support OpenSSL 4
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
2026-09-27 17:40:24 +02:00
Andreas Ebner 44b527efb7 fix(plugins): bound the key handle and nonce copies of the PKCS#11 PubSub policies
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.
2026-09-26 19:17:01 +02:00
Andreas Ebner 45ccdb1fda fix(plugins): release the lock and context on PKCS#11 PubSub setup errors
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.
2026-09-26 19:17:01 +02:00
Andreas Ebner c0be293ee5 fix(plugins): fail closed when a PKCS#11 PubSub encryption fails
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.
2026-09-26 19:17:01 +02:00
Andreas Ebner c021a5a772 fix(plugins): add the UA_CertificateUtils_getKeySize stub without encryption
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.
2026-09-26 13:52:32 +02:00
Andreas Ebner 202c6028a5 fix(server): reject an unparseable certificate in UpdateCertificate
Without a PrivateKey (after CreateSigningRequest), UpdateCertificate staged any bytes as
the new certificate and only ApplyChanges failed. Check that the certificate parses and
answer BadCertificateInvalid, also in UA_Server_updateCertificate when encryption is built.
2026-09-26 13:52:32 +02:00
Julius Pfrommer 3d9db7b33d feat(pubsub): Add a unit test for underflow by an unchecked signature-length in verifyAndDecryptNetworkMessage 2026-09-25 19:28:21 +02:00
Andreas Ebner 8248d142ca fix(server): send a PublishResponse when cancelling a queued Publish
Service_Cancel sent the CancelResponse to the subscriber, typed as a
PublishResponse. Both live in the shared UA_Response union, which is only
initialized for the response type of the request being processed. Encoding a
CancelResponse as a PublishResponse therefore read the uninitialized remainder
of the union as availableSequenceNumbers, notificationMessage, results and
diagnosticInfos: array lengths and pointers that are dereferenced and put on
the wire. Any client holding a subscription can reach this.

It also overwrote the CancelResponse's serviceResult, so a Cancel that did
cancel a queued Publish answered BadRequestCancelledByClient instead of Good.

Send the queued PublishResponse instead. It is fully initialized, it already
carries the matching requestHandle, and it is what the subscriber expects.

Present since the Cancel service was implemented in 9bc6318b6 (v1.4.0-rc1).
2026-09-25 19:27:48 +02:00
StalderT 68352ed713 fix(arch): keep the timer batching state per call (#8491)
batchTimerEntry passed the search window and the batching candidate to the ZIP tree iteration through three file-static variables. The comment above them claims they are only used behind the mutex, but that mutex belongs to a single UA_Timer while the variables belong to the process, and with UA_MULTITHREADING below 100 the lock macros expand to nothing at all. Two timers driven from two threads therefore overwrite each other's state, which is exactly the arrangement the library recommends: one UA_Server and one UA_Client in the same process, each iterated from its own thread, with no instance ever shared.

The effect is silent in a release build. A cyclic callback picks up the nextTime computed for an unrelated timer and fires up to one second early or late, since the batching window is capped at UA_DATETIME_SEC. In a debug build the assertion in findTimer2Batch trips first, because the window it checks against no longer belongs to the entry being iterated.

The window and the candidate now live in a context on the stack of batchTimerEntry. The window doubles as the ZIP_ITER_KEY key, which is how cmpBatchWindow receives both bounds without static state.

No regression test: the overwrite only appears when two batchTimerEntry
calls interleave across threads, and the valgrind stage serialises them.
A threaded probe caught it 10 times out of 10 natively and 0 out of 3
under valgrind, at 240 to 400 seconds a run.
2026-09-25 14:16:44 +02:00
Niels Beier 23d1622825 fix(server): fix dangling pointer in SecureChannel attribute scratch
UA_STATIC_THREAD_LOCAL degrades to a plain stack local when
multithreading is off or the compiler isn't recognized (see its own
doc comment in config.h.in), so UA_SecureChannel_getBuiltinAttribute
returned a Variant pointing into a dead stack frame. Only showed up
on the tcc and lwip(gcc) CI builds, which don't get real thread-local
storage there. Moves the scratch storage onto UA_SecureChannel itself,
so it outlives the call regardless of threading config.
2026-09-25 13:30:13 +02:00
Niels Beier a8c9c10f9d refactor(server): compute SecureChannel attributes on access, lock ns0
Namespace 0 is now restricted to the predefined set of SecureChannel
attributes, for both read and write; other namespaces stay free-form.
The built-in read-only keys are no longer cached in channel->attributes. UA_SecureChannel_getBuiltinAttribute computes them fresh from the
live channel on every access, and notifySecureChannel now reuses it
instead of duplicating the field mappings.
2026-09-25 13:30:13 +02:00
Niels Beier 17a4ea6e55 feat(server): expose SecureChannel background info as read-only attributes
notifySecureChannel now merges the same data it sends in the
SECURECHANNEL_OPENED notification into channel->attributes, so it's
readable via UA_Server_getSecureChannelAttribute and friends. These
keys are rejected by set/delete with BADNOTWRITABLE, only
maxMessageSize stays writable. Also fixes a null-pointer read on
connectionManager in notifySecureChannel and a misleading doc comment
on getSecureChannelAttribute_scalar.
2026-09-25 13:30:13 +02:00
Niels Beier 1a1821933a test(server): cover SecureChannel attributes and the message size override 2026-09-25 13:30:13 +02:00
Niels Beier 0ff9154572 feat(server): let applications tighten the message size limit on a channel
Adds a generic SecureChannel attribute map, mirroring the existing
per-Session attributes, with UA_Server_{get,set,delete}SecureChannel
Attribute. Writing the reserved "0:maxMessageSize" key caps
tcpMaxMsgSize for that one channel, it can only tighten, never
loosen, the configured ceiling. Reuses the existing
secureChannelNotificationCallback as the decision point instead of
adding a dedicated callback.
2026-09-25 13:30:13 +02:00
Bastian Engel 370dd3532a fix(crypto): add certificate update test case, remove mbedTLS-only guard
The new test case exercises the branch that broke previously. Removing
the mbedTLS-only guard exercises the tests for OpenSSL.
2026-09-25 13:25:54 +02:00
Bastian Engel 3d36a7ef0e fix(crypto): don't clear the securityPolicy context
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.
2026-09-25 13:25:54 +02:00
Julius Pfrommer 6084c9c01a Merge pull request #8487 from o6-automation/merge_14_15
Merge 1.4 to 1.5
2026-09-25 13:25:20 +02:00
Andreas Ebner c3bb70e66b ci: build and test without multithreading
UA_MULTITHREADING=0 is supported and the default on targets that are neither UNIX nor WIN32,
but no job built it. Mirrors unit_tests_mt with a different value, on ubuntu-24.04 only to
keep the CI cost low.
2026-09-25 12:40:49 +02:00