When a PublishResponse comes back with BadSessionClosed or BadSessionIdInvalid, processMSGResponse takes the request off the list and cleans up the Session before calling the response callback. cleanupSession cancels the other PublishRequests, whose callbacks decrement the counter, and then resets it to zero. The callback of the response that triggered the cleanup runs afterwards and decrements it once more, so the UInt16 wraps to 65535. From then on backgroundPublish never sends a PublishRequest, also not for the next Session, until the client disconnects.
Every PublishRequest already decrements the counter exactly once, when its callback runs for the response or for the cancellation, so the reset is not needed. Remove it.
Links to a deleted MonitoredItem were only removed when the triggering item reported. If it never reports (Disabled mode, or a value that does not change), they stay forever, and since MonitoredItem ids are never reused a client can grow the link array of one item without bound by creating, linking and deleting items in a loop. Every addLink scans that array, so SetTriggering gets slower with each round.
Part 4, 5.13.1.6 says that the link goes away with the triggered item. SetTriggering now prunes the links of the triggering item to MonitoredItems that no longer exist before applying the request, which keeps the array bounded by the items of the Subscription.
After the first OpenSecureChannel the server keeps a placeholder in securityToken (TokenId zero, no keys) and waits for the client to use the issued token. In the NEWTOKEN_SERVER state checkSymHeader took a message with TokenId zero as "old token still in use", so until the client's first message it was verified with keys that were never derived. In Sign mode that is an HMAC with an empty key, which anyone can compute. The client side has the same placeholder in altSecurityToken after its first OPN.
After an ISSUE there is no old token (Part 4, 5.6.2), and a TokenId that was never issued is unknown (Part 6, 6.7.2.3). TokenId zero is no longer accepted as the old token on either side, and the server skips zero when lastTokenId wraps around.
UA_Server_enableAllPubSubComponents() returned the error of
UA_PubSubManager_start() without releasing the server lock. Release it,
like the psm == NULL path above already does, and add a regression test.
Signed-off-by: Marvin Gnad <marvin.gnad@gmail.com>
The synchronous CreateMonitoredItems and ModifyMonitoredItems keep the Subscription and MonitoredItem pointers across the service call. If the Session is closed meanwhile (BadSessionClosed on a queued Publish, or BadSessionIdInvalid on the request itself), the client removes its Subscriptions before the call returns and these pointers are used after free. For CreateMonitoredItems the MonitoredItem is freed twice and its delete callback runs twice. Look them up again by id and client handle after the call, as the async handlers already do.
CreateMonitoredItems, ModifyMonitoredItems, SetMonitoringMode and DeleteMonitoredItems all check maxMonitoredItemsPerCall, but SetTriggering accepted any number of links. Apply the same limit there, counting the links to add and the links to remove together, and return Bad_TooManyOperations above it (Part 4, 5.13.5).
Also free removeResults with its own size in the out-of-memory path, it was using linksToAddSize.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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).
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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.
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
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.
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>
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.