mz-automation / libiec61850

Official repository for libIEC61850, the open-source library for the IEC 61850 protocols
http://libiec61850.com/libiec61850
GNU General Public License v3.0
898 stars 474 forks source link

Function IedConnection_getDataSetDirectory may cause crash #145

Closed mashomee closed 5 years ago

mashomee commented 5 years ago

below is the sample code and stacktrace. Sometimes when i use IedConnection_getDataSetDirectory to get dataset entry, i'll crash.

         LinkedList report = LinkedList_getNext(reports);
        while (report != nullptr)
        {
            iec::ContinueGuard reportGuard{ [&]() {
                report = LinkedList_getNext(report);
            } };
            const auto reportName = static_cast<char*>(LinkedList_getData(report));
            char rpRef[500]{ 0 };
            snprintf(rpRef, sizeof(rpRef), "%s/%s.RP.%s", deviceName, nodeName, reportName);

        ClientReportControlBlock rcb =
            IedConnection_getRCBValues(_conn, &error, rpRef, nullptr);
        if (error != IED_ERROR_OK)
        {
            LOG(ERROR) << "get Unbuffered Report control block failed! error: [" << err_to_string(error) << "]";
            if (isConnectionErr(error))
            {
                return false;
            }
            else {
                continue;
            }
        }

        auto id = ClientReportControlBlock_getRptId(rcb);
        if (existsRptId(id))
        {
            LOG(INFO) << "already registered control block for RptId: [" << id << "]";
            continue;
        }

        const auto dataRef = ClientReportControlBlock_getDataSetReference(rcb);
        if (dataRef == nullptr)
        {
            LOG(ERROR) << "get Unbuffered Report control block dataset reference failed! error: [" << err_to_string(error) << "]";
            if (isConnectionErr(error))
            {
                return false;
            }
            else {
                continue;
            }
        }
        LOG(INFO) << "report dataset reference [" << dataRef << "]";

        auto dataSetPath = getDataSetPathFromRef(dataRef);
        LinkedList dataSetDirectory =
            IedConnection_getDataSetDirectory(_conn, &error, dataSetPath.c_str(), nullptr);
        if (error != IED_ERROR_OK)
        {
            LOG(ERROR) << "get Unbuffered Report control block dataset directory failed!  error: [" << err_to_string(error) << "]";
            if (isConnectionErr(error))
            {
                return false;
            }
            else {
                continue;
            }
        }
        LOG(INFO) << "report dataset directory read sucess path:[" << dataSetPath << "]";

        // 获取dateset项的路径,到da
        LOG(INFO) << "entry size: [" << LinkedList_size(dataSetDirectory) << "]";

        auto entryNode = LinkedList_getNext(dataSetDirectory);
        while (entryNode != nullptr)
        {
            auto entry = (const char*)(LinkedList_getData(entryNode));
            auto path = getEntryPathFromDataSetEntry(entry);
            auto fc = getFCFromDataSetEntry(entry);
            LOG(INFO) << "entry [" << entry << "] path [" << path << "] fc [" << fc_to_string(fc) << "]";

            IedClientError error;
            MmsVariableSpecification* spec = IedConnection_getVariableSpecification(_conn, &error, path.c_str(), fc);
            if (spec != nullptr)
            {
                recursiveGetDaPath(spec, path.c_str(), wrapper);
            }

            entryNode = LinkedList_getNext(entryNode);
        }

        ClientReportControlBlock_setResv(rcb, true);
        ClientReportControlBlock_setRptEna(rcb, true);
        IedConnection_installReportHandler(_conn, rpRef, ClientReportControlBlock_getRptId(rcb), &Connection::reportWithPathCallback,
            (void*)wrapper.get()); 

        IedConnection_setRCBValues(_conn, &error, rcb, RCB_ELEMENT_RESV | RCB_ELEMENT_RPT_ENA | RCB_ELEMENT_SQ_NUM, true);

        if (error != IED_ERROR_OK)
        {
            IedConnection_uninstallReportHandler(_conn, rpRef);

            LOG(ERROR) << "enable Unbuffered Report failed!  error: [" << err_to_string(error) << "]";
            if (isConnectionErr(error))
            {
                return false;
            }
            else {
                continue;
            }
        }
        LOG(INFO) << "report activate sucess. path: [" << rpRef << "]";
        _RptIds.insert(id);
    }

stacktrace:

#0  0x00007f7676d2f428 in __GI_raise (sig=sig@entry=6) at ../sysdeps/unix/sysv/linux/raise.c:54
#1  0x00007f7676d3102a in __GI_abort () at abort.c:89
#2  0x00000000004d529b in el::base::utils::abort (status=11, reason="") at easylogging++.cc:125
#3  0x00000000004e566d in el::base::debug::crashAbort (sig=11) at easylogging++.cc:2880
#4  0x00000000004e56c3 in el::base::debug::defaultCrashHandler (sig=11) at easylogging++.cc:2888
#5  <signal handler called>
#6  0x000000000062d651 in LinkedList_getNext (list=0x0) at src/common/linked_list.c:172
#7  0x0000000000639001 in IedConnection_getDataSetDirectory (self=0x7f7664000960, error=0x7f76752e31d4, dataSetReference=0x7f7664516450 "ST_SF6_CTLREVEAL/LLN0.dsAinSF", isDeletable=0x0)
    at src/iec61850/client/ied_connection.c:2272
