aboutsummaryrefslogtreecommitdiffstats
path: root/test/asn1_string_poison_test.c
blob: 76508117c59f89e24472a75ddaad680e027b9c48 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
/*
 * Copyright 2026 The OpenSSL Project Authors. All Rights Reserved.
 *
 * Licensed under the Apache License 2.0 (the "License").  You may not use
 * this file except in compliance with the License.  You can obtain a copy
 * in the file LICENSE in the source distribution or at
 * https://www.openssl.org/source/license.html
 */

/**
 * @file asn1_string_poison_test.c
 * Checks that the NUL terminator libcrypto writes after ASN1_STRING data is
 * inaccessible under AddressSanitizer, MemorySanitizer and Valgrind. Run
 * with no argument the program reads the terminator with strlen() and is
 * expected to be killed by the sanitizer; run with "counted" it reads only
 * the counted bytes and is expected to exit successfully. Without a
 * sanitizer the strlen() run exits with failure itself; with one, surviving
 * the strlen() exits successfully, which the recipe reports as a failure.
 * Under Valgrind the process survives the strlen() and memcheck reports the
 * read. When OSSL_VALGRIND_CT is set the test framework runs memcheck with
 * --error-exitcode=1, and the program exits successfully after the strlen()
 * the same way, leaving the exit status to memcheck; under any other
 * Valgrind wrapper it exits with failure.
 */

#include <stdlib.h>
#include <string.h>
#include <openssl/asn1.h>

#include "testutil.h"

#if defined(__has_feature)
#if __has_feature(address_sanitizer) || __has_feature(memory_sanitizer)
#define HAVE_SANITIZER 1
#endif
#endif /* defined(__has_feature) */
#if defined(__SANITIZE_ADDRESS__) && !defined(HAVE_SANITIZER)
#define HAVE_SANITIZER 1
#endif
#if defined __has_include
/* Any compiler you're going to run valgrind on has this */
#if __has_include(<valgrind/valgrind.h>)
#include <valgrind/valgrind.h>
#endif
#endif /* defined(__has_include) */

/* DER UTF8String "hello" */
static const unsigned char der[] = { 0x0c, 0x05, 'h', 'e', 'l', 'l', 'o' };

/**
 * @brief Report whether a checker decides this run's exit status.
 * That is a sanitizer, or memcheck run by the test framework with
 * --error-exitcode=1 (OSSL_VALGRIND_CT).
 * @returns 1 when the checker sets the exit status, 0 otherwise
 */
static int checker_sets_exit_status(void)
{
#if defined(HAVE_SANITIZER)
    return 1;
#else
#if defined(RUNNING_ON_VALGRIND)
    if (RUNNING_ON_VALGRIND && getenv("OSSL_VALGRIND_CT") != NULL)
        return 1;
#endif
    return 0;
#endif /* defined(HAVE_SANITIZER) */
}

/*
 * A plain main() rather than the test framework's: the failing run is
 * expected to die inside the sanitizer, which the framework would report
 * as a crash.
 */
int main(int argc, char *argv[])
{
    const unsigned char *p = der;
    ASN1_UTF8STRING *str = d2i_ASN1_UTF8STRING(NULL, &p, sizeof(der));
    const unsigned char *data;
    volatile size_t sink;
    int exitcode = EXIT_FAILURE;

    if (!TEST_ptr(str)
        || !TEST_size_t_eq(ASN1_STRING_get_length(str), 5))
        goto end;
    data = ASN1_STRING_get0_data(str);

    if (argc > 1 && strcmp(argv[1], "counted") == 0) {
        /* Counted access never touches the terminator. */
        if (TEST_mem_eq(data, ASN1_STRING_get_length(str), "hello", 5))
            exitcode = EXIT_SUCCESS;
        goto end;
    }

    /*
     * Reads the terminator; a sanitizer kills the process here, memcheck
     * reports the read and lets the process continue.
     */
    sink = strlen((const char *)data);
    (void)sink;
    if (checker_sets_exit_status()) {
        /*
         * Reached under a sanitizer only when it did not report the read.
         * The recipe expects this run to fail: under a sanitizer a
         * successful exit is that failure; under memcheck the exit status
         * is replaced by --error-exitcode when the read was reported.
         */
        TEST_note("strlen() on ASN1_STRING data survived");
        exitcode = EXIT_SUCCESS;
    }

end:
    ASN1_UTF8STRING_free(str);
    return exitcode;
}