Coverage Report

Created: 2026-08-13 07:04

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/libiec61850/fuzz/fuzz_sv_subscriber.c
Line
Count
Source
1
/*
2
 * libFuzzer + ASan harness for the IEC 61850-9-2 Sampled Values (SV) subscriber
3
 * decode path — the gap noted in GHSA-jh37-3w86-6rw8 / #598: GOOSE is harnessed
4
 * in fuzz/, SV was not, and SV is not on OSS-Fuzz, which is why parseASDU()
5
 * numeric-field OOB reads sat unfound.
6
 *
7
 * Entry point: SVReceiver_handleL2Message() — the public API (added 2026-07)
8
 * that feeds a raw Ethernet SV frame straight into the library, driving the
9
 * full receive chain: parseSVMessage -> parseSVPayload -> parseSequenceOfASDU
10
 * -> parseASDU -> listener -> SVSubscriber_ASDU_get*() accessors.
11
 *
12
 * The listener exercises EVERY numeric getter, exactly as a real SV application
13
 * would. Those accessors perform fixed-width reads (SmpCnt 2, ConfRev 4,
14
 * RefrTm 8, SmpSynch/SmpMod 1, SmpRate 2). Before a96bd67 parseASDU stored a
15
 * bare pointer for a too-short field and the accessor read past it; a96bd67
16
 * rejects the short field before the pointer store, so the accessor sees NULL.
17
 *
18
 * EXACT-SIZE BUFFER: the frame is copied into an exact-size malloc'd buffer so a
19
 * 1-byte over-read at frame end hits ASan's redzone instead of stack padding.
20
 */
21
#include <stdio.h>
22
#include <stdlib.h>
23
#include <string.h>
24
25
#include "sv_subscriber.h"
26
#include "hal_thread.h"
27
28
/* A real SV application reads the ASDU fields inside its listener callback.
29
 * Calling every accessor here is what turns a stored-but-too-short pointer into
30
 * an observable out-of-bounds read. */
31
static void
32
svListener(SVSubscriber subscriber, void* parameter, SVSubscriber_ASDU asdu)
33
8.98k
{
34
8.98k
    (void)subscriber;
35
8.98k
    (void)parameter;
36
37
8.98k
    (void)SVSubscriber_ASDU_getSmpCnt(asdu);   /* reads 2 bytes */
38
8.98k
    (void)SVSubscriber_ASDU_getConfRev(asdu);  /* reads 4 bytes */
39
8.98k
    (void)SVSubscriber_ASDU_getSmpSynch(asdu); /* reads 1 byte  */
40
8.98k
    (void)SVSubscriber_ASDU_getSmpMod(asdu);   /* reads 1 byte  */
41
8.98k
    (void)SVSubscriber_ASDU_getSmpRate(asdu);  /* reads 2 bytes */
42
43
8.98k
    if (SVSubscriber_ASDU_hasRefrTm(asdu)) {
44
546
        (void)SVSubscriber_ASDU_getRefrTmAsMs(asdu); /* reads 8 bytes */
45
546
        (void)SVSubscriber_ASDU_getRefrTmAsNs(asdu); /* reads 8 bytes */
46
546
    }
47
48
8.98k
    (void)SVSubscriber_ASDU_getSvId(asdu);
49
8.98k
    (void)SVSubscriber_ASDU_getDataSize(asdu);
50
8.98k
}
51
52
int
53
LLVMFuzzerTestOneInput(const uint8_t* data, size_t size)
54
908
{
55
    /* exact-size copy: any read at buf[size] traps on the ASan redzone */
56
908
    uint8_t* buf = (uint8_t*)malloc(size ? size : 1);
57
908
    if (!buf) {
58
0
        return 0;
59
0
    }
60
908
    if (size) {
61
908
        memcpy(buf, data, size);
62
908
    }
63
64
908
    SVReceiver receiver = SVReceiver_create();
65
    /* accept any destination MAC so the fuzzer reaches the decoder */
66
908
    SVReceiver_disableDestAddrCheck(receiver);
67
68
908
    uint8_t ethAddr[6] = { 0x01, 0x0c, 0xcd, 0x04, 0x00, 0x00 };
69
908
    SVSubscriber subscriber = SVSubscriber_create(ethAddr, 0x4000 /* APPID */);
70
908
    SVSubscriber_setListener(subscriber, svListener, NULL);
71
908
    SVReceiver_addSubscriber(receiver, subscriber);
72
73
908
    SVReceiver_handleL2Message(receiver, buf, (int)size);
74
75
908
    SVReceiver_destroy(receiver);
76
908
    free(buf);
77
908
    return 0;
78
908
}