Coverage Report

Created: 2026-11-05 07:00

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
3
removeServerComponent(void *application, UA_ServerComponent *sc) {
21
3
    UA_assert(sc->state == UA_LIFECYCLESTATE_STOPPED);
22
3
    sc->clear(sc);
23
3
    UA_free(sc);
24
3
    return NULL;
25
3
}
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
1
        : server(NULL), bpm(NULL), cm(NULL), listenerContext(NULL), nextConnectionId(2) {
48
1
        UA_ServerConfig config;
49
1
        memset(&config, 0, sizeof(config));
50
1
        config.logging = &silentLogger;
51
1
        if(UA_ServerConfig_setDefault(&config) != UA_STATUSCODE_GOOD) {
52
0
            UA_ServerConfig_clear(&config);
53
0
            return;
54
0
        }
55
1
        config.allowEmptyVariables = UA_RULEHANDLING_ACCEPT;
56
57
1
        server = UA_Server_newWithConfig(&config);
58
1
        if(!server)
59
0
            return;
60
61
        /* Replace the configured transport with the deterministic test transport. */
62
1
        lockServer(server);
63
1
        ZIP_ITER(UA_ServerComponentTree, &server->serverComponents,
64
1
                 removeServerComponent, server);
65
1
        ZIP_INIT(&server->serverComponents);
66
1
        unlockServer(server);
67
1
        bpm = UA_BinaryProtocolManager_new(server);
68
1
        if(!bpm)
69
0
            return;
70
1
        addServerComponent(server, bpm, NULL);
71
72
1
        cm = TestConnectionManager_new("tcp", NULL);
73
1
        if(!cm)
74
0
            return;
75
76
        /* Keep the server socket and protocol manager for the fuzz process. */
77
1
        serverNetworkCallback(cm, 1, bpm, &listenerContext,
78
1
                              UA_CONNECTIONSTATE_ESTABLISHED,
79
1
                              &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
80
1
    }
81
82
3
    ~FuzzServer() {
83
3
        if(listenerContext)
84
3
            serverNetworkCallback(cm, 1, bpm, &listenerContext,
85
3
                                  UA_CONNECTIONSTATE_CLOSING,
86
3
                                  &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
87
3
        if(server)
88
3
            UA_Server_delete(server);
89
3
        if(cm)
90
3
            cm->eventSource.free(&cm->eventSource);
91
3
    }
92
93
    uintptr_t
94
13.1k
    getConnectionId() {
95
13.1k
        uintptr_t connectionId = nextConnectionId++;
96
13.1k
        if(nextConnectionId <= 1)
97
0
            nextConnectionId = 2;
98
13.1k
        return connectionId;
99
13.1k
    }
100
};
101
102
static FuzzServer &
103
5.21k
getFuzzServer(void) {
104
5.21k
    static FuzzServer fuzzServer;
105
5.21k
    return fuzzServer;
106
5.21k
}
107
108
static void
109
73.0k
writeUInt32(UA_Byte *buf, size_t *offset, UA_UInt32 value) {
110
73.0k
    buf[(*offset)++] = (UA_Byte)value;
111
73.0k
    buf[(*offset)++] = (UA_Byte)(value >> 8);
112
73.0k
    buf[(*offset)++] = (UA_Byte)(value >> 16);
113
73.0k
    buf[(*offset)++] = (UA_Byte)(value >> 24);
114
73.0k
}
115
116
static UA_ByteString
117
5.21k
makeHello(void) {
118
5.21k
    static const char endpointUrl[] = "opc.tcp://localhost:4840";
119
5.21k
    const size_t messageSize = 8 + 20 + 4 + sizeof(endpointUrl) - 1;
120
5.21k
    UA_ByteString message = UA_BYTESTRING_NULL;
121
5.21k
    if(UA_ByteString_allocBuffer(&message, messageSize) != UA_STATUSCODE_GOOD)
122
0
        return message;
123
124
5.21k
    size_t offset = 0;
125
5.21k
    memcpy(&message.data[offset], "HELF", 4);
126
5.21k
    offset += 4;
127
5.21k
    writeUInt32(message.data, &offset, (UA_UInt32)messageSize);
128
5.21k
    writeUInt32(message.data, &offset, 0);
129
5.21k
    writeUInt32(message.data, &offset, 65535);
130
5.21k
    writeUInt32(message.data, &offset, 65535);
131
5.21k
    writeUInt32(message.data, &offset, 0);
132
5.21k
    writeUInt32(message.data, &offset, 0);
133
5.21k
    writeUInt32(message.data, &offset, (UA_UInt32)(sizeof(endpointUrl) - 1));
134
5.21k
    memcpy(&message.data[offset], endpointUrl, sizeof(endpointUrl) - 1);
135
5.21k
    return message;
136
5.21k
}
137
138
static UA_ByteString
139
5.21k
makeOPN(const uint8_t *payload, size_t payloadSize) {
140
5.21k
    static const char policyUri[] =
141
5.21k
        "http://opcfoundation.org/UA/SecurityPolicy#None";
142
5.21k
    const size_t headerSize = 8 + 4 + 4 + sizeof(policyUri) - 1 + 4 + 4 + 8;
143
5.21k
    UA_ByteString message = UA_BYTESTRING_NULL;
144
5.21k
    if(payloadSize > UA_UINT32_MAX - headerSize)
145
0
        return message;
146
5.21k
    const size_t messageSize = headerSize + payloadSize;
147
5.21k
    if(UA_ByteString_allocBuffer(&message, messageSize) != UA_STATUSCODE_GOOD)
148
0
        return message;
149
150
5.21k
    size_t offset = 0;
151
5.21k
    memcpy(&message.data[offset], "OPNF", 4);
152
5.21k
    offset += 4;
153
5.21k
    writeUInt32(message.data, &offset, (UA_UInt32)messageSize);
154
5.21k
    writeUInt32(message.data, &offset, 0); /* SecureChannelId */
155
5.21k
    writeUInt32(message.data, &offset, (UA_UInt32)(sizeof(policyUri) - 1));
156
5.21k
    memcpy(&message.data[offset], policyUri, sizeof(policyUri) - 1);
157
5.21k
    offset += sizeof(policyUri) - 1;
158
5.21k
    writeUInt32(message.data, &offset, UA_UINT32_MAX); /* No sender certificate */
159
5.21k
    writeUInt32(message.data, &offset, UA_UINT32_MAX); /* No receiver thumbprint */
160
5.21k
    writeUInt32(message.data, &offset, 1); /* SequenceNumber */
161
5.21k
    writeUInt32(message.data, &offset, 1); /* RequestId */
162
5.21k
    memcpy(&message.data[offset], payload, payloadSize);
163
5.21k
    return message;
164
5.21k
}
165
166
static void
167
processMessage(UA_ConnectionManager *cm, UA_ServerComponent *bpm, uintptr_t connectionId,
168
               void **connectionContext,
169
5.20k
               UA_ByteString message) {
170
5.20k
    serverNetworkCallback(cm, connectionId, bpm,
171
5.20k
                          connectionContext,
172
5.20k
                          UA_CONNECTIONSTATE_ESTABLISHED,
173
5.20k
                          &UA_KEYVALUEMAP_NULL, message);
174
5.20k
    UA_ByteString_clear(&message);
175
5.20k
}
176
177
extern "C" int
178
5.21k
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
179
    /* Generated corpus entries contain complete OPN chunks. Reuse their
180
     * decoded service payload and ignore the optional four-byte memory-limit
181
     * trailer appended by the corpus generator. */
182
5.21k
    if(size >= 79 && memcmp(data, "OPN", 3) == 0) {
183
145
        UA_UInt32 declaredSize = (UA_UInt32)data[4] |
184
145
            ((UA_UInt32)data[5] << 8) | ((UA_UInt32)data[6] << 16) |
185
145
            ((UA_UInt32)data[7] << 24);
186
145
        if(declaredSize >= 79 && declaredSize <= size) {
187
40
            data += 79;
188
40
            size = declaredSize - 79;
189
40
        }
190
145
    }
191
192
5.21k
    FuzzServer &fuzzServer = getFuzzServer();
193
5.21k
    if(!fuzzServer.bpm || !fuzzServer.listenerContext)
194
0
        return 0;
195
196
    /* Create only disposable connection state for this iteration. */
197
5.21k
    uintptr_t connectionId = fuzzServer.getConnectionId();
198
5.21k
    void *connectionContext = fuzzServer.listenerContext;
199
5.21k
    serverNetworkCallback(fuzzServer.cm, connectionId, fuzzServer.bpm,
200
5.21k
                          &connectionContext,
201
5.21k
                          UA_CONNECTIONSTATE_ESTABLISHED,
202
5.21k
                          &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
203
204
5.21k
    UA_ByteString hello = makeHello();
205
5.21k
    if(hello.data)
206
5.21k
        processMessage(fuzzServer.cm, fuzzServer.bpm, connectionId, &connectionContext, hello);
207
208
5.21k
    UA_ByteString opn = makeOPN(data, size);
209
5.21k
    if(opn.data)
210
5.21k
        processMessage(fuzzServer.cm, fuzzServer.bpm, connectionId, &connectionContext, opn);
211
212
    /* Drop the channel so the next mutation starts from CONNECTED again. */
213
5.21k
    if(connectionContext)
214
5.21k
        serverNetworkCallback(fuzzServer.cm, connectionId,
215
5.21k
                              fuzzServer.bpm, &connectionContext,
216
5.21k
                              UA_CONNECTIONSTATE_CLOSING,
217
5.21k
                              &UA_KEYVALUEMAP_NULL, UA_BYTESTRING_NULL);
218
5.21k
    return 0;
219
5.21k
}