13.3. C11 Secure Functions in C Runtime Support Library

The TI Arm Clang Compiler Tools (tiarmclang) provides an implementation of a subset of the “secure” functions that were introduced as optional extensions to the C11 language standard. You can find a full description of the C11 secure functions in Annex K of a recent C Language Standard.

The following C11 secure functions are supported in the tiarmclang compiler tools (annotated with the C11 language standard section number where function is described):

  • abort_handler_s() - K.3.6.1.2

  • gets_s() - K.3.5.4.1

  • ignore_handler_s() - K.3.6.1.3

  • memcpy_s() - K.3.7.1.1

  • memmove_s() - K.3.7.1.2

  • memset_s() - K.3.7.4.1

  • set_constraint_handler_s() - K.3.6.1.1

  • strcat_s() - K.3.7.2.1

  • strcpy_s() - K.3.7.1.3

  • strncat_s() - K.3.7.2.2

  • strncpy_s() - K.3.7.1.4

  • strnlen_s() - K.3.7.4.4

The Annex K C11 secure functions that are not supported in the tiarmclang sre listed in alphabetical order below (annotated with C11 language standard section number where function is described):

  • asctime_h() - K.3.8.2.1

  • bsearch_s() - K.3.6.3.1

  • ctime_s() - K.3.8.2.2

  • fopen_s() - K.3.5.2.1

  • fprintf_s() - K.3.5.3.1

  • freopen_s() - K.3.5.2.2

  • fscanf_s() - K.3.5.3.2

  • fwprintf_s() - K.3.9.1.1

  • fwscanf_s() - K.3.9.1.2

  • getenv_s() - K.3.6.2.1

  • gmtime_s() - K.3.8.2.3

  • localtime_s() - K.3.8.2.4

  • mbrsrtowcs_s() - K.3.9.3.2.1

  • mbstowcs_s() - K.3.6.5.1

  • printf_s() - K.3.5.3.3

  • qsort_s() - K.3.6.3.2

  • scanf_s() - K.3.5.3.4

  • snprintf_s() - K.3.5.3.5

  • snwprintf_s() - K.3.9.1.3

  • sprintf_s() - K.3.5.3.6

  • sscanf_s() - K.3.5.3.7

  • strerror_s() - K.3.7.4.2

  • strerrorlen_s() - K.3.7.4.3

  • strtok_s() - K.3.7.3.1

  • swprintf_s() - K.3.9.1.4

  • swscanf_s() - K.3.9.1.5

  • tmpfile_s() - K.3.5.1.1

  • tmpnam_s() - K.3.5.1.2

  • vfprintf_s() - K.3.5.3.8

  • vfscanf_s() - K.3.5.3.9

  • vfwprintf_s() - K.3.9.1.6

  • vfwscanf_s() - K.3.9.1.7

  • vprintf_s() - K.3.5.3.10

  • vscanf_s() - K.3.5.3.11

  • vsnprintf_s() - K.3.5.3.12

  • vsnwprintf_s() - K.3.9.1.8

  • vsprintf_s() - K.3.5.3.13

  • vsscanf_s() - K.3.5.3.14

  • vswprintf_s() - K.3.9.1.9

  • vswscanf_s() - K.3.9.1.10

  • vwprintf_s() - K.3.9.1.11

  • vwscanf_s() - K.3.9.1.12

  • wcrtomb_s() - K.3.9.3.1.1

  • wcscat_s() - K.3.9.2.2.1

  • wcscpy_s() - K.3.9.2.1.1

  • wcsncat_s() - K.3.9.2.2.2

  • wcsncpy_s() - K.3.9.2.1.2

  • wcsnlen_s() - K.3.9.2.4.1

  • wcsrtombs_s() - K.3.9.3.2.2

  • wcstok_s() - K.3.9.2.3.1

  • wcstombs_s() - K.3.6.5.2

  • wctomb_s() - K.3.6.4.1

  • wmemcpy_s() - K.3.9.2.1.3

  • wmemmove_s() - K.3.9.2.1.4

  • wprintf_s() - K.3.9.1.13

  • wscanf_s() - K.3.9.1.14

