malloc failure causes exception in sprintf

Asked by Neil

When a floating-point format specifier is used with sprintf, one of sprintf's subroutines calls malloc. If malloc returns NULL, the subroutine attempts to write to address 0x00000004 at _dtoa_r+0x1C (looking through the source, it looks like multiple malloc calls are affected; the issue was discovered with newlib-nano but may also affect newlib). On the target processor, this memory is read-only, so the CPU jumps to the exception vector. Dereferencing invalid pointers can lead to undefined (or at least unexpected) behavior. If malloc fails, sprintf should return a value indicating that an error occurred.

Example program (requires a vector table to actually run it on the target):
/* Toolchain version: gcc-arm-none-eabi-4_8-2014q2-20140609-win32 */
/* Compile options: arm-none-eabi-gcc -mcpu=cortex-m0 -mthumb -g -specs=nano.specs -u _printf_float sprintf_issue.c -o sprintf_issue.axf */
#include <stdio.h>
#include <errno.h>

void * _sbrk(ptrdiff_t nbytes)
{
    /* Simulate out of memory condition (actual application had a real sbrk but ran out of heap space) */
    errno = ENOMEM;
    return (void *)-1;
}

void _exit(int status)
{
    while(1) { }
}

int main()
{
    char buf[32];
    int result = sprintf(buf, "%f", 17.0 / 23.0);
    return 0;
}

Question information

Language:
English Edit question
Status:
Answered
For:
GNU Arm Embedded Toolchain Edit question
Assignee:
No assignee Edit question
Last query:
Last reply:
Revision history for this message
Launchpad Janitor (janitor) said :
#1

This question was expired because it remained in the 'Open' state without activity for the last 15 days.

Revision history for this message
Thomas Preud'homme (thomas-preudhomme) said :
#2

Hi Neil,

My analysis so far is that the problematic malloc is in _REENT_CHECK which is called through _RENT_CHECK_MP at the start of _dtoa_r.

The definition for this macro can be found in newlib/libc/include/sys/reent.h (see at [1] for instance):

#define _REENT_CHECK(var, what, type, size, init) do { \
  struct _reent *_r = (var); \
  if (_r->what == NULL) { \
    _r->what = (type)malloc(size); \
    __reent_assert(_r->what); \
    init; \
  } \
} while (0)

[1] https://sourceware.org/viewvc/src/newlib/libc/include/sys/reent.h?revision=1.57&view=markup#l470

I cannot look deeper into this for now but clearly an assert is not sufficient here. Feel free to report it to newlib community otherwise I will do it myself when I have a bit more time.

Best regards.

Revision history for this message
Neil (neilt) said :
#3

There are also some unchecked calls to Balloc (calls calloc) in _dtoa_r and the mprec.c subroutines it uses.

Revision history for this message
Thomas Preud'homme (thomas-preudhomme) said :
#4

Indeed, it's all over the place. Fixing all of this would thus be a significant effort as the code would need to be globally reviewed. And if this were fixed, the function would still not work in this case and return an error or only print part of the string which makes it less worthwhile to fix. You are still welcome to open a bug report, either here or report the issue upstream but I wouldn't hold my breath. As a workaround you can check that the available memory is above a given threshold (which you determine with experimentation) before doing such a printf.

Best regards.

Can you help with this problem?

Provide an answer of your own, or ask Neil for more information if necessary.

To post a message you must log in.