Coverage Report

Created: 2025-07-18 06:33

/src/zydis/tools/ZydisFuzzEncoder.c
Line
Count
Source (jump to first uncovered line)
1
/***************************************************************************************************
2
3
  Zyan Disassembler Library (Zydis)
4
5
  Original Author : Mappa
6
7
 * Permission is hereby granted, free of charge, to any person obtaining a copy
8
 * of this software and associated documentation files (the "Software"), to deal
9
 * in the Software without restriction, including without limitation the rights
10
 * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
11
 * copies of the Software, and to permit persons to whom the Software is
12
 * furnished to do so, subject to the following conditions:
13
 *
14
 * The above copyright notice and this permission notice shall be included in all
15
 * copies or substantial portions of the Software.
16
 *
17
 * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
18
 * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
19
 * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
20
 * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
21
 * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
22
 * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
23
 * SOFTWARE.
24
25
***************************************************************************************************/
26
27
/**
28
 * @file
29
 *
30
 * This file implements fuzz target for encoder.
31
 */
32
33
#include "ZydisFuzzShared.h"
34
35
/* ============================================================================================== */
36
/* Fuzz target                                                                                    */
37
/* ============================================================================================== */
38
39
// TODO: This could check `EVEX`/`MVEX` stuff as well
40
void ZydisCompareRequestToInstruction(const ZydisEncoderRequest *request,
41
    const ZydisDecodedInstruction *insn, const ZydisDecodedOperand* operands)
