Coverage Report

Created: 2026-06-30 06:45

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/open62541_15/tests/fuzz/fuzz_process_request.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
6
/*
7
 * Harness targeting the server-side service dispatch gate:
8
 *   processRequest()            (src/server/ua_services.c)
9
 *     -> processServiceInternal()  (src/server/ua_services.c)
10
 *
11
 * processServiceInternal() is the logic that runs *after* a request is decoded
12
 * but *before* the concrete Service_* callback is invoked: the request
13
 * timestamp check, the "discovery services only on an unencrypted channel"
14
 * gate, the CreateSession/ActivateSession/CloseSession lifecycle dispatch, the
15
 * anonymous-session setup, the "session required but not activated" rejection
16
 * and finally the service dispatch. It is static, reachable only through its
17
 * caller processRequest() (exported in ua_server_internal.h).
18
 *
19
 * This harness reproduces the small amount of processMSG() decoding logic
20
 * (decode the request type NodeId, look up the ServiceDescription, decode the
21
 * request body) and then calls processRequest() directly on an already-OPEN
22
 * SecureChannel. Compared to fuzz_binary_message -- which drives the full
23
 * TCP/secure-channel state machine and therefore has to synthesise a valid
24
 * HEL+OPN handshake before any MSG is processed -- this removes the deep
25
 * handshake prefix that prevents the dispatch gate from being reached in
26
 * practice.
27
 *
28
 * A single fuzz input is split into a *sequence* of request payloads processed
29
 * on the same channel. This lets the fuzzer build stateful sequences
30
 * (CreateSession -> ActivateSession -> a session-required service); the
31
 * FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION hooks in ua_services.c stash the
32
 * CreateSession authentication token and replay it on the following requests.
33
 */
34
35
#include <open62541/plugin/log_stdout.h>
36
#include <open62541/server_config_default.h>
37
#include <open62541/types.h>
38
39
#include "ua_server_internal.h"
40
#include "ua_securechannel.h"
41
#include "ua_services.h"
42
#include "ua_session.h"
43
#include "ua_types_encoding_binary.h"
44
#include "testing_networklayers.h"
45
46
/* File-local global in ua_services.c used by the fuzzing-only token replay
47
 * hooks. Only defined when the fuzzing build mode is active. */
48
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
49
extern "C" UA_NodeId unsafe_fuzz_authenticationToken;
50
#endif
51
52
/* Maximum number of request payloads processed for a single fuzz input. Bounds
53
 * the per-input work so the fuzzer does not time out on pathological inputs. */
54
4.40k
#define MAX_MESSAGES 32
55
56
/* Decode the request type NodeId + request body from a payload and run it
57
 * through processRequest(). Mirrors processMSG() (src/server/ua_server_binary.c). */
