scripts/s2n_safety_macros.py (653 lines of code) (raw):
import textwrap
copyright = """
/*
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
*
* Licensed under the Apache License, Version 2.0 (the "License").
* You may not use this file except in compliance with the License.
* A copy of the License is located at
*
* http://aws.amazon.com/apache2.0
*
* or in the "license" file accompanying this file. This file is distributed
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
* express or implied. See the License for the specific language governing
* permissions and limitations under the License.
*/
"""
header = copyright + """
#pragma once
/**
* DO NOT DIRECTLY MODIFY THIS FILE:
*
* The code in this file is generated from scripts/s2n_safety_macros.py and any modifications
* should be in there.
*/
/* clang-format off */
#include "error/s2n_errno.h"
#include "utils/s2n_ensure.h"
#include "utils/s2n_result.h"
/**
* The goal of s2n_safety is to provide helpers to perform common
* checks, which help with code readability.
*/
/* Success signal value for OpenSSL functions */
#define _OSSL_SUCCESS 1
"""
POSIX = dict(
name="POSIX",
is_ok="(result) > S2N_FAILURE",
ok="S2N_SUCCESS",
error="S2N_FAILURE",
ret="int",
ret_doc="`int` (POSIX error signal)",
expect_ok="EXPECT_SUCCESS",
expect_err="EXPECT_FAILURE_WITH_ERRNO",
)
PTR = dict(
name="PTR",
is_ok="(result) != NULL",
ok='"ok"',
error="NULL",
ret="const char*",
ret_doc="a pointer",
expect_ok="EXPECT_NOT_NULL",
expect_err="EXPECT_NULL_WITH_ERRNO",
)
RESULT = dict(
name="RESULT",
is_ok="s2n_result_is_ok(result)",
ok="S2N_RESULT_OK",
error="S2N_RESULT_ERROR",
ret="s2n_result",
ret_doc="`S2N_RESULT`",
expect_ok="EXPECT_OK",
expect_err="EXPECT_ERROR_WITH_ERRNO",
)
DEFAULT = dict(
name="",
is_ok=RESULT['is_ok'],
ok=RESULT['ok'],
error=RESULT['error'],
ret=RESULT['ret'],
expect_ok=RESULT['expect_ok'],
expect_err=RESULT['expect_err'],
)
# TODO add DEFAULT and remove RESULT once all PR branches are up-to-date
CONTEXTS = [RESULT, POSIX, PTR]
max_prefix_len = max(map(lambda c: len(c['name']), CONTEXTS))
def cmp_check(op):
return '__S2N_ENSURE((a) ' + op + ' (b), {bail}(S2N_ERR_SAFETY))'
# TODO ensure type compatibility
# return '''\\
# do {{ \\
# static_assert(__builtin_types_compatible_p(__typeof(a), __typeof(b)), "types do not match"); \\
# __typeof(a) __tmp_a = ( a ); \\
# __typeof(b) __tmp_b = ( b ); \\
# __S2N_ENSURE(__tmp_a ''' + op + ''' __tmp_b, {bail}(S2N_ERR_SAFETY)); \\
# }} while(0)
# '''
MACROS = {
'BAIL(error)': dict(
doc='Sets the global `s2n_errno` to `error` and returns with an `{error}`',
impl='do {{ _S2N_ERROR((error)); __S2N_ENSURE_CHECKED_RETURN({error}); }} while (0)',
harness='''
static {ret} {bail}_harness()
{{
{bail}(S2N_ERR_SAFETY);
return {ok};
}}
''',
tests=[
'{expect_err}({bail}_harness(), S2N_ERR_SAFETY);'
],
),
'ENSURE(condition, error)': dict(
doc='Ensures the `condition` is `true`, otherwise the function will `{bail}` with `error`',
impl='__S2N_ENSURE((condition), {bail}(error))',
harness='''
static {ret} {prefix}ENSURE_harness(bool is_ok)
{{
{prefix}ENSURE(is_ok, S2N_ERR_SAFETY);
return {ok};
}}
''',
tests=[
'{expect_ok}({prefix}ENSURE_harness(true));',
'{expect_err}({prefix}ENSURE_harness(false), S2N_ERR_SAFETY);'
],
),
'DEBUG_ENSURE(condition, error)': dict(
doc='''
Ensures the `condition` is `true`, otherwise the function will `{bail}` with `error`
NOTE: The condition will _only_ be checked when the code is compiled in debug mode.
In release mode, the check is removed.
''',
impl='__S2N_ENSURE_DEBUG((condition), {bail}(error))',
harness='''
static {ret} {prefix}DEBUG_ENSURE_harness(bool is_ok)
{{
{prefix}DEBUG_ENSURE(is_ok, S2N_ERR_SAFETY);
return {ok};
}}
''',
tests=[
'{expect_ok}({prefix}DEBUG_ENSURE_harness(true));',
'#ifdef NDEBUG',
'{expect_ok}({prefix}DEBUG_ENSURE_harness(false));',
'#else',
'{expect_err}({prefix}DEBUG_ENSURE_harness(false), S2N_ERR_SAFETY);',
'#endif',
],
),
'ENSURE_OK(result, error)': dict(
doc='''
Ensures `{is_ok}`, otherwise the function will `{bail}` with `error`
This can be useful for overriding the global `s2n_errno`
''',
impl='__S2N_ENSURE({is_ok}, {bail}(error))',
harness='''
static {ret} {prefix}ENSURE_OK_harness(bool is_ok)
{{
{prefix}ENSURE_OK({prefix}ENSURE_harness(is_ok), S2N_ERR_IO);
return {ok};
}}
''',
tests=[
'{expect_ok}({prefix}ENSURE_OK_harness(true));',
'{expect_err}({prefix}ENSURE_OK_harness(false), S2N_ERR_IO);'
],
),
'ENSURE_GTE(a, b)': dict(
doc='''
Ensures `a` is greater than or equal to `b`, otherwise the function will `{bail}` with a `S2N_ERR_SAFETY` error
''',
impl=cmp_check('>='),
harness='''
static {ret} {prefix}ENSURE_GTE_harness_uint32(uint32_t a, uint32_t b)
{{
{prefix}ENSURE_GTE(a, b);
/* test the inverse */
{prefix}ENSURE_LTE(b, a);
return {ok};
}}
static {ret} {prefix}ENSURE_GTE_harness_int32(int32_t a, int32_t b)
{{
{prefix}ENSURE_GTE(a, b);
/* test the inverse */
{prefix}ENSURE_LTE(b, a);
return {ok};
}}
''',
tests=[
'{expect_ok}({prefix}ENSURE_GTE_harness_uint32(0, 0));',
'{expect_ok}({prefix}ENSURE_GTE_harness_uint32(1, 0));',
'{expect_err}({prefix}ENSURE_GTE_harness_uint32(0, 1), S2N_ERR_SAFETY);',
'{expect_ok}({prefix}ENSURE_GTE_harness_int32(-1, -2));',
'{expect_ok}({prefix}ENSURE_GTE_harness_int32(-1, -1));',
'{expect_err}({prefix}ENSURE_GTE_harness_int32(-2, -1), S2N_ERR_SAFETY);',
],
),
'ENSURE_LTE(a, b)': dict(
doc='''
Ensures `a` is less than or equal to `b`, otherwise the function will `{bail}` with a `S2N_ERR_SAFETY` error
''',
impl=cmp_check('<='),
harness='''
static {ret} {prefix}ENSURE_LTE_harness_uint32(uint32_t a, uint32_t b)
{{
{prefix}ENSURE_LTE(a, b);
/* test the inverse */
{prefix}ENSURE_GTE(b, a);
return {ok};
}}
static {ret} {prefix}ENSURE_LTE_harness_int32(int32_t a, int32_t b)
{{
{prefix}ENSURE_LTE(a, b);
/* test the inverse */
{prefix}ENSURE_GTE(b, a);
return {ok};
}}
''',
tests=[
'{expect_ok}({prefix}ENSURE_LTE_harness_uint32(0, 0));',
'{expect_ok}({prefix}ENSURE_LTE_harness_uint32(0, 1));',
'{expect_err}({prefix}ENSURE_LTE_harness_uint32(1, 0), S2N_ERR_SAFETY);',
'{expect_ok}({prefix}ENSURE_LTE_harness_int32(-2, -1));',
'{expect_ok}({prefix}ENSURE_LTE_harness_int32(-1, -1));',
'{expect_err}({prefix}ENSURE_LTE_harness_int32(-1, -2), S2N_ERR_SAFETY);',
],
),
'ENSURE_GT(a, b)': dict(
doc='''
Ensures `a` is greater than `b`, otherwise the function will `{bail}` with a `S2N_ERR_SAFETY` error
''',
impl=cmp_check('>'),
harness='''
static {ret} {prefix}ENSURE_GT_harness_uint32(uint32_t a, uint32_t b)
{{
{prefix}ENSURE_GT(a, b);
/* test the inverse */
{prefix}ENSURE_LT(b, a);
return {ok};
}}
static {ret} {prefix}ENSURE_GT_harness_int32(int32_t a, int32_t b)
{{
{prefix}ENSURE_GT(a, b);
/* test the inverse */
{prefix}ENSURE_LT(b, a);
return {ok};
}}
''',
tests=[
'{expect_err}({prefix}ENSURE_GT_harness_uint32(0, 0), S2N_ERR_SAFETY);',
'{expect_ok}({prefix}ENSURE_GT_harness_uint32(1, 0));',
'{expect_err}({prefix}ENSURE_GT_harness_uint32(0, 1), S2N_ERR_SAFETY);',
'{expect_ok}({prefix}ENSURE_GT_harness_int32(-1, -2));',
'{expect_err}({prefix}ENSURE_GT_harness_int32(-1, -1), S2N_ERR_SAFETY);',
'{expect_err}({prefix}ENSURE_GT_harness_int32(-2, -1), S2N_ERR_SAFETY);',
],
),
'ENSURE_LT(a, b)': dict(
doc='''
Ensures `a` is less than `b`, otherwise the function will `{bail}` with a `S2N_ERR_SAFETY` error
''',
impl=cmp_check('<'),
harness='''
static {ret} {prefix}ENSURE_LT_harness_uint32(uint32_t a, uint32_t b)
{{
{prefix}ENSURE_LT(a, b);
/* test the inverse */
{prefix}ENSURE_GT(b, a);
return {ok};
}}
static {ret} {prefix}ENSURE_LT_harness_int32(int32_t a, int32_t b)
{{
{prefix}ENSURE_LT(a, b);
/* test the inverse */
{prefix}ENSURE_GT(b, a);
return {ok};
}}
''',
tests=[
'{expect_err}({prefix}ENSURE_LT_harness_uint32(0, 0), S2N_ERR_SAFETY);',
'{expect_ok}({prefix}ENSURE_LT_harness_uint32(0, 1));',
'{expect_err}({prefix}ENSURE_LT_harness_uint32(1, 0), S2N_ERR_SAFETY);',
'{expect_ok}({prefix}ENSURE_LT_harness_int32(-2, -1));',
'{expect_err}({prefix}ENSURE_LT_harness_int32(-1, -1), S2N_ERR_SAFETY);',
'{expect_err}({prefix}ENSURE_LT_harness_int32(-1, -2), S2N_ERR_SAFETY);',
],
),
'ENSURE_EQ(a, b)': dict(
doc='''
Ensures `a` is equal to `b`, otherwise the function will `{bail}` with a `S2N_ERR_SAFETY` error
''',
impl=cmp_check('=='),
harness='''
static {ret} {prefix}ENSURE_EQ_harness_uint32(uint32_t a, uint32_t b)
{{
{prefix}ENSURE_EQ(a, b);
{prefix}ENSURE_EQ(b, a);
return {ok};
}}
static {ret} {prefix}ENSURE_EQ_harness_int32(int32_t a, int32_t b)
{{
{prefix}ENSURE_EQ(a, b);
{prefix}ENSURE_EQ(b, a);
return {ok};
}}
''',
tests=[
'{expect_ok}({prefix}ENSURE_EQ_harness_uint32(0, 0));',
'{expect_err}({prefix}ENSURE_EQ_harness_uint32(1, 0), S2N_ERR_SAFETY);',
'{expect_ok}({prefix}ENSURE_EQ_harness_int32(-1, -1));',
'{expect_err}({prefix}ENSURE_EQ_harness_int32(-2, -1), S2N_ERR_SAFETY);',
],
),
'ENSURE_NE(a, b)': dict(
doc='''
Ensures `a` is not equal to `b`, otherwise the function will `{bail}` with a `S2N_ERR_SAFETY` error
''',
impl=cmp_check('!='),
harness='''
static {ret} {prefix}ENSURE_NE_harness_uint32(uint32_t a, uint32_t b)
{{
{prefix}ENSURE_NE(a, b);
{prefix}ENSURE_NE(b, a);
return {ok};
}}
static {ret} {prefix}ENSURE_NE_harness_int32(int32_t a, int32_t b)
{{
{prefix}ENSURE_NE(a, b);
{prefix}ENSURE_NE(b, a);
return {ok};
}}
''',
tests=[
'{expect_ok}({prefix}ENSURE_NE_harness_uint32(1, 0));',
'{expect_err}({prefix}ENSURE_NE_harness_uint32(0, 0), S2N_ERR_SAFETY);',
'{expect_ok}({prefix}ENSURE_NE_harness_int32(-2, -1));',
'{expect_err}({prefix}ENSURE_NE_harness_int32(-1, -1), S2N_ERR_SAFETY);',
],
),
'ENSURE_INCLUSIVE_RANGE(min, n, max)': dict(
doc='Ensures `min <= n <= max`, otherwise the function will `{bail}` with `S2N_ERR_SAFETY`',
impl=''' \\
do {{ \\
__typeof(n) __tmp_n = ( n ); \\
__typeof(n) __tmp_min = ( min ); \\
__typeof(n) __tmp_max = ( max ); \\
{prefix}ENSURE_GTE(__tmp_n, __tmp_min); \\
{prefix}ENSURE_LTE(__tmp_n, __tmp_max); \\
}} while(0)''',
harness='''
static {ret} {prefix}ENSURE_INCLUSIVE_RANGE_harness_uint32(uint32_t a, uint32_t b, uint32_t c)
{{
{prefix}ENSURE_INCLUSIVE_RANGE(a, b, c);
return {ok};
}}
static {ret} {prefix}ENSURE_INCLUSIVE_RANGE_harness_int32(int32_t a, int32_t b, int32_t c)
{{
{prefix}ENSURE_INCLUSIVE_RANGE(a, b, c);
return {ok};
}}
''',
tests=[
'{expect_err}({prefix}ENSURE_INCLUSIVE_RANGE_harness_uint32(1, 0, 2), S2N_ERR_SAFETY);',
'{expect_ok}({prefix}ENSURE_INCLUSIVE_RANGE_harness_uint32(1, 1, 2));',
'{expect_ok}({prefix}ENSURE_INCLUSIVE_RANGE_harness_uint32(1, 2, 2));',
'{expect_err}({prefix}ENSURE_INCLUSIVE_RANGE_harness_uint32(1, 3, 2), S2N_ERR_SAFETY);',
'{expect_err}({prefix}ENSURE_INCLUSIVE_RANGE_harness_int32(-2, -3, -1), S2N_ERR_SAFETY);',
'{expect_ok}({prefix}ENSURE_INCLUSIVE_RANGE_harness_int32(-2, -2, -1));',
'{expect_ok}({prefix}ENSURE_INCLUSIVE_RANGE_harness_int32(-2, -1, -1));',
'{expect_err}({prefix}ENSURE_INCLUSIVE_RANGE_harness_int32(-2, 0, -1), S2N_ERR_SAFETY);',
],
),
'ENSURE_EXCLUSIVE_RANGE(min, n, max)': dict(
doc='Ensures `min < n < max`, otherwise the function will `{bail}` with `S2N_ERR_SAFETY`',
impl=''' \\
do {{ \\
__typeof(n) __tmp_n = ( n ); \\
__typeof(n) __tmp_min = ( min ); \\
__typeof(n) __tmp_max = ( max ); \\
{prefix}ENSURE_GT(__tmp_n, __tmp_min); \\
{prefix}ENSURE_LT(__tmp_n, __tmp_max); \\
}} while(0)''',
harness='''
static {ret} {prefix}ENSURE_EXCLUSIVE_RANGE_harness_uint32(uint32_t a, uint32_t b, uint32_t c)
{{
{prefix}ENSURE_EXCLUSIVE_RANGE(a, b, c);
return {ok};
}}
static {ret} {prefix}ENSURE_EXCLUSIVE_RANGE_harness_int32(int32_t a, int32_t b, int32_t c)
{{
{prefix}ENSURE_EXCLUSIVE_RANGE(a, b, c);
return {ok};
}}
''',
tests=[
'{expect_err}({prefix}ENSURE_EXCLUSIVE_RANGE_harness_uint32(1, 0, 3), S2N_ERR_SAFETY);',
'{expect_err}({prefix}ENSURE_EXCLUSIVE_RANGE_harness_uint32(1, 1, 3), S2N_ERR_SAFETY);',
'{expect_ok}({prefix}ENSURE_EXCLUSIVE_RANGE_harness_uint32(1, 2, 3));',
'{expect_err}({prefix}ENSURE_EXCLUSIVE_RANGE_harness_uint32(1, 3, 3), S2N_ERR_SAFETY);',
'{expect_err}({prefix}ENSURE_EXCLUSIVE_RANGE_harness_uint32(1, 4, 3), S2N_ERR_SAFETY);',
'{expect_err}({prefix}ENSURE_EXCLUSIVE_RANGE_harness_int32(-3, -4, -1), S2N_ERR_SAFETY);',
'{expect_err}({prefix}ENSURE_EXCLUSIVE_RANGE_harness_int32(-3, -3, -1), S2N_ERR_SAFETY);',
'{expect_ok}({prefix}ENSURE_EXCLUSIVE_RANGE_harness_int32(-3, -2, -1));',
'{expect_err}({prefix}ENSURE_EXCLUSIVE_RANGE_harness_int32(-3, -1, -1), S2N_ERR_SAFETY);',
'{expect_err}({prefix}ENSURE_EXCLUSIVE_RANGE_harness_int32(-3, 0, -1), S2N_ERR_SAFETY);',
],
),
'ENSURE_REF(x)': dict(
doc='Ensures `x` is a readable reference, otherwise the function will `{bail}` with `S2N_ERR_NULL`',
impl='__S2N_ENSURE(S2N_OBJECT_PTR_IS_READABLE(x), {bail}(S2N_ERR_NULL))',
harness='''
static {ret} {prefix}ENSURE_REF_harness(const char* str)
{{
{prefix}ENSURE_REF(str);
return {ok};
}}
''',
tests=[
'{expect_ok}({prefix}ENSURE_REF_harness(""));',
'{expect_ok}({prefix}ENSURE_REF_harness("ok"));',
'{expect_err}({prefix}ENSURE_REF_harness(NULL), S2N_ERR_NULL);',
],
),
'ENSURE_MUT(x)': dict(
doc='Ensures `x` is a mutable reference, otherwise the function will `{bail}` with `S2N_ERR_NULL`',
impl='__S2N_ENSURE(S2N_OBJECT_PTR_IS_WRITABLE(x), {bail}(S2N_ERR_NULL))',
harness='''
static {ret} {prefix}ENSURE_MUT_harness(uint32_t* v)
{{
{prefix}ENSURE_MUT(v);
return {ok};
}}
''',
tests=[
'uint32_t {prefix}ensure_mut_test = 0;',
'{expect_ok}({prefix}ENSURE_MUT_harness(&{prefix}ensure_mut_test));',
'{prefix}ensure_mut_test = 1;',
'{expect_ok}({prefix}ENSURE_MUT_harness(&{prefix}ensure_mut_test));',
'{expect_err}({prefix}ENSURE_MUT_harness(NULL), S2N_ERR_NULL);',
],
),
'PRECONDITION(result)': dict(
doc='''
Ensures the `result` is `S2N_RESULT_OK`, otherwise the function will return an error signal
`{prefix}PRECONDITION` should be used at the beginning of a function to make assertions about
the provided arguments. By default, it is functionally equivalent to `{prefix}GUARD_RESULT(result)`
but can be altered by a testing environment to provide additional guarantees.
''',
impl='{prefix}GUARD_RESULT(__S2N_ENSURE_PRECONDITION((result)))',
harness='''
static S2N_RESULT {prefix}PRECONDITION_harness_check(bool is_ok)
{{
RESULT_ENSURE(is_ok, S2N_ERR_SAFETY);
return S2N_RESULT_OK;
}}
static {ret} {prefix}PRECONDITION_harness(s2n_result result)
{{
{prefix}PRECONDITION(result);
return {ok};
}}
''',
tests=[
'{expect_ok}({prefix}PRECONDITION_harness({prefix}PRECONDITION_harness_check(true)));',
'{expect_err}({prefix}PRECONDITION_harness({prefix}PRECONDITION_harness_check(false)), S2N_ERR_SAFETY);',
],
),
'POSTCONDITION(result)': dict(
doc='''
Ensures the `result` is `S2N_RESULT_OK`, otherwise the function will return an error signal
NOTE: The condition will _only_ be checked when the code is compiled in debug mode.
In release mode, the check is removed.
`{prefix}POSTCONDITION` should be used at the end of a function to make assertions about
the resulting state. In debug mode, it is functionally equivalent to `{prefix}GUARD_RESULT(result)`.
In production builds, it becomes a no-op. This can also be altered by a testing environment
to provide additional guarantees.
''',
impl='{prefix}GUARD_RESULT(__S2N_ENSURE_POSTCONDITION((result)))',
harness='''
static S2N_RESULT {prefix}POSTCONDITION_harness_check(bool is_ok)
{{
RESULT_ENSURE(is_ok, S2N_ERR_SAFETY);
return S2N_RESULT_OK;
}}
static {ret} {prefix}POSTCONDITION_harness(s2n_result result)
{{
{prefix}POSTCONDITION(result);
return {ok};
}}
''',
tests=[
'{expect_ok}({prefix}POSTCONDITION_harness({prefix}POSTCONDITION_harness_check(true)));',
'#ifdef NDEBUG',
'{expect_ok}({prefix}POSTCONDITION_harness({prefix}POSTCONDITION_harness_check(false)));',
'#else',
'{expect_err}({prefix}POSTCONDITION_harness({prefix}POSTCONDITION_harness_check(false)), S2N_ERR_SAFETY);',
'#endif',
],
),
'CHECKED_MEMCPY(destination, source, len)': dict(
doc='''
Performs a safer memcpy.
The following checks are performed:
* `destination` is non-null
* `source` is non-null
Callers will still need to ensure the following:
* The size of the data pointed to by both the `destination` and `source` parameters,
shall be at least `len` bytes.
''',
impl='__S2N_ENSURE_SAFE_MEMMOVE((destination), (source), (len), {prefix}ENSURE_REF)',
harness='''
static {ret} {prefix}CHECKED_MEMCPY_harness(uint32_t* dest, uint32_t* source, size_t len)
{{
{prefix}CHECKED_MEMCPY(dest, source, len);
return {ok};
}}
''',
tests=[
'uint32_t {prefix}_checked_memcpy_dest = 1;',
'uint32_t {prefix}_checked_memcpy_source = 2;',
'{expect_ok}({prefix}CHECKED_MEMCPY_harness(&{prefix}_checked_memcpy_dest, &{prefix}_checked_memcpy_source, 0));',
'EXPECT_EQUAL({prefix}_checked_memcpy_dest, 1);',
'{expect_err}({prefix}CHECKED_MEMCPY_harness(NULL, &{prefix}_checked_memcpy_source, 4), S2N_ERR_NULL);',
'{expect_err}({prefix}CHECKED_MEMCPY_harness(&{prefix}_checked_memcpy_dest, NULL, 4), S2N_ERR_NULL);',
'{expect_ok}({prefix}CHECKED_MEMCPY_harness(&{prefix}_checked_memcpy_dest, &{prefix}_checked_memcpy_source, 4));',
'EXPECT_EQUAL({prefix}_checked_memcpy_dest, {prefix}_checked_memcpy_source);'
],
),
'CHECKED_MEMSET(destination, value, len)': dict(
doc='''
Performs a safer memset
The following checks are performed:
* `destination` is non-null
Callers will still need to ensure the following:
* The size of the data pointed to by the `destination` parameter shall be at least
`len` bytes.
''',
impl='__S2N_ENSURE_SAFE_MEMSET((destination), (value), (len), {prefix}ENSURE_REF)',
harness='''
static {ret} {prefix}CHECKED_MEMSET_harness(uint32_t* dest, uint8_t value, size_t len)
{{
{prefix}CHECKED_MEMSET(dest, value, len);
return {ok};
}}
''',
tests=[
'uint32_t {prefix}_checked_memset_dest = 1;',
'{expect_ok}({prefix}CHECKED_MEMSET_harness(&{prefix}_checked_memset_dest, 0x42, 0));',
'EXPECT_EQUAL({prefix}_checked_memset_dest, 1);',
'{expect_err}({prefix}CHECKED_MEMSET_harness(NULL, 0x42, 1), S2N_ERR_NULL);',
'{expect_ok}({prefix}CHECKED_MEMSET_harness(&{prefix}_checked_memset_dest, 0x42, 4));',
'EXPECT_EQUAL({prefix}_checked_memset_dest, 0x42424242);'
],
),
'GUARD(result)': dict(
doc='Ensures `{is_ok}`, otherwise the function will return `{error}`',
impl='__S2N_ENSURE({is_ok}, __S2N_ENSURE_CHECKED_RETURN({error}))',
harness='''
static {ret} {prefix}GUARD_harness({ret} result)
{{
{prefix}GUARD(result);
return {ok};
}}
''',
tests=[
'{expect_ok}({prefix}GUARD_harness({prefix}ENSURE_harness(true)));',
'{expect_err}({prefix}GUARD_harness({prefix}ENSURE_harness(false)), S2N_ERR_SAFETY);',
],
),
'GUARD_OSSL(result, error)': dict(
doc='Ensures `result == _OSSL_SUCCESS`, otherwise the function will `{bail}` with `error`',
impl='__S2N_ENSURE((result) == _OSSL_SUCCESS, {bail}(error))',
harness='''
static {ret} {prefix}GUARD_OSSL_harness(int result, int error)
{{
{prefix}GUARD_OSSL(result, error);
return {ok};
}}
''',
tests=[
'{expect_ok}({prefix}GUARD_OSSL_harness(1, S2N_ERR_SAFETY));',
'{expect_err}({prefix}GUARD_OSSL_harness(0, S2N_ERR_SAFETY), S2N_ERR_SAFETY);',
],
),
}
max_macro_len = max(map(len, MACROS.keys())) + 8
def push_macro(args):
macro_indent = ' ' * (max_macro_len - len(args['macro']))
h = ""
h += '/**\n'
for line in args['doc'].split('\n'):
h += ' *'
if len(line) > 0:
h += ' ' + line
h += '\n'
h += ' */\n'
h += '#define '
h += args['prefix']
h += args['macro']
h += args['indent']
h += macro_indent
h += args['impl'].format_map(args)
h += '\n\n'
return h
for context in CONTEXTS:
# initialize contexts
if len(context['name']) > 0:
context['prefix'] = context['name'] + '_'
context['suffix'] = '_' + context['name']
else:
context['prefix'] = ''
context['suffix'] = ''
context['indent'] = ' ' * (max_prefix_len - len(context['prefix']))
context['bail'] = '{prefix}BAIL'.format_map(context)
harnesses = ""
docs = """
[//]: # (DO NOT DIRECTLY MODIFY THIS FILE:)
[//]: # (The code in this file is generated from scripts/s2n_safety_macros.py and any modifications)
[//]: # (should be in there.)
# S2N Safety Macros
"""
checks = []
deprecation_message = "DEPRECATED: all methods (except those in s2n.h) should return s2n_result."
def push_doc(args):
args['doc'] = textwrap.dedent(args['doc']).format_map(args).strip()
return textwrap.dedent("""
### {prefix}{macro}
{doc}
""").format_map(args)
for context in CONTEXTS:
docs += textwrap.dedent("""
## Macros for functions that return {ret_doc}
""").format_map(context)
for name, value in MACROS.items():
args = {'macro': name}
args.update(context)
args.update(value)
args['doc'] = textwrap.dedent(args['doc']).strip()
if context['ret'] != DEFAULT['ret']:
args['doc'] = (deprecation_message + "\n\n" + args['doc'])
docs += push_doc(args)
header += push_macro(args)
harness = value.get('harness', None)
if harness != None:
harnesses += textwrap.dedent(harness).format_map(context)
checks.append('/* ' + context['prefix'] + name + ' */')
assert len(value['tests']) > 0, "{} is missing tests".format(name)
for check in value['tests']:
checks.append(check.format_map(context))
checks.append('')
for other in CONTEXTS:
if len(other['suffix']) > 0:
doc = 'Ensures `{is_ok}`, otherwise the function will return `{error}`'
if other == PTR:
doc += '\n\nDoes not set s2n_errno to S2N_ERR_NULL, so is NOT a direct replacement for {prefix}ENSURE_REF.'
if context['ret'] != DEFAULT['ret']:
doc = (deprecation_message + "\n\n" + doc)
if other == context:
continue
impl = '__S2N_ENSURE({is_ok}, __S2N_ENSURE_CHECKED_RETURN({error}))'
args = {
'prefix': context['prefix'],
'suffix': other['suffix'],
'is_ok': other['is_ok'],
'ok': other['ok'],
'error': context['error'],
'indent': context['indent'],
'doc': doc,
'impl': impl,
}
args['macro'] = 'GUARD{suffix}(result)'.format_map(args)
docs += push_doc(args)
header += push_macro(args)
def cleanup(contents):
# Remove any unnecessary generated "X_GUARD_X"s, like "RESULT_GUARD_RESULT"
for context in CONTEXTS:
x_guard = "{name}_GUARD".format_map(context)
x_guard_x = "{name}_GUARD_{name}".format_map(context)
contents = contents.replace(x_guard_x, x_guard)
return contents
def write(f, contents):
contents = cleanup(contents)
with open(f, "w") as header_file:
header_file.write(contents)
write("utils/s2n_safety_macros.h", header)
test = copyright + '''
/* clang-format off */
#include "s2n_test.h"
#include "utils/s2n_safety.h"
/**
* DO NOT DIRECTLY MODIFY THIS FILE:
*
* The code in this file is generated from scripts/s2n_safety_macros.py and any modifications
* should be in there.
*/
/* harnesses */
'''
test += harnesses
test += '''
int main(int argc, char **argv)
{
BEGIN_TEST();
'''
for check in checks:
if len(check) > 0:
test += ' ' + check
test += '\n'
test += '''
END_TEST();
return S2N_SUCCESS;
}
'''
write("tests/unit/s2n_safety_macros_test.c", test)
write("docs/SAFETY-MACROS.md", docs)