Coverage Report

Created: 2026-09-27 07:10

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/open62541_15/tests/fuzz/fuzz_opn_message.cc
Line
Count
Source
1
/* This Source Code Form is subject to the terms of the Mozilla Public
2
 * License, v. 2.0. If a copy of the MPL was not distributed with this
3
 * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
4
5
#include <open62541/server_config_default.h>
6
7
#include "ua_server_internal.h"
8
#include "testing_networklayers.h"
9
10
#include <stdint.h>
11
#include <stdarg.h>
12
#include <string.h>
13
14
/* The fixed envelope uses SecurityPolicy None. It still passes through the
15
 * production asymmetric-header and sequence-header decoders, while avoiding
16
 * signatures and encryption that would reject almost every mutation before
17
 * the OpenSecureChannelRequest decoder is reached. */
18
19
static void *
20
0
removeServerComponent(void *application, UA_ServerComponent *sc) {
21
0
    UA_assert(sc->state == UA_LIFECYCLESTATE_STOPPED);
22
0
    sc->clear(sc);
23
0
    UA_free(sc);
24
0
    return NULL;
25
0
}
26
27
static void
28
silentLog(void *context, UA_LogLevel level, UA_LogCategory category,
29
0
          const char *message, va_list args) {
30
0
    (void)context;
31
0
    (void)level;
32
0
    (void)category;
33
0
    (void)message;
34
0
    (void)args;
35
0
}
36
37
static UA_Logger silentLogger = {silentLog, NULL, NULL};
38
39
struct FuzzServer {
40
    UA_Server *server;
41
    UA_ServerComponent *bpm;
42
    UA_ConnectionManager *cm;
43
    void *listenerContext;
44
    uintptr_t nextConnectionId;
45
46
    FuzzServer()
47
0
        : server(NULL), bpm(NULL), cm(NULL), listenerContext(NULL), nextConnectionId(2) {
48
0
        UA_ServerConfig config;
49
0
        memset(&config, 0, sizeof(config));
50
0
        config.logging = &silentLogger;
51
0
        if(UA_ServerConfig_setDefault(&config) != UA_STATUSCODE_GOOD) {
52
0
            UA_ServerConfig_clear(&config);
53
0
            return;
54
0
        }
55
0
        config.allowEmptyVariables = UA_RULEHANDLING_ACCEPT;
56
57
0
        server = UA_Server_newWithConfig(&config);
58
0
        if(!server)
59
0
            return;
60
61
        /* Replace the configured transport with the deterministic test transport. */
62
0
        ZIP_ITER(UA_ServerComponentTree, &server->serverComponents,
63
0
                 removeServerComponent, server);
64
0
        ZIP_INIT(&server->serverComponents);
65
0
        bpm = UA_BinaryProtocolManager_new(server);
66
0
        if(!bpm)
67
0
            return;
68
0
        addServerComponent(server, bpm, NULL);
69
70
0
        cm = TestConnectionManager_new("tcp", NULL);
71
0
        if(!cm)
72
0
            return;
73
74
        /* Keep the server socket and protocol manager for the fuzz process. */
75
0
        serverNetworkCallback(cm, 1, bpm, &listenerContext,
76
0
                              UA_CONNECTIONSTATE_ESTABLISHED,
77
0
                              &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
78
0
    }
79
80
0
    ~FuzzServer() {
81
0
        if(listenerContext)
82
0
            serverNetworkCallback(cm, 1, bpm, &listenerContext,
83
0
                                  UA_CONNECTIONSTATE_CLOSING,
84
0
                                  &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
85
0
        if(server)
86
0
            UA_Server_delete(server);
87
0
        if(cm)
88
0
            cm->eventSource.free(&cm->eventSource);
89
0
    }
90
91
    uintptr_t
92
0
    getConnectionId() {
93
0
        uintptr_t connectionId = nextConnectionId++;
94
0
        if(nextConnectionId <= 1)
95
0
            nextConnectionId = 2;
96
0
        return connectionId;
97
0
    }
98
};
99
100
static FuzzServer &
101
0
getFuzzServer(void) {
102
0
    static FuzzServer fuzzServer;
103
0
    return fuzzServer;
104
0
}
105
106
static void
107
0
writeUInt32(UA_Byte *buf, size_t *offset, UA_UInt32 value) {
108
0
    buf[(*offset)++] = (UA_Byte)value;
109
0
    buf[(*offset)++] = (UA_Byte)(value >> 8);
110
0
    buf[(*offset)++] = (UA_Byte)(value >> 16);
111
0
    buf[(*offset)++] = (UA_Byte)(value >> 24);
112
0
}
113
114
static UA_ByteString
115
0
makeHello(void) {
116
0
    static const char endpointUrl[] = "opc.tcp://localhost:4840";
117
0
    const size_t messageSize = 8 + 20 + 4 + sizeof(endpointUrl) - 1;
118
0
    UA_ByteString message = UA_BYTESTRING_NULL;
119
0
    if(UA_ByteString_allocBuffer(&message, messageSize) != UA_STATUSCODE_GOOD)
120
0
        return message;
121
122
0
    size_t offset = 0;
123
0
    memcpy(&message.data[offset], "HELF", 4);
124
0
    offset += 4;
125
0
    writeUInt32(message.data, &offset, (UA_UInt32)messageSize);
126
0
    writeUInt32(message.data, &offset, 0);
127
0
    writeUInt32(message.data, &offset, 65535);
128
0
    writeUInt32(message.data, &offset, 65535);
129
0
    writeUInt32(message.data, &offset, 0);
130
0
    writeUInt32(message.data, &offset, 0);
131
0
    writeUInt32(message.data, &offset, (UA_UInt32)(sizeof(endpointUrl) - 1));
132
0
    memcpy(&message.data[offset], endpointUrl, sizeof(endpointUrl) - 1);
133
0
    return message;
134
0
}
135
136
static UA_ByteString
137
0
makeOPN(const uint8_t *payload, size_t payloadSize) {
138
0
    static const char policyUri[] =
139
0
        "http://opcfoundation.org/UA/SecurityPolicy#None";
140
0
    const size_t headerSize = 8 + 4 + 4 + sizeof(policyUri) - 1 + 4 + 4 + 8;
141
0
    UA_ByteString message = UA_BYTESTRING_NULL;
142
0
    if(payloadSize > UA_UINT32_MAX - headerSize)
143
0
        return message;
144
0
    const size_t messageSize = headerSize + payloadSize;
145
0
    if(UA_ByteString_allocBuffer(&message, messageSize) != UA_STATUSCODE_GOOD)
146
0
        return message;
147
148
0
    size_t offset = 0;
149
0
    memcpy(&message.data[offset], "OPNF", 4);
150
0
    offset += 4;
151
0
    writeUInt32(message.data, &offset, (UA_UInt32)messageSize);
152
0
    writeUInt32(message.data, &offset, 0); /* SecureChannelId */
153
0
    writeUInt32(message.data, &offset, (UA_UInt32)(sizeof(policyUri) - 1));
154
0
    memcpy(&message.data[offset], policyUri, sizeof(policyUri) - 1);
155
0
    offset += sizeof(policyUri) - 1;
156
0
    writeUInt32(message.data, &offset, UA_UINT32_MAX); /* No sender certificate */
157
0
    writeUInt32(message.data, &offset, UA_UINT32_MAX); /* No receiver thumbprint */
158
0
    writeUInt32(message.data, &offset, 1); /* SequenceNumber */
159
0
    writeUInt32(message.data, &offset, 1); /* RequestId */
160
0
    memcpy(&message.data[offset], payload, payloadSize);
161
0
    return message;
162
0
}
163
164
static void
165
processMessage(UA_ConnectionManager *cm, UA_ServerComponent *bpm, uintptr_t connectionId,
166
               void **connectionContext,
167
0
               UA_ByteString message) {
168
0
    serverNetworkCallback(cm, connectionId, bpm,
169
0
                          connectionContext,
170
0
                          UA_CONNECTIONSTATE_ESTABLISHED,
171
0
                          &UA_KEYVALUEMAP_NULL, message);
172
0
    UA_ByteString_clear(&message);
173
0
}
174
175
extern "C" int
176
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
177
    /* Generated corpus entries contain complete OPN chunks. Reuse their
178
     * decoded service payload and ignore the optional four-byte memory-limit
179
     * trailer appended by the corpus generator. */
180
    if(size >= 79 && memcmp(data, "OPN", 3) == 0) {
181
        UA_UInt32 declaredSize = (UA_UInt32)data[4] |
182
            ((UA_UInt32)data[5] << 8) | ((UA_UInt32)data[6] << 16) |
183
            ((UA_UInt32)data[7] << 24);
184
        if(declaredSize >= 79 && declaredSize <= size) {
185
            data += 79;
186
            size = declaredSize - 79;
187
        }
188
    }
189
190
    FuzzServer &fuzzServer = getFuzzServer();
191
    if(!fuzzServer.bpm || !fuzzServer.listenerContext)
192
        return 0;
193
194
    /* Create only disposable connection state for this iteration. */
195
    uintptr_t connectionId = fuzzServer.getConnectionId();
196
    void *connectionContext = fuzzServer.listenerContext;
197
    serverNetworkCallback(fuzzServer.cm, connectionId, fuzzServer.bpm,
198
                          &connectionContext,
199
                          UA_CONNECTIONSTATE_ESTABLISHED,
200
                          &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
201
202
    UA_ByteString hello = makeHello();
203
    if(hello.data)
204
        processMessage(fuzzServer.cm, fuzzServer.bpm, connectionId, &connectionContext, hello);
205
206
    UA_ByteString opn = makeOPN(data, size);
207
    if(opn.data)
208
        processMessage(fuzzServer.cm, fuzzServer.bpm, connectionId, &connectionContext, opn);
209
210
    /* Drop the channel so the next mutation starts from CONNECTED again. */
211
    if(connectionContext)
212
        serverNetworkCallback(fuzzServer.cm, connectionId,
213
                              fuzzServer.bpm, &connectionContext,
214
                              UA_CONNECTIONSTATE_CLOSING,
215
                              &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
216
    return 0;
217
}