42
1.31k
{
43
    // Special case, `xchg rAX, rAX` is an alias for `NOP`
44
1.31k
    if ((request->mnemonic == ZYDIS_MNEMONIC_XCHG) &&
45
1.31k
        (request->operand_count == 2) &&
46
1.31k
        (request->operands[0].type == ZYDIS_OPERAND_TYPE_REGISTER) &&
47
1.31k
        (request->operands[1].type == ZYDIS_OPERAND_TYPE_REGISTER) &&
48
1.31k
        (request->operands[0].reg.value == request->operands[1].reg.value) &&
49
1.31k
        (insn->mnemonic == ZYDIS_MNEMONIC_NOP))
50
4
    {
51
4
        switch (request->operands[0].reg.value)
52
4
        {
53
1
        case ZYDIS_REGISTER_AX:
54
3
        case ZYDIS_REGISTER_EAX:
55
4
        case ZYDIS_REGISTER_RAX:
56
4
            return;
57
0
        default:
58
0
            break;
59
4
        }
60
4
    }
61
62
1.31k
    ZyanBool prefixes_match = ((insn->attributes & request->prefixes) == request->prefixes);
63
1.31k
    if (!prefixes_match &&
64
1.31k
        (request->machine_mode != ZYDIS_MACHINE_MODE_LONG_64) &&
65
1.31k
        (request->prefixes & ZYDIS_ATTRIB_HAS_SEGMENT_DS))
66
7
    {
67
        // Encoder allows specifying DS override even when it might be interpreted as NOTRACK
68
7
        ZyanU64 acceptable_prefixes = (request->prefixes & (~ZYDIS_ATTRIB_HAS_SEGMENT_DS)) |
69
7
                                      ZYDIS_ATTRIB_HAS_NOTRACK;
70
7
        prefixes_match = ((insn->attributes & acceptable_prefixes) == acceptable_prefixes);
71
7
    }
72
1.31k
    if ((request->machine_mode != insn->machine_mode) ||
73
1.31k
        (request->mnemonic != insn->mnemonic) ||
74
1.31k
        (request->operand_count != insn->operand_count_visible) ||
75
1.31k
        !prefixes_match)
76
0
    {
77
0
        fputs("Basic instruction attributes mismatch\n", ZYAN_STDERR);
78
0
        abort();
79
0
    }
80
81
3.87k
    for (ZyanU8 i = 0; i < insn->operand_count_visible; ++i)
82
2.56k
    {
83
2.56k
        const ZydisEncoderOperand *op1 = &request->operands[i];
84
2.56k
        const ZydisDecodedOperand *op2 = &operands[i];
85
2.56k
        if (op1->type != op2->type)
86
0
        {
87
0
            fprintf(ZYAN_STDERR, "Mismatch for operand %u\n", i);
88
0
            abort();
89
0
        }
90
2.56k
        switch (op1->type)
91
2.56k
        {
92
1.35k
        case ZYDIS_OPERAND_TYPE_REGISTER:
93
1.35k
            if (op1->reg.value != op2->reg.value)
94
0
            {
95
0
                fprintf(ZYAN_STDERR, "Mismatch for register operand %u\n", i);
96
0
                abort();
97
0
            }
98
1.35k
            break;
99
1.35k
        case ZYDIS_OPERAND_TYPE_MEMORY:
100
595
            if ((op1->mem.base != op2->mem.base) ||
101
595
                (op1->mem.index != op2->mem.index) ||
102
595
                (op1->mem.scale != op2->mem.scale && op2->mem.type != ZYDIS_MEMOP_TYPE_MIB) ||
103
595
                (op1->mem.displacement != op2->mem.disp.value))
104
0
            {
105
0
                ZyanBool acceptable_mismatch = ZYAN_FALSE;
106
0
                if (op1->mem.displacement != op2->mem.disp.value)
107
0
                {
108
0
                    if ((op2->mem.disp.size) &&
109
0
                        (op1->mem.index == ZYDIS_REGISTER_NONE) &&
110
0
                        ((op1->mem.base == ZYDIS_REGISTER_NONE) ||
111
0
                         (op1->mem.base == ZYDIS_REGISTER_EIP) ||
112
0
                         (op1->mem.base == ZYDIS_REGISTER_RIP)))
113
0
                    {
114
0
                        ZyanU64 addr;
115
0
                        ZydisCalcAbsoluteAddress(insn, op2, 0, &addr);
116
0
                        acceptable_mismatch = ((ZyanU64)op1->mem.displacement == addr);
117
0
                    }
118
0
                    if ((insn->machine_mode == ZYDIS_MACHINE_MODE_REAL_16) ||
119
0
                        (insn->machine_mode == ZYDIS_MACHINE_MODE_LEGACY_16) ||
120
0
                        (insn->machine_mode == ZYDIS_MACHINE_MODE_LONG_COMPAT_16) ||
121
0
                        (insn->stack_width == 16) ||
122
0
                        (insn->address_width == 16))
123
0
                    {
124
0
                        acceptable_mismatch = ((op1->mem.displacement & 0xFFFF) ==
125
0
                            (op2->mem.disp.value & 0xFFFF));
126
0
                    }
127
0
                }
128
0
                if (!acceptable_mismatch)
129
0
                {
130
0
                    fprintf(ZYAN_STDERR, "Mismatch for memory operand %u\n", i);
131
0
                    abort();
132
0
                }
133
0
            }
134
595
            break;
135
595
        case ZYDIS_OPERAND_TYPE_POINTER:
136
37
            if ((op1->ptr.segment != op2->ptr.segment) ||
137
37
                (op1->ptr.offset != op2->ptr.offset))
138
0
            {
139
0
                fprintf(ZYAN_STDERR, "Mismatch for pointer operand %u\n", i);
140
0
                abort();
141
0
            }
142
37
            break;
143
574
        case ZYDIS_OPERAND_TYPE_IMMEDIATE:
144
574
            if (op1->imm.u != op2->imm.value.u)
145
0
            {
146
0
                ZyanBool acceptable_mismatch = ZYAN_FALSE;
147
0
                if ((insn->meta.category == ZYDIS_CATEGORY_DATAXFER) ||
148
0
                    (insn->meta.category == ZYDIS_CATEGORY_LOGICAL))
149
0
                {
150
0
                    if (op2->size < 64)
151
0
                    {
152
0
                        ZyanU64 mask = (1ULL << op2->size) - 1;
153
0
                        acceptable_mismatch =
154
0
                            (op1->imm.u & mask) == (op2->imm.value.u & mask);
155
0
                    }
156
0
                    else
157
0
                    {
158
0
                        acceptable_mismatch = op1->imm.u == op2->imm.value.u;
159
0
                    }
160
0
                }
161
0
                if (!acceptable_mismatch)
162
0
                {
163
0
                    fprintf(ZYAN_STDERR, "Mismatch for immediate operand %u\n", i);
164
0
                    abort();
165
0
                }
166
0
            }
167
574
            break;
168
574
        default:
169
0
            fprintf(ZYAN_STDERR, "Invalid operand type for operand %u\n", i);
170
0
            abort();
171
2.56k
        }
172
2.56k
    }
173
1.31k
}
174
175
ZYAN_NO_SANITIZE("enum")
176
int ZydisFuzzTarget(ZydisStreamRead read_fn, void *stream_ctx)
177
3.16k
{
178
3.16k
    ZydisEncoderRequest request;
179
3.16k
    if (read_fn(stream_ctx, (ZyanU8 *)&request, sizeof(request)) != sizeof(request))
180
23
    {
181
23
        ZYDIS_MAYBE_FPUTS("Not enough bytes to fuzz\n", ZYAN_STDERR);
182
23
        return EXIT_SUCCESS;
183
23
    }
184
185
    // Sanitization greatly improves coverage, without it most inputs will fail at basic checks
186
    // inside `ZydisEncoderCheckRequestSanity`
187
3.13k
    request.operand_count %= ZYDIS_ENCODER_MAX_OPERANDS + 1;
188
3.13k
    ZYDIS_SANITIZE_MASK32(request.allowed_encodings, ZydisEncodableEncoding,
189
3.13k
        ZYDIS_ENCODABLE_ENCODING_MAX_VALUE);
190
3.13k
    ZYDIS_SANITIZE_MASK64(request.prefixes, ZydisInstructionAttributes, ZYDIS_ENCODABLE_PREFIXES);
191
3.13k
    ZYDIS_SANITIZE_ENUM(request.machine_mode, ZydisMachineMode, ZYDIS_MACHINE_MODE_MAX_VALUE);
192
3.13k
    ZYDIS_SANITIZE_ENUM(request.mnemonic, ZydisMnemonic, ZYDIS_MNEMONIC_MAX_VALUE);
193
3.13k
    ZYDIS_SANITIZE_ENUM(request.branch_type, ZydisBranchType, ZYDIS_BRANCH_TYPE_MAX_VALUE);
194
3.13k
    ZYDIS_SANITIZE_ENUM(request.branch_width, ZydisBranchWidth, ZYDIS_BRANCH_WIDTH_MAX_VALUE);
195
3.13k
    ZYDIS_SANITIZE_ENUM(request.address_size_hint, ZydisAddressSizeHint,
196
3.13k
        ZYDIS_ADDRESS_SIZE_HINT_MAX_VALUE);
197
3.13k
    ZYDIS_SANITIZE_ENUM(request.operand_size_hint, ZydisOperandSizeHint,
198
3.13k
        ZYDIS_OPERAND_SIZE_HINT_MAX_VALUE);
199
3.13k
    ZYDIS_SANITIZE_ENUM(request.evex.broadcast, ZydisBroadcastMode, ZYDIS_BROADCAST_MODE_MAX_VALUE);
200
3.13k
    ZYDIS_SANITIZE_ENUM(request.evex.rounding, ZydisRoundingMode, ZYDIS_ROUNDING_MODE_MAX_VALUE);
201
3.13k
    ZYDIS_SANITIZE_ENUM(request.mvex.broadcast, ZydisBroadcastMode, ZYDIS_BROADCAST_MODE_MAX_VALUE);
202
3.13k
    ZYDIS_SANITIZE_ENUM(request.mvex.conversion, ZydisConversionMode,
203
3.13k
        ZYDIS_CONVERSION_MODE_MAX_VALUE);
204
3.13k
    ZYDIS_SANITIZE_ENUM(request.mvex.rounding, ZydisRoundingMode, ZYDIS_ROUNDING_MODE_MAX_VALUE);
205
3.13k
    ZYDIS_SANITIZE_ENUM(request.mvex.swizzle, ZydisSwizzleMode, ZYDIS_SWIZZLE_MODE_MAX_VALUE);
206
9.79k
    for (ZyanU8 i = 0; i < request.operand_count; ++i)
207
6.65k
    {
208
6.65k
        ZydisEncoderOperand *op = &request.operands[i];
209
6.65k
        op->type = (ZydisOperandType)(ZYDIS_OPERAND_TYPE_REGISTER +
210
6.65k
            ((ZyanUSize)op->type % ZYDIS_OPERAND_TYPE_MAX_VALUE));
211
6.65k
        switch (op->type)
212
6.65k
        {
213
3.65k
        case ZYDIS_OPERAND_TYPE_REGISTER:
214
3.65k
            ZYDIS_SANITIZE_ENUM(op->reg.value, ZydisRegister, ZYDIS_REGISTER_MAX_VALUE);
215
3.65k
            break;
216
1.81k
        case ZYDIS_OPERAND_TYPE_MEMORY:
217
1.81k
            ZYDIS_SANITIZE_ENUM(op->mem.base, ZydisRegister, ZYDIS_REGISTER_MAX_VALUE);
218
1.81k
            ZYDIS_SANITIZE_ENUM(op->mem.index, ZydisRegister, ZYDIS_REGISTER_MAX_VALUE);
219
1.81k
            break;
220
90
        case ZYDIS_OPERAND_TYPE_POINTER:
221
1.19k
        case ZYDIS_OPERAND_TYPE_IMMEDIATE:
222
1.19k
            break;
223
0
        default:
224
0
            ZYAN_UNREACHABLE;
225
6.65k
        }
226
6.65k
    }
227
228
    // APX has conflicts with KNC, so we override `ZYDIS_ENCODABLE_ENCODING_DEFAULT` to disallow
229
    // `MVEX` unless `MVEX`-only features are required.
230
3.13k
    const ZyanBool requested_knc =
231
3.13k
        (request.mvex.broadcast != ZYDIS_BROADCAST_MODE_NONE) ||
232
3.13k
        (request.mvex.conversion != ZYDIS_CONVERSION_MODE_NONE) ||
233
3.13k
        (request.mvex.rounding != ZYDIS_ROUNDING_MODE_NONE) ||
234
3.13k
        (request.mvex.swizzle != ZYDIS_SWIZZLE_MODE_NONE) ||
235
3.13k
        (request.mvex.sae) ||
236
3.13k
        (request.mvex.eviction_hint) ||
237
3.13k
        (request.mnemonic == ZYDIS_MNEMONIC_KMOV) ||
238
3.13k
        (request.mnemonic == ZYDIS_MNEMONIC_KNOT) ||
239
3.13k
        (request.mnemonic == ZYDIS_MNEMONIC_KORTEST);
240
3.13k
    static const ZydisEncodableEncoding conflicting_encodings =
241
3.13k
        ZYDIS_ENCODABLE_ENCODING_EVEX | ZYDIS_ENCODABLE_ENCODING_MVEX;
242
3.13k
    static const ZydisEncodableEncoding no_mvex = (ZydisEncodableEncoding)
243
3.13k
        (ZYDIS_ENCODABLE_ENCODING_MAX_VALUE & (~ZYDIS_ENCODABLE_ENCODING_MVEX));
244
3.13k
    if ((request.allowed_encodings == ZYDIS_ENCODABLE_ENCODING_DEFAULT) && (!requested_knc))
245
1.70k
    {
246
1.70k
        request.allowed_encodings = no_mvex;
247
1.70k
    }
248
1.43k
    else if ((request.allowed_encodings & conflicting_encodings) == conflicting_encodings)
249
730
    {
250
        // In case of potential conflict favor `EVEX` space
251
730
        request.allowed_encodings =
252
730
            (ZydisEncodableEncoding)(request.allowed_encodings & (~ZYDIS_ENCODABLE_ENCODING_MVEX));
253
730
    }
254
3.13k
    if ((request.allowed_encodings & ZYDIS_ENCODABLE_ENCODING_MVEX) != 0)
255
153
    {
256
153
        if ((request.mnemonic == ZYDIS_MNEMONIC_KMOVW) ||
257
153
            (request.mnemonic == ZYDIS_MNEMONIC_KNOTW) ||
258
153
            (request.mnemonic == ZYDIS_MNEMONIC_KORTESTW))
259
3
        {
260
3
            request.allowed_encodings = no_mvex;
261
3
        }
262
153
    }
263
264
3.13k
    ZyanU8 encoded_instruction[ZYDIS_MAX_INSTRUCTION_LENGTH];
265
3.13k
    ZyanUSize encoded_length = sizeof(encoded_instruction);
266
3.13k
    ZyanStatus status = ZydisEncoderEncodeInstruction(&request, encoded_instruction,
267
3.13k
        &encoded_length);
268
3.13k
    if (!ZYAN_SUCCESS(status))
269
1.82k
    {
270
1.82k
#ifdef ZYDIS_LIBFUZZER
271
1.82k
        return EXIT_SUCCESS;
272
#else
273
        return EXIT_ENCODING_FAILURE;
274
#endif // ZYDIS_LIBFUZZER
275
1.82k
    }
276
277
1.31k
    ZydisStackWidth stack_width;
278
1.31k
    switch (request.machine_mode)
279
1.31k
    {
280
1.03k
    case ZYDIS_MACHINE_MODE_LONG_64:
281
1.03k
        stack_width = ZYDIS_STACK_WIDTH_64;
282
1.03k
        break;
283
41
    case ZYDIS_MACHINE_MODE_LONG_COMPAT_32:
284
81
    case ZYDIS_MACHINE_MODE_LEGACY_32:
285
81
        stack_width = ZYDIS_STACK_WIDTH_32;
286
81
        break;
287
98
    case ZYDIS_MACHINE_MODE_LONG_COMPAT_16:
288
166
    case ZYDIS_MACHINE_MODE_LEGACY_16:
289
195
    case ZYDIS_MACHINE_MODE_REAL_16:
290
195
        stack_width = ZYDIS_STACK_WIDTH_16;
291
195
        break;
292
0
    default:
293
0
        ZYAN_UNREACHABLE;
294
1.31k
    }
295
296
1.31k
    ZydisDecoder decoder;
297
1.31k
    if (!ZYAN_SUCCESS(ZydisDecoderInit(&decoder, request.machine_mode, stack_width)))
298
0
    {
299
0
        fputs("Failed to initialize decoder\n", ZYAN_STDERR);
300
0
        abort();
301
0
    }
302
1.31k
    if (requested_knc || ((request.allowed_encodings & ZYDIS_ENCODABLE_ENCODING_MVEX) != 0))
303
158
    {
304
158
        status = ZydisDecoderEnableMode(&decoder, ZYDIS_DECODER_MODE_KNC, ZYAN_TRUE);
305
158
        if (!ZYAN_SUCCESS(status))
306
0
        {
307
0
            fputs("Failed to enable KNC mode\n", ZYAN_STDERR);
308
0
            abort();
309
0
        }
310
158
    }
311
1.31k
    if (request.mnemonic == ZYDIS_MNEMONIC_UD0 && request.operand_count == 0)
312
1
    {
313
1
        status = ZydisDecoderEnableMode(&decoder, ZYDIS_DECODER_MODE_UD0_COMPAT, ZYAN_TRUE);
314
1
        if (!ZYAN_SUCCESS(status))
315
0
        {
316
0
            fputs("Failed to enable UD0_COMPAT mode\n", ZYAN_STDERR);
317
0
            abort();
318
0
        }
319
1
    }
320
321
1.31k
    ZydisDecodedInstruction insn1;
322
1.31k
    ZydisDecodedOperand operands1[ZYDIS_MAX_OPERAND_COUNT];
323
1.31k
    status = ZydisDecoderDecodeFull(&decoder, encoded_instruction, encoded_length, &insn1,
324
1.31k
        operands1);
325
1.31k
    if (!ZYAN_SUCCESS(status))
326
0
    {
327
0
        fputs("Failed to decode instruction\n", ZYAN_STDERR);
328
0
        abort();
329
0
    }
330
331
1.31k
    ZydisCompareRequestToInstruction(&request, &insn1, operands1);
332
1.31k
    ZydisReEncodeInstruction(&decoder, &insn1, operands1, insn1.operand_count, 
333
1.31k
        encoded_instruction);
334
335
1.31k
    return EXIT_SUCCESS;
336
1.31k
}
337
338
/* ============================================================================================== */