| /* This Source Code Form is subject to the terms of the Mozilla Public |
| * License, v. 2.0. If a copy of the MPL was not distributed with this |
| * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ |
| |
| /** |
| * This code is used to generate a binary file for every request type |
| * which can be sent from a client to the server. |
| * These files form the basic corpus for fuzzing the server. |
| */ |
| |
| #ifndef UA_DEBUG_DUMP_PKGS_FILE |
| #error UA_DEBUG_DUMP_PKGS_FILE must be defined |
| #endif |
| |
| #include <open62541/transport_generated_encoding_binary.h> |
| #include <open62541/types.h> |
| #include <open62541/types_generated_encoding_binary.h> |
| |
| #include "server/ua_server_internal.h" |
| |
| #include <stdio.h> |
| #include <stdlib.h> |
| #include <unistd.h> |
| |
| // This number is added to the end of every corpus data as 4 bytes. |
| // It allows to generate valid corpus and then the fuzzer will use |
| // these last 4 bytes to determine the simulated available RAM. |
| // The fuzzer will then fiddle around with this number and (hopefully) |
| // make it smaller, so that we can simulate Out-of-memory errors. |
| #define UA_DUMP_RAM_SIZE 8 * 1024 * 1024 |
| |
| unsigned int UA_dump_chunkCount = 0; |
| |
| char *UA_dump_messageTypes[] = {"ack", "hel", "msg", "opn", "clo", "err", "unk"}; |
| |
| struct UA_dump_filename { |
| const char *messageType; |
| char serviceName[100]; |
| }; |
| |
| void UA_debug_dumpCompleteChunk(UA_Server *const server, UA_Connection *const connection, |
| UA_ByteString *messageBuffer); |
| |
| /** |
| * Gets a pointer to the string representing the given message type from UA_dump_messageTypes. |
| * Used for naming the dumped file. |
| */ |
| static const char * |
| UA_debug_dumpGetMessageTypePrefix(UA_UInt32 messageType) { |
| switch(messageType & 0x00ffffff) { |
| case UA_MESSAGETYPE_ACK: |
| return UA_dump_messageTypes[0]; |
| case UA_MESSAGETYPE_HEL: |
| return UA_dump_messageTypes[1]; |
| case UA_MESSAGETYPE_MSG: |
| return UA_dump_messageTypes[2]; |
| case UA_MESSAGETYPE_OPN: |
| return UA_dump_messageTypes[3]; |
| case UA_MESSAGETYPE_CLO: |
| return UA_dump_messageTypes[4]; |
| case UA_MESSAGETYPE_ERR: |
| return UA_dump_messageTypes[5]; |
| default: |
| return UA_dump_messageTypes[6]; |
| } |
| } |
| |
| /** |
| * Decode the request message type from the given byte string and |
| * set the global requestServiceName variable to the name of the request. |
| * E.g. `GetEndpointsRequest` |
| */ |
| static UA_StatusCode |
| UA_debug_dumpSetServiceName(const UA_ByteString *msg, char serviceNameTarget[100]) { |
| /* At 0, the nodeid starts... */ |
| size_t offset = 0; |
| |
| /* Decode the nodeid */ |
| UA_NodeId requestTypeId; |
| UA_StatusCode retval = UA_NodeId_decodeBinary(msg, &offset, &requestTypeId); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| if(requestTypeId.identifierType != UA_NODEIDTYPE_NUMERIC || requestTypeId.namespaceIndex != 0) { |
| snprintf(serviceNameTarget, 100, "invalid_request_id"); |
| return UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| |
| const UA_DataType *requestType = NULL; |
| |
| for (size_t i=0; i<UA_TYPES_COUNT; i++) { |
| if (UA_TYPES[i].binaryEncodingId == requestTypeId.identifier.numeric) { |
| requestType = &UA_TYPES[i]; |
| break; |
| } |
| } |
| if (requestType == NULL) { |
| snprintf(serviceNameTarget, 100, "invalid_request_no_type"); |
| return UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| |
| snprintf(serviceNameTarget, 100, "_%s", requestType->typeName); |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| /** |
| * We need to decode the given binary message to get the name of the called service. |
| * This method is used if the connection has no channel yet. |
| */ |
| static UA_StatusCode |
| UA_debug_dump_setName_withoutChannel(UA_Server *server, UA_Connection *connection, |
| UA_ByteString *message, struct UA_dump_filename* dump_filename) { |
| size_t offset = 0; |
| UA_TcpMessageHeader tcpMessageHeader; |
| UA_StatusCode retval = |
| UA_TcpMessageHeader_decodeBinary(message, &offset, &tcpMessageHeader); |
| if(retval != UA_STATUSCODE_GOOD) |
| return retval; |
| |
| dump_filename->messageType = |
| UA_debug_dumpGetMessageTypePrefix(tcpMessageHeader.messageTypeAndChunkType & 0x00ffffff); |
| |
| if ((tcpMessageHeader.messageTypeAndChunkType & 0x00ffffff) == UA_MESSAGETYPE_MSG) { |
| // this should not happen in normal operation |
| UA_LOG_ERROR(&server->config.logger, UA_LOGCATEGORY_SERVER, "Got MSG package without channel."); |
| return UA_STATUSCODE_BADUNEXPECTEDERROR; |
| } |
| return UA_STATUSCODE_GOOD; |
| } |
| |
| |
| /** |
| * We need to decode the given binary message to get the name of the called service. |
| * This method is used if the connection an established secure channel. |
| * |
| * message is the decoded message starting at the nodeid of the content type. |
| */ |
| static void |
| UA_debug_dump_setName_withChannel(void *application, UA_SecureChannel *channel, |
| UA_MessageType messagetype, UA_UInt32 requestId, |
| const UA_ByteString *message) { |
| struct UA_dump_filename *dump_filename = (struct UA_dump_filename *)application; |
| dump_filename->messageType = UA_debug_dumpGetMessageTypePrefix(messagetype); |
| if(messagetype == UA_MESSAGETYPE_MSG) |
| UA_debug_dumpSetServiceName(message, dump_filename->serviceName); |
| } |
| |
| /** |
| * Called in processCompleteChunk for every complete chunk which is received by the server. |
| * |
| * It will first try to decode the message to get the name of the called service. |
| * When we have a name the message is dumped as binary to that file. |
| * If the file already exists a new file will be created with a counter at the end. |
| */ |
| void |
| UA_debug_dumpCompleteChunk(UA_Server *const server, UA_Connection *const connection, |
| UA_ByteString *messageBuffer) { |
| struct UA_dump_filename dump_filename; |
| dump_filename.messageType = NULL; |
| dump_filename.serviceName[0] = 0; |
| |
| if(!connection->channel) { |
| UA_debug_dump_setName_withoutChannel(server, connection, messageBuffer, &dump_filename); |
| } else { |
| UA_SecureChannel dummy = *connection->channel; |
| TAILQ_INIT(&dummy.messages); |
| UA_ByteString messageBufferCopy; |
| UA_ByteString_copy(messageBuffer, &messageBufferCopy); |
| UA_SecureChannel_decryptAddChunk(&dummy, &messageBufferCopy, UA_TRUE); |
| UA_SecureChannel_processCompleteMessages(&dummy, &dump_filename, UA_debug_dump_setName_withChannel); |
| UA_SecureChannel_deleteMessages(&dummy); |
| UA_ByteString_deleteMembers(&messageBufferCopy); |
| } |
| |
| char fileName[250]; |
| snprintf(fileName, sizeof(fileName), "%s/%05u_%s%s", UA_CORPUS_OUTPUT_DIR, ++UA_dump_chunkCount, |
| dump_filename.messageType ? dump_filename.messageType : "", dump_filename.serviceName); |
| |
| char dumpOutputFile[266]; |
| snprintf(dumpOutputFile, 255, "%s.bin", fileName); |
| // check if file exists and if yes create a counting filename to avoid overwriting |
| unsigned cnt = 1; |
| while ( access( dumpOutputFile, F_OK ) != -1 ) { |
| snprintf(dumpOutputFile, 266, "%s_%u.bin", fileName, cnt); |
| cnt++; |
| } |
| |
| UA_LOG_INFO(&server->config.logger, UA_LOGCATEGORY_SERVER, |
| "Dumping package %s", dumpOutputFile); |
| |
| FILE *write_ptr = fopen(dumpOutputFile, "ab"); |
| fwrite(messageBuffer->data, messageBuffer->length, 1, write_ptr); // write 10 bytes from our buffer |
| // add the available memory size. See the UA_DUMP_RAM_SIZE define for more info. |
| uint32_t ramSize = UA_DUMP_RAM_SIZE; |
| fwrite(&ramSize, sizeof(ramSize), 1, write_ptr); |
| fclose(write_ptr); |
| } |