13.3.1. C11 Secure Function Constraint Violations

The intent behind the “Bounds-checking interfaces” described in Annex K of the C language standard is to provide a means for a developer to detect, at run-time, unintended behavior in C runtime library functions that write to memory.

For example, consider the memcpy() C runtime library function:

void *memcpy(void *dest, const void *src, size_t count);

A typical C runtime library implementation of the memcpy() function is optimized to execute as efficiently as possible. Most likely, the memcpy() implementation does not check the validity of the dest and src arguments before attempting the copy. There is also no information available to memcpy() about the size of the buffer pointed to by dest, and therefore, there is no way to check whether the copy may write past the end of the dest buffer. Consequently, an issue like writing past the end of the buffer that the dest points to goes undetected and could lead to run-time behavior that is difficult to debug.

The tiarmclang compiler tools provide the C11 secure version of the memcpy() function, memcpy_s(),

errno_t memcpy_s(void *dest, rsize_t destsz, const void *src, rsize_t count);

which is implemented as follows:

/****************************************************************************/
/* memcpy_s.c                                                               */
/*                                                                          */
/* Copyright (c) 2024 Texas Instruments Incorporated                        */
/* https://www.ti.com/                                                       */
/*                                                                          */
/*  Redistribution and  use in source  and binary forms, with  or without   */
/*  modification,  are permitted provided  that the  following conditions   */
/*  are met:                                                                */
/*                                                                          */
/*     Redistributions  of source  code must  retain the  above copyright   */
/*     notice, this list of conditions and the following disclaimer.        */
/*                                                                          */
/*     Redistributions in binary form  must reproduce the above copyright   */
/*     notice, this  list of conditions  and the following  disclaimer in   */
/*     the  documentation  and/or   other  materials  provided  with  the   */
/*     distribution.                                                        */
/*                                                                          */
/*     Neither the  name of Texas Instruments Incorporated  nor the names   */
/*     of its  contributors may  be used to  endorse or  promote products   */
/*     derived  from   this  software  without   specific  prior  written   */
/*     permission.                                                          */
/*                                                                          */
/*  THIS SOFTWARE  IS PROVIDED BY THE COPYRIGHT  HOLDERS AND CONTRIBUTORS   */
/*  "AS IS"  AND ANY  EXPRESS OR IMPLIED  WARRANTIES, INCLUDING,  BUT NOT   */
/*  LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR   */
/*  A PARTICULAR PURPOSE ARE DISCLAIMED.  IN NO EVENT SHALL THE COPYRIGHT   */
/*  OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,   */
/*  SPECIAL,  EXEMPLARY,  OR CONSEQUENTIAL  DAMAGES  (INCLUDING, BUT  NOT   */
/*  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,   */
/*  DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY   */
/*  THEORY OF  LIABILITY, WHETHER IN CONTRACT, STRICT  LIABILITY, OR TORT   */
/*  (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE   */
/*  OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.    */
/*                                                                          */
/****************************************************************************/

#include <stdlib.h>
#include <stdint.h>
#include <string.h>
#include <errno.h>
#include "_c11_secure_private.h"