#8  0x00000000004b73c9 in Connection::activateReports (this=0x26f7d10) at Connection.cpp:751
#9  0x00000000004b4718 in Connection::connect (this=0x26f7d10, activeReports=true) at Connection.cpp:334
#10 0x000000000043e1f5 in ComWorker::connectServer (this=0x26f8ec8, honorStopSignal=true) at ComWorker.cpp:233
#11 0x000000000043cbe8 in ComWorker::<lambda(bool)>::operator()(bool) const (__closure=0x7f76752e3d10, honorStopSignal=true) at ComWorker.cpp:110
#12 0x000000000043db50 in ComWorker::operator() (this=0x26f8ec8) at ComWorker.cpp:127
#13 0x000000000043a19c in std::_Bind_simple<ComWorker ()>::_M_invoke<>(std::_Index_tuple<>) (this=0x26f8ec8) at /usr/include/c++/5/functional:1531
#14 0x0000000000439ede in std::_Bind_simple<ComWorker ()>::operator()() (this=0x26f8ec8) at /usr/include/c++/5/functional:1520
#15 0x0000000000439bc4 in std::thread::_Impl<std::_Bind_simple<ComWorker ()> >::_M_run() (this=0x26f8eb0) at /usr/include/c++/5/thread:115
#16 0x00007f767769bc80 in ?? () from /usr/lib/x86_64-linux-gnu/libstdc++.so.6
#17 0x00007f7677d8a6ba in start_thread (arg=0x7f76752e4700) at pthread_create.c:333
#18 0x00007f7676e0141d in clone () at ../sysdeps/unix/sysv/linux/x86_64/clone.S:109

What's bothering me is that it not always crashes.

mashomee commented 5 years ago

version is 1.3.3 from #6 0x000000000062d651 in LinkedList_getNext (list=0x0) at src/common/linked_list.c:172, we can see the list is nullptr, perhapes the entries should be checked if it's nullptr.

LinkedList /* <char*> */
IedConnection_getDataSetDirectory(IedConnection self, IedClientError* error, const char* dataSetReference, bool* isDeletable)
{
    bool deletable = false;

    LinkedList dataSetMembers = NULL;

    char domainIdBuffer[65];
    char itemIdBuffer[DATA_SET_MAX_NAME_LENGTH + 1];

    const char* domainId = NULL;
    const char* itemId = NULL;

    bool isAssociationSpecific = false;

    if (dataSetReference[0] != '@') {
        if ((dataSetReference[0] == '/') || (strchr(dataSetReference, '/') == NULL)) {
            domainId = NULL;

            if (dataSetReference[0] == '/')
                itemId = dataSetReference + 1;
            else
                itemId = dataSetReference;
        }
        else {
            domainId = MmsMapping_getMmsDomainFromObjectReference(dataSetReference, domainIdBuffer);

            if (domainId == NULL) {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            const char* itemIdRef = dataSetReference + strlen(domainId) + 1;

            if (strlen(itemIdRef) > DATA_SET_MAX_NAME_LENGTH) {
                *error = IED_ERROR_OBJECT_REFERENCE_INVALID;
                goto exit_function;
            }

            char* itemIdRefInBuffer = StringUtils_copyStringToBuffer(itemIdRef, itemIdBuffer);
            StringUtils_replace(itemIdRefInBuffer, '.', '$');
            itemId = itemIdRefInBuffer;
        }
    }
    else {
        itemId = dataSetReference + 1;
        isAssociationSpecific = true;
    }

    MmsError mmsError;

    LinkedList entries;

    if (isAssociationSpecific)
        entries = MmsConnection_readNamedVariableListDirectoryAssociationSpecific(self->connection,
                &mmsError, itemId, &deletable);
    else
        entries = MmsConnection_readNamedVariableListDirectory(self->connection,
                &mmsError, domainId, itemId, &deletable);

    if (mmsError == MMS_ERROR_NONE) {

        LinkedList entry = LinkedList_getNext(entries);

        dataSetMembers = LinkedList_create();

        while (entry != NULL) {
            MmsVariableAccessSpecification* varAccessSpec = (MmsVariableAccessSpecification*) entry->data;

            char* objectReference = MmsMapping_varAccessSpecToObjectReference(varAccessSpec);

            LinkedList_add(dataSetMembers, objectReference);

            entry = LinkedList_getNext(entry);
        }

        if (isDeletable != NULL)
            *isDeletable = deletable;

        LinkedList_destroyDeep(entries, (LinkedListValueDeleteFunction) MmsVariableAccessSpecification_destroy);
    }

    *error = iedConnection_mapMmsErrorToIedError(mmsError);

exit_function:
    return dataSetMembers;
}
mzillgith commented 5 years ago

It is possible that this problem can happen when the client parser thinks that the server sent and invalid response. I added some additional checks to catch this case. Please check the latest patch if it solves your problem.

mashomee commented 5 years ago

Thanks.

mashomee commented 5 years ago

Seems like that patch fixes this. Thanks again.