Right, it needs to be allocated elsewhere.
To do this in OOP+GC, you simply return the object and forget about it, no problem. C is too simplistic for that method however.
The hidden steps of that operation, are: allocating the object on the heap (C's malloc), returning the pointer to the object, and later disposing of the object (free) when it falls out of scope.
If you ever forget to free the object, it gets stuck on the heap. This is called a memory leak. If you ever free an object then reuse its pointer, you'll be reading garbage, and randomly get anything from protection faults to possible code execution vulnerability (use-after-free).
So while you could malloc inside the function, return a pointer, and free once it's used, it's probably not a good idea. Better not to have functions with side effects (namely, modifying the heap).
So the better way is to pass in a pointer to available memory. In other languages, this is a pass-by-reference. You'll need,
void read_data_8(uint16_t idx, uint16_t len, uint8_t * buf) { ... }
...
read_data_8(idx, len, &buf);
You could also break it into multiple accesses, which is ugly since those would be multiple different function calls and you might as well inline it or something. But sometimes that's an okay way to go.
Tim