Coverage Report

Created: 2026-10-15 06:50

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