/****************************************************************************/
/* MEMCPY_S()                                                               */
/*    Read count characters from a source buffer and write them to a        */
/*    destination buffer. The C11 secure version of memcpy will enforce     */
/*    security constraints as prescribed by the C11 standard. Details of    */
/*    the constraints to be enforced are described in the comments below.   */
/****************************************************************************/
#define MAX_EMSG_SZ	100
errno_t
memcpy_s(void * __restrict dest, rsize_t destsz, 
         const void * __restrict src, rsize_t count)
{
  char emsg[MAX_EMSG_SZ] = "memcpy_s : ";

  /*------------------------------------------------------------------------*/
  /* Destination buffer pointer cannot be NULL.                             */
  /*------------------------------------------------------------------------*/
  if (dest == NULL)
    strcat(emsg, "dest is NULL");

  /*------------------------------------------------------------------------*/
  /* Source buffer pointer cannot be NULL.                                  */
  /*------------------------------------------------------------------------*/
  else if (src == NULL)
    strcat(emsg, "src is NULL");

  /*------------------------------------------------------------------------*/
  /* Indicated destination buffer size cannot be > RSIZE_MAX.               */
  /*------------------------------------------------------------------------*/
  else if (destsz > RSIZE_MAX)
    strcat(emsg, "destsz > RSIZE_MAX");

  /*------------------------------------------------------------------------*/
  /* Indicated count cannot be > RSIZE_MAX.                                 */
  /*------------------------------------------------------------------------*/
  else if (count > RSIZE_MAX)
    strcat(emsg, "count > RSIZE_MAX");

  /*------------------------------------------------------------------------*/
  /* Indicated count cannot be > indicated destination buffer size.         */
  /*------------------------------------------------------------------------*/
  else if (count > destsz)
    strcat(emsg, "count > destsz");

  /*------------------------------------------------------------------------*/
  /* Source and destination buffers may not overlap.                        */
  /*------------------------------------------------------------------------*/
  else if ((((unsigned char *)src >= (unsigned char *)dest) &&
            ((unsigned char *)src < ((unsigned char *)dest + destsz))) ||
           (((unsigned char *)src <= (unsigned char *)dest) &&
            ((unsigned char *)dest < ((unsigned char *)src + count))))
    strcat(emsg, "src and dest overlap");

  /*------------------------------------------------------------------------*/
  /* All constraint violation checks have been cleared. Do the copy.        */
  /*------------------------------------------------------------------------*/
  else {
    memcpy(dest, src, count);
    return (0);
  }

  /*------------------------------------------------------------------------*/
  /* If any constraint violations are detected, the C11 standard prescribes */
  /* that zeroes are written to dest[0] through dest[destsz-1].             */
  /*------------------------------------------------------------------------*/
  if (dest && (destsz <= RSIZE_MAX))
    memset(dest, 0, destsz);

  __throw_constraint_handler_s(emsg, EINVAL);
  return (EINVAL);
}

In contrast to a typical memcpy() implementation, the memcpy_s() implementation performs several run-time checks on the validity of the arguments, including:

  • Neither the dest, nor the src pointers may be NULL

  • Both the destsz and the count arguments must be less then RSIZE_MAX

  • The count argument must be less than or equal to the destsz argument

  • The src buffer must not overlap with the dest buffer

A failure of any of these run-time checks constitutes a constraint violation, in which case, the memcpy_s() implementation does not perform the copy operation. Instead, the first destsz bytes of the dest buffer is filled with zero, and the type of the constraint violation is communicated to a constraint handler function. Also, in the event of a constraint violation, the memcpy_s() function returns a non-zero error type value to indicate to the calling function that a constraint violation has been detected.

13.3.2. Setting Up a Constraint Violation Handler Function

Included with the C11 secure functions themselves, the C runtime support library API provides a function that allows a developer to designate their own function as the one to be called when a constraint violation is detected by one of the implemented secure functions.

set_constraint_handler_s <stdlib.h>
constraint_handler_t set_constraint_handler_s(constraint_handler_t handler);

where the constraint_handler_t type is defined in stdlib.h as follows:

typedef typedef void (*constraint_handler_t)(const char *, void *, errno_t);

and the errno_t type is also defined in stdlib.h as follows:

typedef int errno_t;

The constraint handler registration helper function registers a custom constraint handler function to be called when a constraint violation is detected, set_constraint_handler_s() must be called with a pointer to the constraint handler function that is to be called when a violation is detected.

A custom implementation of a constraint handler function must match the signature as specified in the definition of the constraint_handler_t type. That is,

void <name of constraint handler function>(const char *, void *, errno_t);

Two example constraint handler function implementations are provided in the C Runtime Support Library:

  • abort_handler_s() - a constraint violation causes the application to abort

  • ignore_handler_s() - a constraint violation goes unreported

If set_constraint_handler_s() is not called before the first constraint violation is detected, then ignore_handler_s() is assumed to be the default constraint handler.

