If you dig into the newlib source, even if confusing and easy to get lost trying to decipher it, it appears that the use of sprintf/snprintf sets the __SWR and __SSTR FILE flags (FILE struct on stack in sniprintf function), and with those flags set you do not reach any call to malloc. No malloc call, no heap used and no need for _sbrk to do anything useful.
simply providing the _sbrk function that always returns a failure-
int _sbrk(int){ return -1; }
will allow the use of sprintf/snprintf, which WILL also bring in _malloc_r whether it actually gets used or not (and _sbrk_r which calls _sbrk). Since we know the heap is not used for sprintf/snprintf, but to satisfy the potential call from _malloc_r to _sbrk_r and ultimately _sbrk (user provided in the case of the current newlib nano library) that _sbrk function must exist.
This simple single line of code for the _sbrk function will satisfy the requirements to get snprintf working without the need to get into details of using the heap. If the heap is later wanted, or it starts being used without your knowledge (with the resulting malloc failures), then the _sbrk function will need to change to allow it.