58
static void
59
processOnePayload(UA_Server *server, UA_SecureChannel *channel,
60
16.2k
                  UA_UInt32 requestId, const UA_ByteString *msg) {
61
16.2k
    size_t offset = 0;
62
63
16.2k
    UA_DecodeBinaryOptions opt;
64
16.2k
    memset(&opt, 0, sizeof(opt));
65
16.2k
    opt.customTypes = serverCustomTypes(server);
66
67
    /* Decode the request type NodeId */
68
16.2k
    UA_NodeId requestTypeId;
69
16.2k
    UA_NodeId_init(&requestTypeId);
70
16.2k
    if(UA_decodeBinaryInternal(msg, &offset, &requestTypeId,
71
16.2k
                               &UA_TYPES[UA_TYPES_NODEID], &opt) != UA_STATUSCODE_GOOD) {
72
6.90k
        UA_NodeId_clear(&requestTypeId);
73
6.90k
        return;
74
6.90k
    }
75
9.37k
    if(requestTypeId.namespaceIndex != 0 ||
76
8.82k
       requestTypeId.identifierType != UA_NODEIDTYPE_NUMERIC) {
77
605
        UA_NodeId_clear(&requestTypeId);
78
605
        return;
79
605
    }
80
81
    /* Look up the service */
82
8.77k
    UA_ServiceDescription *sd = getServiceDescription(requestTypeId.identifier.numeric);
83
8.77k
    UA_NodeId_clear(&requestTypeId);
84
8.77k
    if(!sd)
85
507
        return;
86
87
    /* Decode the request body */
88
8.26k
    UA_Request request;
89
8.26k
    if(UA_decodeBinaryInternal(msg, &offset, &request,
90
8.26k
                               sd->requestType, &opt) != UA_STATUSCODE_GOOD) {
91
2.82k
        UA_clear(&request, sd->requestType);
92
2.82k
        return;
93
2.82k
    }
94
95
    /* Initialise the response and dispatch through the gate */
96
5.44k
    UA_Response response;
97
5.44k
    UA_init(&response, sd->responseType);
98
5.44k
    response.responseHeader.requestHandle = request.requestHeader.requestHandle;
99
100
5.44k
    UA_LOCK(&server->serviceMutex);
101
5.44k
    processRequest(server, channel, requestId, sd, &request, &response);
102
5.44k
    UA_UNLOCK(&server->serviceMutex);
103
104
5.44k
    UA_clear(&request, sd->requestType);
105
5.44k
    UA_clear(&response, sd->responseType);
106
5.44k
}
107
108
extern "C" int
109
4.41k
LLVMFuzzerTestOneInput(const uint8_t *data, size_t size) {
110
    /* 1 control byte + at least one 2-byte length prefix */
111
4.41k
    if(size < 3)
112
4
        return 0;
113
114
    /* ---- Control byte: drive the configuration branches of the gate ---- */
115
4.40k
    const uint8_t ctrl = data[0];
116
4.40k
    data++;
117
4.40k
    size--;
118
119
4.40k
    UA_ServerConfig config;
120
4.40k
    memset(&config, 0, sizeof(UA_ServerConfig));
121
4.40k
    UA_StatusCode retval = UA_ServerConfig_setDefault(&config);
122
4.40k
    if(retval != UA_STATUSCODE_GOOD) {
123
0
        UA_ServerConfig_clean(&config);
124
0
        return 0;
125
0
    }
126
4.40k
    config.allowEmptyVariables = UA_RULEHANDLING_ACCEPT;
127
    /* bits 0-1: how strictly a missing request timestamp is handled */
128
4.40k
    config.verifyRequestTimestamp = (UA_RuleHandling)(ctrl & 0x03);
129
    /* bit 2: only allow discovery services on an unencrypted (#None) channel */
130
4.40k
    config.securityPolicyNoneDiscoveryOnly = (ctrl & 0x04) ? true : false;
131
132
4.40k
    UA_Server *server = UA_Server_newWithConfig(&config);
133
4.40k
    if(!server) {
134
0
        UA_ServerConfig_clean(&config);
135
0
        return 0;
136
0
    }
137
138
    /* The default config always installs the #None SecurityPolicy first. */
139
4.40k
    if(server->config.securityPoliciesSize == 0) {
140
0
        UA_Server_delete(server);
141
0
        return 0;
142
0
    }
143
144
    /* A test ConnectionManager makes any send paths reachable from the services
145
     * (e.g. async/publish responses) safe no-ops instead of touching a socket. */
146
4.40k
    UA_ConnectionManager *cm = TestConnectionManager_new("tcp", NULL);
147
4.40k
    if(!cm) {
148
0
        UA_Server_delete(server);
149
0
        return 0;
150
0
    }
151
152
    /* ---- Build an already-OPEN SecureChannel bound to the #None policy ---- */
153
4.40k
    UA_SecureChannel channel;
154
4.40k
    UA_SecureChannel_init(&channel);
155
4.40k
    channel.connectionManager = cm;
156
4.40k
    channel.connectionId = 1;
157
    /* Buffer sizes are normally negotiated during the HEL/ACK handshake. We set
158
     * sane values directly so any response encoding chunks correctly instead of
159
     * tripping on the default zero-initialised config. 0 == unbounded. */
160
4.40k
    channel.config.protocolVersion = 0;
161
4.40k
    channel.config.recvBufferSize = 1 << 16;
162
4.40k
    channel.config.sendBufferSize = 1 << 16;
163
4.40k
    channel.config.localMaxMessageSize = 0;
164
4.40k
    channel.config.remoteMaxMessageSize = 0;
165
4.40k
    channel.config.localMaxChunkCount = 0;
166
4.40k
    channel.config.remoteMaxChunkCount = 0;
167
168
4.40k
    UA_ByteString noCertificate = UA_BYTESTRING_NULL;
169
4.40k
    retval = UA_SecureChannel_setSecurityPolicy(&channel,
170
4.40k
                                                &server->config.securityPolicies[0],
171
4.40k
                                                &noCertificate);
172
4.40k
    if(retval != UA_STATUSCODE_GOOD) {
173
0
        cm->eventSource.free(&cm->eventSource);
174
0
        UA_Server_delete(server);
175
0
        return 0;
176
0
    }
177
4.40k
    channel.state = UA_SECURECHANNELSTATE_OPEN;
178
4.40k
    channel.securityToken.channelId = 1;
179
4.40k
    channel.securityToken.tokenId = 1;
180
181
    /* Start each input from a clean token so message sequences are
182
     * deterministic and do not leak across fuzzer iterations. */
183
4.40k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
184
4.40k
    UA_NodeId_clear(&unsafe_fuzz_authenticationToken);
185
4.40k
#endif
186
187
    /* ---- Feed a sequence of length-prefixed request payloads ----
188
     * Layout (after the control byte): repeated records of
189
     *   [u16 big-endian length][request payload]
190
     * Each payload begins at the request type NodeId. Processing several
191
     * records on the same channel enables stateful sessions. */
192
4.40k
    UA_UInt32 requestId = 1;
193
4.40k
    size_t pos = 0;
194
4.40k
    int budget = MAX_MESSAGES;
195
20.6k
    while(pos + 2 <= size && budget-- > 0) {
196
16.2k
        size_t len = ((size_t)data[pos] << 8) | (size_t)data[pos + 1];
197
16.2k
        pos += 2;
198
16.2k
        if(len > size - pos)
199
4.23k
            len = size - pos; /* clamp the final, possibly truncated, record */
200
201
16.2k
        UA_ByteString msg;
202
16.2k
        msg.length = len;
203
16.2k
        msg.data = (UA_Byte *)(uintptr_t)(data + pos);
204
16.2k
        pos += len;
205
206
16.2k
        if(channel.state != UA_SECURECHANNELSTATE_OPEN)
207
0
            break;
208
209
16.2k
        processOnePayload(server, &channel, requestId++, &msg);
210
16.2k
    }
211
212
    /* ---- Teardown ---- */
213
4.40k
    UA_LOCK(&server->serviceMutex);
214
    /* A successful CreateSession binds a Session to the channel. These must be
215
     * detached (which also drops outstanding Publish requests) before clearing
216
     * the channel -- UA_SecureChannel_clear asserts channel->sessions == NULL.
217
     * This mirrors the server's own removeSecureChannel() teardown. */
218
8.76k
    while(channel.sessions)
219
4.35k
        UA_Session_detachFromSecureChannel(server, channel.sessions);
220
4.40k
    UA_SecureChannel_clear(&channel);
221
4.40k
    UA_UNLOCK(&server->serviceMutex);
222
223
4.40k
#ifdef FUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
224
4.40k
    UA_NodeId_clear(&unsafe_fuzz_authenticationToken);
225
4.40k
#endif
226
227
4.40k
    cm->eventSource.free(&cm->eventSource);
228
4.40k
    UA_Server_delete(server);
229
4.40k
    return 0;
230
4.40k
}