13.3.3. Enabling Use of Secure Functions via __STDC_WANT_LIB_EXT1__ Definition

In the tiarmclang C runtime library header files, all C11 secure function prototypes are guarded by a pre-processor directive. In order for a given C11 secure function prototype to be made known to the compiler before encountering a call to the secure function in the C source file, the following conditions must be true:

  • The compiler must be invoked assuming the C11 (or later) language standard. The compiler assumes a C language standard of C17 with GNU extensions (-std=gnu17) by default.

  • The C source file must define __STDC_WANT_LIB_EXT1__ to “1” prior to including the header file that contains the prototype of the secure function to be called.

  • The C source file must include the C runtime support library header file that contains the prototype of the secure function to be called.

Please see the example below for further details.

Note

__STDC_WANT_LIB_EXT1__ must be defined to “1” when using C11 secure functions

An attempt to call a C11 secure function without defining the __STDC_WANT_LIB_EXT1__ compile-time symbol to 1 is likely to cause unpredictable behavior if the linked application is loaded and run. If no appropriate prototype of the C11 secure function is provided prior to a call to that function, the compiler emits a warning diagnostic like this:

ex_n.c:17:3: warning: call to undeclared function 'strcat_s'; ISO C99 and later do not support implicit function declarations [-Wimplicit-function-declaration]
   17 |   strcat_s(dest_string, 10, source_string);
      |
1 warning generated.

Consider using the -Werror=implicit-function-declaration option to instruct the tiarmclang compiler to interpret a call to an undeclared function as an error instead of a warning.

13.3.4. Example

To summarize the different aspects of using a C11 secure function properly, consider a function that declares a char buffer of fixed length and then attempts to concatenate the string from an incoming buffer to the string that currently exists in the fixed length local buffer. To guard against several unintended effects, the strcat_s() function could be used as follows:

/*****************************************************************************/
/* safe_strcat_ex.c                                                          */
/*                                                                           */
/* Example of proper setup and use of strcat_s() C11 secure function.        */
/*                                                                           */
/*****************************************************************************/
#define __STDC_WANT_LIB_EXT1__	1

#include <stdlib.h>
#include <string.h>
#include <stdio.h>
#include <errno.h>

/*****************************************************************************/
/* dump_error_msg() - a custom constraint handler function.                  */
/*****************************************************************************/
void dump_error_msg(const char * __restrict emsg,
                    void * __restrict ptr,
                    errno_t eval)
{
  printf("Constraint violation detected:\n");
  printf("\t%s\n", emsg);
}

/*****************************************************************************/
/* safe_strcat()                                                             */
/*****************************************************************************/
errnot_t safe_strcat(const char *source_string) {
  errno_t ret_val = 0;
  char dest_string[10] = "aaa";

  /*-------------------------------------------------------------------------*/
  /* Register custom constraint handler function.                            */
  /*-------------------------------------------------------------------------*/
  set_constraint_handler_s((constraint_handler_t)&dump_error_msg);

  /*-------------------------------------------------------------------------*/
  /* Concatenate source_string buffer contents to end of dest_string buffer  */
  /* contents with safety checks.                                            */
  /*-------------------------------------------------------------------------*/
  return ret_val = strcat_s(dest_string, 10, source_string);
}

Observations of note include:

  • The __STDC_WANT_LIB_EXT1__ compile-time symbol is defined to “1” prior to inclusion of any of the C runtime support header files, making C11 secure function prototypes that are declared in any of the following include files visible to the compiler.

  • The C source file includes an implementation of a constraint hander function, dump_error_msg(), whose signature matches the constraint_handler_t type that is defined in stdlib.h.

  • The set_constraint_handler_s() function is called to register dump_error_msg() as the constraint handler function to be called when a constraint violation is detected.

  • The call to the strcat_s() C11 secure function returns a non-zero errno_t type value to the caller to indicate whether a constraint violation is detected.

  • In the definition of strcat_s(), there are a few constraint violations that may occur depending on the value of the source_string argument that is passed into the safe_strcat() function, including:

    • if the value of source_string is NULL,

    • if the buffer pointed to by source_string contains a string > 7 bytes long, which would cause the concatenation operation to write past the end of the dest_string buffer, or

    • if the value of source_string happened to be an address that falls within the bounds of the dest_string buffer.

  • If a constraint violation is detected in the execution of strcat_s(), then the dump_error_msg() constraint handler function would be called to print the type of the violation out to stdout.

