Re: Usage of __assert_func in standard library

Jeff Johnston <[email protected]>
Newsgroups gmane.comp.lib.newlib
Message-ID <CAOox84tUdJsidr0Z=aknQTAorN4JtzG+3tp7-LQxA15ifpEy+Q@mail.gmail.com>
On Tue, Nov 21, 2023 at 5:05 AM Alex Tarasov <[email protected]>
wrote:

> On Tue, Nov 21, 2023 at 0:56 AM Jeff Johnston <[email protected]> wrote:
>
>>
>> On Mon, Nov 20, 2023 at 5:34 AM Corinna Vinschen <[email protected]>
>> wrote:
>>
>>> On Nov 18 19:13, Alex Tarasov wrote:
>>> > Dear developers of  Newlib,
>>> >
>>> > I think we have a bit of an issue concerning usage of *__assert_func
>>> *function
>>> > in the current version of the standard library.
>>> > [...]
>>> > ld.exe:
>>> PathToCompiler/../lib/gcc/arm-none-eabi/13.2.1/thumb/v7e-m+fp/hard\libg.a(libc_a-closer.o):
>>> > in function `_close_r':
>>> > closer.c:(.text._close_r+0xc): undefined reference to `_close'
>>> > [...]
>>> > The analysis of cross reference table in the .map file showed me the
>>> source
>>> > of these errors. Somehow, in the newer toolchain functions from the
>>> > standard library (*strtod* in my case) call *"__assert_func"* which in
>>> turn
>>> > lead to various system calls. I downloaded the latest Newlib version
>>> on the
>>> > "main" branch and saw this code in the *newlib/libc/stdlib/mprec.h*
>>> file:
>>> >
>>> > #define eBalloc(__reent_ptr, __len) ({ \
>>> >    void *__ptr = Balloc(__reent_ptr, __len); \
>>> >    if (__ptr == NULL) \
>>> >      __assert_func(__FILE__, __LINE__, (char *)0, "Balloc succeeded");
>>> \
>>> >    __ptr; \
>>> >    })
>>> >
>>> > According to *git log* this *eBalloc* macro was introduced in commit
>>> > with f88aece242178ff0c187d56e34a79645fbc44a23
>>> > hash on October 4th 2019. This leads to several functions in the
>>> standard
>>> > library calling *__assert_func* inside them. Standard definition of
>>> this
>>> > function in *newlib/libc/stdlib/assert.c* calls *abort* and *fiprintf*
>>> > functions
>>> > which in turn drag a lot of system functions (see error log that I
>>> > mentioned above).
>>> >
>>> > This leads to several issues:
>>> >
>>> >    - If I want to use some functions from the standard library (like
>>> >    *strtod*) but I don't want any system calls, I need to redefine
>>> >    *__assert_func* in my project. I have to do it even if I don't use
>>> >    assert's anywhere in my own code.
>>> >    - There is a project where I use <assert.h> and my own
>>> > implementation of *__assert_func.
>>> >    *I expect that when I build my project with NDEBUG macro defined
>>> >    (release configuration), I would not see any calls to that
>>> function. That
>>> >    is not the case if any function from Newlib's standard library that
>>> uses
>>> >    *eBalloc* macro gets linked.
>>> >
>>> > I think this behaviour is too implicit and needs to be fixed. I suggest
>>> > removing *__assert_func* from *eBalloc* macro or making some other
>>> changes
>>> > to Newlib that will get rid of the mentioned issues.
>>>
>>> Yeah, that looks a tad too aggressive.
>>>
>>> I see only ~20 calls to eBalloc in newlib.  Maybe somebody can take
>>> a stab at it and convert the calls to Balloc plus error checking?
>>>
>>> However, the parent functions of the functions calling eBalloc
>>> potentially don't check error conditions either, so this might be indeed
>>> a bit more work than a quick count of the eBalloc calls indicates...
>>>
>>> Jeff? Any idea how to go forward?
>>>
>>>
>>> The eBalloc macro calling __assert_func is a fix for a CVE whereby a
>> write to 0 could
>> occur silently within newlib.  It is intentional that it aborts and
>> doesn't disable with NDEBUG.
>>
>> As mentioned by Corinna, the call chain of eBalloc isn't checking results
>> and it was why we
>> chose to use the assert to handle the CVE.
>>
>> That said, one issue for you seems to be the fiprintf() call.  A possible
>> solution is to have
>> a macro which disables the fiprintf() or you can choose to override the
>> fiprintf() method
>> yourself.
>>
>> -- Jeff J.
>>
>>
>> Corinna
>>>
>>>
> Thank you both for your response!
>
> > issue for you seems to be the fiprintf() call
> No, the issue for me is the whole __assert_func() with both fiprintf() and
> abort(). The latter one needs _kill() and _get_pid(), while fiprintf()
> references _write(), _close(), _lseek(), etc.
>
> Try to see it from the perspective of programmers who write new bare-metal
> projects. Let's assume that they need to parse string-messages from some
> device. They decide to use the strtod() function from the standard library
> to get some values from these messages. Suddenly, they get errors from the
> linker about missing definitions of functions _kill(), _write(), _close()
> etc. To resolve this issue they need to check the .map file and try to
> untangle the references from the Cross Reference Table. Which is not an
> easy task since you can only see what object file contains a call to your
> function. Only then can a programmer see that the reason for their linkage
> error are calls to __assert_func() which in turn leads to other system
> calls. Then they have to consider what to do with this. The best approach
> would be (as far as I know) to download Newlib, look at where this
> __assert_func() is called, see that it is only needed to check Balloc's
> result, and then implement your own definition of __assert_func() that
> doesn't lead to any system calls (that's what I've done).
>
> In my opinion this is far too much for simply trying to use a strtod()
> function from the standard library. Using -specs=nosys.specs here can also
> do the trick, but I don't consider it a good solution since it leaves a lot
> of unnecessary stub functions. It also makes it less clear what is being
> linked in your program. So, it's either spending a lot of time debugging
> the issue or using less effective approaches like implementing different
> linkage flags or avoiding standard library at all.
>
> I know that in a new project when using the standard library you also have
> to deal with dynamic memory allocation. You have to provide a
> definition for sbrk() or redefine malloc_r(), free_r(), etc yourself. But
> this seems to be common knowledge, while the case with __assert_func() is
> not.
>
> As for the reasons for this __assert_func(), wasn't it just a quick fix?
> Isn't it a better solution to check for NULL everywhere Balloc get's called
> (as Corrina already suggested)? I know that it can be a lot of work since
> we need to untangle a lot of calls but it can also save a lot of time for
> the users of Newlib.
>
> -- Alexander T.
>

It is not just a quick and dirty fix.  The application has run out of
memory deep in the newlib internals.  There are methods that don't have
failure return codes and/or are not documented to set an errno for out of
memory.   It is not reasonable that the user should be required to add code
to try to recognize such a situation.  That said, I think that newlib
aborting without the fiprintf is quite reasonable.  Your complaint that
_kill() and _get_pid() being required is not a real issue considering these
are part of the base syscall set for newlib and there is a libnosys()
library to provide stubbed calls if they aren't implemented for the
particular BSP or the user doesn't want a full-fledged implementation.

-- Jeff J.
lmpx.com only provides a reader for public news (NNTP) servers. It is not affiliated with the servers or forums shown here and is not responsible for the content of articles, which is written by their respective authors.