From: Michael Onken Date: Mon, 6 Jul 2026 20:39:03 +0000 (+0200) Subject: Fix A-ASSOCIATE-RQ/AC error-path leaks in DUL FSM. X-Git-Tag: archive/raspbian/3.7.0+really3.7.0-7+rpi1^2~4 X-Git-Url: https://dgit.raspbian.org/?a=commitdiff_plain;h=361f28a7bf90e54153289d13b22265e8d37c1786;p=dcmtk.git Fix A-ASSOCIATE-RQ/AC error-path leaks in DUL FSM. Applied-Upstream: 2312891a8d058c862e00bcbd636e5da26308658a Last-Update: 2026-05-29 Reviewed-By: Étienne Mollier Bug-Debian: https://bugs.debian.org/1141411 AE_3_AssociateConfirmationAccept and AE_6_ExamineAssociateRequest leaked the whole parsed PDU graph (presentation contexts, transfer syntax sub-chains, role list, user info) on error returns taken after parseAssociate() succeeded, since only the success path freed it. For an SCP this is remotely triggerable and accumulates per connection. Free the parsed PDU before each such return; translatePresentationContextList() also frees the proposed transfer syntax list of a rejected context. Add a dcmnet regression test (tassocleak.cc) driving the AE_6 zero-transfer-syntax path against an in-process DcmSCP; leak-clean with the fix under DCMTK_WITH_SANITIZERS. Thanks for the report and analysis to Abhinav Agarwal. This closes DCMTK issue #1217. Gbp-Pq: Name CVE-2026-35505.patch --- diff --git a/dcmnet/libsrc/dulfsm.cc b/dcmnet/libsrc/dulfsm.cc index 00ee6444..f0945403 100644 --- a/dcmnet/libsrc/dulfsm.cc +++ b/dcmnet/libsrc/dulfsm.cc @@ -967,14 +967,26 @@ AE_3_AssociateConfirmationAccept(PRIVATE_NETWORKKEY ** /*network*/, OFStandard::strlcpy(service->callingAPTitle, assoc.callingAPTitle, sizeof(service->callingAPTitle)); OFStandard::strlcpy(service->applicationContextName, assoc.applicationContext.data, sizeof(service->applicationContextName)); - if ((service->acceptedPresentationContext = LST_Create()) == NULL) return EC_MemoryExhausted; + if ((service->acceptedPresentationContext = LST_Create()) == NULL) + { + /* free memory allocated by parseAssociate() before returning */ + destroyAssociatePDUPresentationContextList(&assoc.presentationContextList); + destroyUserInformationLists(&assoc.userInfo); + return EC_MemoryExhausted; + } prvCtx = (PRV_PRESENTATIONCONTEXTITEM*)LST_Head(&assoc.presentationContextList); if (prvCtx != NULL) (void) LST_Position(&assoc.presentationContextList, (LST_NODE*)prvCtx); while (prvCtx != NULL) { userPresentationCtx = (DUL_PRESENTATIONCONTEXT*)malloc(sizeof(DUL_PRESENTATIONCONTEXT)); - if (userPresentationCtx == NULL) return EC_MemoryExhausted; + if (userPresentationCtx == NULL) + { + /* free memory allocated by parseAssociate() before returning */ + destroyAssociatePDUPresentationContextList(&assoc.presentationContextList); + destroyUserInformationLists(&assoc.userInfo); + return EC_MemoryExhausted; + } (void) memset(userPresentationCtx, 0, sizeof(DUL_PRESENTATIONCONTEXT)); userPresentationCtx->result = prvCtx->result; @@ -1007,6 +1019,9 @@ AE_3_AssociateConfirmationAccept(PRIVATE_NETWORKKEY ** /*network*/, char buf1[256]; OFStandard::snprintf(buf1, sizeof(buf1), "DUL Peer supplied illegal number of transfer syntaxes (%d)", 0); free(userPresentationCtx); + /* free memory allocated by parseAssociate() before returning */ + destroyAssociatePDUPresentationContextList(&assoc.presentationContextList); + destroyUserInformationLists(&assoc.userInfo); return makeDcmnetCondition(DULC_PEERILLEGALXFERSYNTAXCOUNT, OF_error, buf1); } @@ -1015,6 +1030,9 @@ AE_3_AssociateConfirmationAccept(PRIVATE_NETWORKKEY ** /*network*/, char buf2[256]; OFStandard::snprintf(buf2, sizeof(buf2), "DUL Peer supplied illegal number of transfer syntaxes (%ld)", LST_Count(&prvCtx->transferSyntaxList)); free(userPresentationCtx); + /* free memory allocated by parseAssociate() before returning */ + destroyAssociatePDUPresentationContextList(&assoc.presentationContextList); + destroyUserInformationLists(&assoc.userInfo); return makeDcmnetCondition(DULC_PEERILLEGALXFERSYNTAXCOUNT, OF_error, buf2); } subItem = (DUL_SUBITEM*)LST_Head(&prvCtx->transferSyntaxList); @@ -1037,7 +1055,13 @@ AE_3_AssociateConfirmationAccept(PRIVATE_NETWORKKEY ** /*network*/, */ if (assoc.userInfo.extNegList != NULL) { service->acceptedExtNegList = new SOPClassExtendedNegotiationSubItemList; - if (service->acceptedExtNegList == NULL) return EC_MemoryExhausted; + if (service->acceptedExtNegList == NULL) + { + /* free memory allocated by parseAssociate() before returning */ + destroyAssociatePDUPresentationContextList(&assoc.presentationContextList); + destroyUserInformationLists(&assoc.userInfo); + return EC_MemoryExhausted; + } appendList(*assoc.userInfo.extNegList, *service->acceptedExtNegList); } @@ -1045,7 +1069,13 @@ AE_3_AssociateConfirmationAccept(PRIVATE_NETWORKKEY ** /*network*/, if (assoc.userInfo.usrIdent != NULL) { service->ackUserIdentNeg = new UserIdentityNegotiationSubItemAC( *(OFstatic_cast(UserIdentityNegotiationSubItemAC*, assoc.userInfo.usrIdent))); - if (service->ackUserIdentNeg == NULL) return EC_MemoryExhausted; + if (service->ackUserIdentNeg == NULL) + { + /* free memory allocated by parseAssociate() before returning */ + destroyAssociatePDUPresentationContextList(&assoc.presentationContextList); + destroyUserInformationLists(&assoc.userInfo); + return EC_MemoryExhausted; + } } @@ -1230,11 +1260,20 @@ AE_6_ExamineAssociateRequest(PRIVATE_NETWORKKEY ** /*network*/, OFStandard::strlcpy(service->callingAPTitle, assoc.callingAPTitle, sizeof(service->callingAPTitle)); OFStandard::strlcpy(service->applicationContextName, assoc.applicationContext.data, sizeof(service->applicationContextName)); - if ((service->requestedPresentationContext = LST_Create()) == NULL) return EC_MemoryExhausted; + if ((service->requestedPresentationContext = LST_Create()) == NULL) + { + /* free memory allocated by parseAssociate() before returning */ + destroyAssociatePDUPresentationContextList(&assoc.presentationContextList); + destroyUserInformationLists(&assoc.userInfo); + return EC_MemoryExhausted; + } if (translatePresentationContextList(&assoc.presentationContextList, &assoc.userInfo.SCUSCPRoleList, &service->requestedPresentationContext).bad()) { + /* free memory allocated by parseAssociate() before returning */ + destroyAssociatePDUPresentationContextList(&assoc.presentationContextList); + destroyUserInformationLists(&assoc.userInfo); return DUL_PCTRANSLATIONFAILURE; } @@ -1248,14 +1287,26 @@ AE_6_ExamineAssociateRequest(PRIVATE_NETWORKKEY ** /*network*/, */ if (assoc.userInfo.extNegList != NULL) { service->requestedExtNegList = new SOPClassExtendedNegotiationSubItemList; - if (service->requestedExtNegList == NULL) return EC_MemoryExhausted; + if (service->requestedExtNegList == NULL) + { + /* free memory allocated by parseAssociate() before returning */ + destroyAssociatePDUPresentationContextList(&assoc.presentationContextList); + destroyUserInformationLists(&assoc.userInfo); + return EC_MemoryExhausted; + } appendList(*assoc.userInfo.extNegList, *service->requestedExtNegList); } /* user identity negotiation: Remember request values in association parameters (copy)*/ if (assoc.userInfo.usrIdent != NULL) { service->reqUserIdentNeg = new UserIdentityNegotiationSubItemRQ(); - if (service->reqUserIdentNeg == NULL) return EC_MemoryExhausted; + if (service->reqUserIdentNeg == NULL) + { + /* free memory allocated by parseAssociate() before returning */ + destroyAssociatePDUPresentationContextList(&assoc.presentationContextList); + destroyUserInformationLists(&assoc.userInfo); + return EC_MemoryExhausted; + } *(service->reqUserIdentNeg) = *(OFstatic_cast(UserIdentityNegotiationSubItemRQ*,assoc.userInfo.usrIdent)); } @@ -3943,6 +3994,8 @@ translatePresentationContextList(LST_HEAD ** internalList, { char buf1[256]; OFStandard::snprintf(buf1, sizeof(buf1), "DUL Peer supplied illegal number of transfer syntaxes (%d)", 0); + /* free the (empty) transfer syntax list allocated for this context above */ + LST_Destroy(&userContext->proposedTransferSyntax); free(userContext); return makeDcmnetCondition(DULC_PEERILLEGALXFERSYNTAXCOUNT, OF_error, buf1); } diff --git a/dcmnet/tests/CMakeLists.txt b/dcmnet/tests/CMakeLists.txt index c3567a83..f7e04a5d 100644 --- a/dcmnet/tests/CMakeLists.txt +++ b/dcmnet/tests/CMakeLists.txt @@ -1,5 +1,6 @@ # declare executables DCMTK_ADD_TEST_EXECUTABLE(dcmnet_tests + tassocleak.cc tdimse.cc tdump.cc tests.cc diff --git a/dcmnet/tests/Makefile.in b/dcmnet/tests/Makefile.in index 5060274e..d96e5714 100644 --- a/dcmnet/tests/Makefile.in +++ b/dcmnet/tests/Makefile.in @@ -25,7 +25,7 @@ LIBDIRS = -L$(top_srcdir)/libsrc -L$(ofstddir)/libsrc -L$(oflogdir)/libsrc \ LOCALLIBS = -ldcmnet -ldcmdata -loflog -lofstd -loficonv $(ZLIBLIBS) \ $(TCPWRAPPERLIBS) $(CHARCONVLIBS) $(MATHLIBS) -objs = tests.o tdump.o tdimse.o tpool.o tscuscp.o tscusession.o +objs = tests.o tdump.o tdimse.o tassocleak.o tpool.o tscuscp.o tscusession.o progs = tests diff --git a/dcmnet/tests/tassocleak.cc b/dcmnet/tests/tassocleak.cc new file mode 100644 index 00000000..fd132515 --- /dev/null +++ b/dcmnet/tests/tassocleak.cc @@ -0,0 +1,312 @@ +/* + * + * Copyright (C) 2026, OFFIS e.V. + * All rights reserved. See COPYRIGHT file for details. + * + * This software and supporting documentation were developed by + * + * OFFIS e.V. + * R&D Division Health + * Escherweg 2 + * D-26121 Oldenburg, Germany + * + * + * Module: dcmnet + * + * Author: Michael Onken + * + * Purpose: Integration regression tests that drive an in-process DcmSCP with + * hand-crafted A-ASSOCIATE-RQ PDUs. They exercise the receiver-side + * association request handling on malformed input that a conformant + * DCMTK requestor (DcmSCU / the ASC_* API) would refuse to put on + * the wire. Built with DCMTK_WITH_SANITIZERS=ON these tests double + * as memory-error / leak regression tests for the DUL association + * parsing and translation code paths. + * + */ + + +#include "dcmtk/config/osconfig.h" /* make sure OS specific configuration is included first */ + +#ifdef WITH_THREADS + +#include "dcmtk/ofstd/oftest.h" +#include "dcmtk/ofstd/ofstd.h" /* for OFStandard::initializeNetwork() */ +#include "dcmtk/ofstd/ofthread.h" /* for OFThread */ +#include "dcmtk/dcmnet/scp.h" +#include "dcmtk/dcmnet/dcmtrans.h" /* for DcmTCPConnection */ +#include "dcmtk/dcmnet/dcompat.h" /* for platform socket headers */ +#include "dcmtk/dcmnet/dulstruc.h" /* DUL_TYPE* constants, DUL_PROTOCOL */ +#include "dcmtk/dcmnet/dul.h" + + +/* -------------------------------------------------------------------------- + * Minimal in-process SCP that accepts exactly one association attempt and + * then returns. The DUL state machine (and therefore the association request + * parsing/translation) runs inside ASC_receiveAssociation() before the SCP + * gets a chance to accept or reject, so it suffices to let the SCP try to + * receive a single association. + * ------------------------------------------------------------------------ */ +struct OneShotReceiverSCP : DcmSCP, OFThread +{ + OneShotReceiverSCP() + : DcmSCP() + , m_listen_result(EC_NotYetImplemented) + , m_portNum(0) + { + DcmSCPConfig& config = getConfig(); + config.setAETitle("RECV_SCP"); + // Non-blocking accept with a short connection timeout, so the accept + // loop cannot get stuck if a (future) malformed PDU leaves the SCP + // waiting on a partial read, or if no client connects at all. For the + // normal flow the client connects immediately, so this timeout is not + // hit and adds no delay. + config.setConnectionBlockingMode(DUL_NOBLOCK); + config.setConnectionTimeout(2 /* seconds */); + config.setHostLookupEnabled(OFFalse); + config.setPort(0); // OS chooses a free port + OFList xfers; + xfers.push_back(UID_LittleEndianImplicitTransferSyntax); + OFCHECK(config.addPresentationContext(UID_VerificationSOPClass, xfers).good()); + OFCHECK(openListenPort().good()); + m_portNum = config.getPort(); + } + + /** Stop the accept loop after the single association attempt has been + * handled, so the SCP thread returns and can be joined. + */ + virtual OFBool stopAfterCurrentAssociation() + { + return OFTrue; + } + + /** Also stop if the connection timeout elapses without any association + * request. Together with the non-blocking mode above this guarantees the + * accept loop terminates even if no client ever connects, so a stuck test + * fails fast (via join()) instead of hanging. + */ + virtual OFBool stopAfterConnectionTimeout() + { + return OFTrue; + } + + virtual void run() + { + m_listen_result = acceptAssociations(); + } + + OFCondition m_listen_result; + Uint16 m_portNum; +}; + + +/* Write a 16-bit big-endian value into buf and advance the cursor. */ +static void put_u16_be(unsigned char *&p, unsigned short v) +{ + *p++ = OFstatic_cast(unsigned char, (v >> 8) & 0xff); + *p++ = OFstatic_cast(unsigned char, v & 0xff); +} + +/* Write a 32-bit big-endian value into buf and advance the cursor. */ +static void put_u32_be(unsigned char *&p, unsigned long v) +{ + *p++ = OFstatic_cast(unsigned char, (v >> 24) & 0xff); + *p++ = OFstatic_cast(unsigned char, (v >> 16) & 0xff); + *p++ = OFstatic_cast(unsigned char, (v >> 8) & 0xff); + *p++ = OFstatic_cast(unsigned char, v & 0xff); +} + +/* Write a sub-item header (type + reserved + 2-byte big-endian length) and + * advance the cursor. */ +static void put_subitem_header(unsigned char *&p, unsigned char type, unsigned short bodyLen) +{ + *p++ = type; + *p++ = 0x00; + put_u16_be(p, bodyLen); +} + + +/* Open a blocking TCP connection to 127.0.0.1:port. Sending and + * tearing the connection down is done via DcmTCPConnection so the test needs + * no platform-specific socket code. Returns an invalid socket on failure. */ +static DcmNativeSocketType connect_loopback(Uint16 port) +{ + DcmNativeSocketType sock = socket(AF_INET, SOCK_STREAM, 0); + if (sock == OFstatic_cast(DcmNativeSocketType, -1)) + return sock; + + struct sockaddr_in addr; + memset(&addr, 0, sizeof(addr)); + addr.sin_family = AF_INET; + addr.sin_port = htons(port); + addr.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + + if (connect(sock, OFreinterpret_cast(struct sockaddr*, &addr), sizeof(addr)) != 0) + return OFstatic_cast(DcmNativeSocketType, -1); + return sock; +} + + +/* Build an A-ASSOCIATE-RQ PDU whose single presentation context contains an + * Abstract Syntax sub-item (Verification SOP Class) but NO Transfer Syntax + * sub-items. The PDU is otherwise well-formed: Application Context item + + * User Information item with Maximum Length and Implementation Class UID + * sub-items. + * + * We cannot use the existing C++/C API to construct this since in DCMTK + * it is protected not to run any Presentation Contexts with 0 transfer + * syntaxes. + * + * @param outLen receives the total PDU length on the wire (incl. preamble) + * @return heap-allocated buffer (caller frees with delete[]) + */ +static unsigned char* build_zero_ts_assoc_rq(unsigned long &outLen) +{ + const char *appCtx = "1.2.840.10008.3.1.1.1"; // DICOM Application Context + const char *abstractStx = "1.2.840.10008.1.1"; // Verification SOP Class + const char *implClassUID = "1.2.276.0.7230010.3.0.3.6.0"; // random digits + + const unsigned short appCtxLen = OFstatic_cast(unsigned short, strlen(appCtx)); + const unsigned short abstractLen = OFstatic_cast(unsigned short, strlen(abstractStx)); + const unsigned short implLen = OFstatic_cast(unsigned short, strlen(implClassUID)); + + // Sub-PDU sizes (4-byte header + body). + const unsigned long appCtxItem = 4 + appCtxLen; + const unsigned long abstractItem = 4 + abstractLen; + // Presentation Context body = ctxID + 3 reserved + abstract syntax item. + const unsigned long pcBody = 4 + abstractItem; + const unsigned long pcItem = 4 + pcBody; + const unsigned long maxLenItem = 4 + 4; // Max Length sub-item (body = 4-byte maxPDU) + const unsigned long implItem = 4 + implLen; // Implementation Class UID sub-item + const unsigned long userInfoBody = maxLenItem + implItem; + const unsigned long userInfoItem = 4 + userInfoBody; + + // Fixed A-ASSOCIATE-RQ header before variable items: + // protocol(2) + reserved(2) + calledAE(16) + callingAE(16) + reserved(32) = 68 + const unsigned long fixedAfterLen = 2 + 2 + 16 + 16 + 32; + const unsigned long variableItems = appCtxItem + pcItem + userInfoItem; + + // PDU length field counts everything AFTER the 6-byte preamble + // (type + reserved + 4-byte length), i.e. the fixed header plus items. + const unsigned long pduPayloadLen = fixedAfterLen + variableItems; + const unsigned long totalLen = 6 + pduPayloadLen; + + unsigned char *buf = new unsigned char[totalLen]; + unsigned char *p = buf; + + // PDU preamble. + *p++ = DUL_TYPEASSOCIATERQ; // 0x01 + *p++ = 0x00; // reserved + put_u32_be(p, pduPayloadLen); + + // Fixed header. + put_u16_be(p, DUL_PROTOCOL); // protocol version + *p++ = 0x00; *p++ = 0x00; // reserved + { + const char *calledAE = "RECV_SCP "; // 16 bytes, space padded + const char *callingAE = "SEND_SCU "; // 16 bytes, space padded + for (int i = 0; i < 16; ++i) *p++ = OFstatic_cast(unsigned char, calledAE[i]); + for (int i = 0; i < 16; ++i) *p++ = OFstatic_cast(unsigned char, callingAE[i]); + } + for (int i = 0; i < 32; ++i) *p++ = 0x00; // reserved + + // Application Context item. + put_subitem_header(p, DUL_TYPEAPPLICATIONCONTEXT, appCtxLen); + for (unsigned short i = 0; i < appCtxLen; ++i) *p++ = OFstatic_cast(unsigned char, appCtx[i]); + + // Presentation Context item (with abstract syntax, NO transfer syntax). + put_subitem_header(p, DUL_TYPEPRESENTATIONCONTEXTRQ, OFstatic_cast(unsigned short, pcBody)); + *p++ = 0x01; // presentation context ID (odd, as required) + *p++ = 0x00; // reserved + *p++ = 0x00; // reserved (result/reason, unused in RQ) + *p++ = 0x00; // reserved + // Abstract Syntax sub-item. + put_subitem_header(p, DUL_TYPEABSTRACTSYNTAX, abstractLen); + for (unsigned short i = 0; i < abstractLen; ++i) *p++ = OFstatic_cast(unsigned char, abstractStx[i]); + // (intentionally no Transfer Syntax sub-item) + + // User Information item. + put_subitem_header(p, DUL_TYPEUSERINFO, OFstatic_cast(unsigned short, userInfoBody)); + // Maximum Length sub-item (body = 4-byte maximum PDU length). + put_subitem_header(p, DUL_TYPEMAXLENGTH, 4); + put_u32_be(p, 16384); + // Implementation Class UID sub-item. + put_subitem_header(p, DUL_TYPEIMPLEMENTATIONCLASSUID, implLen); + for (unsigned short i = 0; i < implLen; ++i) *p++ = OFstatic_cast(unsigned char, implClassUID[i]); + + OFCHECK_EQUAL(OFstatic_cast(unsigned long, p - buf), totalLen); + outLen = totalLen; + return buf; +} + + +/* Regression test for two leaks on the receiver-side handling of an + * A-ASSOCIATE-RQ whose single presentation context carries an Abstract Syntax + * but ZERO Transfer Syntaxes: + * + * 1. parseAssociate() succeeds, then translatePresentationContextList() + * rejects the context with an illegal transfer-syntax count and + * AE_6_ExamineAssociateRequest() returns DUL_PCTRANSLATIONFAILURE. The + * already-parsed PRV_ASSOCIATEPDU (presentation context + user + * information lists) must be freed on that error path. + * 2. translatePresentationContextList() itself must free the (empty) + * proposedTransferSyntax list it allocated for the rejected context. + * + * Both leaks are tracked in DCMTK issue #1217. + * + * A conformant DCMTK requestor cannot emit a zero-transfer-syntax context + * (ASC_addPresentationContext rejects it), so the malicious PDU is crafted by + * hand and sent over a raw TCP connection to an in-process DcmSCP. The leaks + * are asserted implicitly by LeakSanitizer at process exit (build with + * DCMTK_WITH_SANITIZERS=ON on Linux); the OFCHECKs below only assert the + * harness behaved (socket connected, send succeeded, SCP thread joined). + */ +OFTEST(dcmnet_scp_assocRQ_zeroTransferSyntax_no_leak) +{ + OFStandard::initializeNetwork(); + + OneShotReceiverSCP scp; + // The listen socket is already bound and listening (openListenPort() runs + // in the SCP constructor), so a client connect() succeeds via the TCP + // backlog even before the accept thread is scheduled -- no startup sleep + // is needed here. + scp.start(); + + DcmNativeSocketType sock = connect_loopback(scp.m_portNum); + OFCHECK(sock != OFstatic_cast(DcmNativeSocketType, -1)); + + if (sock != OFstatic_cast(DcmNativeSocketType, -1)) + { + DcmTCPConnection conn(sock); // takes ownership of the socket + + unsigned long pduLen = 0; + unsigned char *pdu = build_zero_ts_assoc_rq(pduLen); + + // Send the hand-built A-ASSOCIATE-RQ. This drives the SCP through + // parseAssociate() + translatePresentationContextList() (the original leak path). + unsigned long sent = 0; + OFBool sendOk = OFTrue; + while (sent < pduLen) + { + ssize_t n = conn.write(pdu + sent, OFstatic_cast(size_t, pduLen - sent)); + if (n <= 0) { sendOk = OFFalse; break; } + sent += OFstatic_cast(unsigned long, n); + } + OFCHECK(sendOk); + + // Close immediately after sending instead of reading a response. The + // buffered PDU is still delivered to the SCP before the FIN, so it + // reaches the leak path; the FIN then unblocks the SCP's receive call + // at once. Reading here would instead leave both ends blocked on each + // other until the socket receive timeout (dcmSocketReceiveTimeout, + // 60s by default) expired -- which made this test take ~63s. + delete[] pdu; + conn.close(); + } + + // Wait for the SCP thread to finish handling the (failed) association. + const int joinResult = scp.join(); + OFCHECK(joinResult != OFThread::busy); +} + +#endif // WITH_THREADS diff --git a/dcmnet/tests/tests.cc b/dcmnet/tests/tests.cc index e7a2b438..0b0fbef6 100644 --- a/dcmnet/tests/tests.cc +++ b/dcmnet/tests/tests.cc @@ -51,6 +51,8 @@ OFTEST_REGISTER(dcmnet_scu_sendNSETRequest_fails_when_requestedsopinstance_is_em OFTEST_REGISTER(dcmnet_scu_sendNSETRequest_succeeds_and_modifies_instance_when_scp_has_instance); OFTEST_REGISTER(dcmnet_scu_sendNSETRequest_succeeds_and_sets_responsestatuscode_from_scp_when_scp_sets_error_status); +OFTEST_REGISTER(dcmnet_scp_assocRQ_zeroTransferSyntax_no_leak); + #endif // WITH_THREADS OFTEST_MAIN("dcmnet")