13.3.5. The Secure Functions

Included below is a summary description of each of the C11 secure functions that are implemented in the tiarmclang C runtime library. In the majority of cases, the C11 secure functions takes an extra destsz argument – or _size in the case of gets_s() – with which the caller indicates the total size of the buffer that data is being written into.

gets_s <stdio.h>
char *gets_s(char *_ptr, rsize_t _size);

Writes input from stdin to _ptr buffer. The following constraint violations are detected and reported:

  • storage buffer pointer _ptr is NULL

  • specified _size is 0

  • specified _size is > RSIZE_MAX

  • input from stdin is truncated

memcpy_s <string,h>
errno_t memcpy_s(void *dest, rsize_t destsz, const void *src, rsize_t count);

Copy count bytes from src buffer to dest buffer. The following constraint violations are detected and reported:

  • src or dest pointer is NULL

  • specified destsz > RSIZE_MAX

  • specified count > RSIZE_MAX

  • specified count > specified destsz

  • src and dest buffers overlap

memmove_s <string.h>
errno_t memmove_s(void *dest, rsize_t destsz, const void *src, rsize_t count);

Copy count bytes from src to dest. The following constraint violations are detected and reported:

  • src or dest pointer is NULL

  • specified destsz > RSIZE_MAX

  • specified count > RSIZE_MAX

  • specified count > specified destsz

memset_s <string.h>
errno_t memset_s(void *dest, rsize_t destsz, int ch, rsize_t count);

Write count instances of specified character ch to dest. The following constraint violations are detected and reported:

  • dest pointer is NULL

  • specified destsz > RSIZE_MAX

  • specified count > RSIZE_MAX

  • specified count > specified destsz

strcat_s <string.h>
errno_t strcat_s(char *dest, rsize_t destsz, const char *src);

Concatenate contents of src to the end of the dest buffer. The following constraint violations are detected and reported:

  • src or dest pointer is NULL

  • specified destsz is 0 or > RSIZE_MAX

  • no null terminator character is present in the first destsz bytes of the dest buffer

  • the contents from src are truncated

  • src and dest buffers overlap

strcpy_s <string.h>
errno_t strcpy_s(char *dest, rsize_t destsz, const char *src);

Copy contents of src buffer into dest buffer. The following constraint violations are detected and reported:

  • src or dest pointer is NULL

  • specified destsz is 0 or > RSIZE_MAX

  • the contents from src are truncated

  • src and dest buffers overlap

strncat_s <string.h>
errno_t strncat_s(char *dest, rsize_t destsz, const char *src, rsize_t count);

Concatenate a maximum of count characters from the src buffer to the end of the content in the dest buffer. The following constraint violations are detected and reported:

  • src or dest pointer is NULL

  • specified destsz is 0 or > RSIZE_MAX

  • specified count > RSIZE_MAX

  • no null terminator character is present in the first destsz bytes of the dest buffer

  • the contents from src are truncated

  • src and dest buffers overlap

strncpy_s <string.h>
errno_t strncpy_s(char *dest, rsize_t destsz, const char *src, rsize_t count);

Copy a maximum of count characters from the src buffer into the dest buffer. The following constraint violations are detected and reported:

  • src or dest pointer is NULL

  • specified destsz is 0 or > RSIZE_MAX

  • specified count > RSIZE_MAX

  • the contents from src are truncated

  • src and dest buffers overlap

In addition, the following C11 security-related helper function is included in the C runtime support library (libc.a):

strnlen_s <string.h>
size_t strnlen_s(const char *string, size_t maxLen);

A variation on the strlen() function. In this case, if the length of string is longer than the specified maxLen, then strnlen_s() returns maxLen instead of the actual length of string.