Here’s a review of libotr I performed on a couple of long plane journeys recently.
The source
The reviewed source is this tree. I passed all the source through astylefirst. This is intended to reveal any bugs hidden by formatting, and
fortunately also removes libotr’s unconventional (but consistent) indentation1.
Line numbers given below are given for the processed source.
The overview
libotr is not a travesty of confusion and neglect like openssl. In fact, it
shows encouraging signs of being competently written. But all software has bugs, and
everything can be improved.
In summary:
The ‘secure memory’ allocation arena seems to have the classic CWE-14 problem.
There are a large number of unchecked mallocs/strdups. This is at most a reliablity/DOS problem
in normal environments, but if libotr is ever used in an embedded environment this can become
arbitrary code execution2. If you really squint.
There is quite a lot of manual string manipulation. I didn’t find any overt errors here, but it’s
a worry for the future.
There are some integer overflows, but none which seem to be dangerous because they have
immensely impractical constraints. However, I’d argue this is only fortuitous.
Recommendations
Allocate-or-die
There are a lot of calls to libgcrypt functions which have malloc-or-die semantics
(via gcry_xmalloc etc.). This means libotr as it stands might just exit the process
under memory pressure. This isn’t a wonderful behaviour for a library, but is an improvement
over faulting or if application code can’t be taught to handle errors.
I would suggest adopting xmalloc semantics for all existing malloc/strdup calls.
String manipulation
Replace manual string manipulation with a higher-level interface. For most uses in the library,
using a xasprintf-alike would be a vast improvement.
Original code:
vs.
This is shorter, simpler, and has none of the problems of the original.
I’m surprised that anything is happy with a free(3) replacement which will segfault
on free(NULL).
General CWE-14 worries about compilers removing memset, based on lack of reads and
subsequent free meaning there cannot be any active aliases in a conforming program.
Wut. Doing it four times is just bananas.
Note that libgcrypt says of the repetition: “This does not make much sense: probably this memory
is held in the cache. We do it anyway.” Also, it doesn’t use memset.
I suggest introducing a void otrl_mem_clear(volatile void *ptr, size_t len).
This calculation is opaque, and appears elsewhere (proto.c:1018).
Three unchecked strdups. Notably, this function’s caller returns
GPG_ERR_ENOMEM in the same case. I guess this will cause messages
to go missing under memory pressure.
Error swept under rug if otrl_proto_default_query_msg fails (which it does if
malloc fails).
Error swept under rug if gcry_malloc_secure fails. But fortunately not
otrl_proto_default_query_msg this time!
Unchecked malloc.
Impractical integer underflow followed by heap overflow.
smpmsglen is the wrong type for a length, so will be < 0 if very large.
That will make the allocation too small for the subsequent strcpy/memmove.
otrl_userstate_free is allergic to NULL userstate via otrl_context_forget_all,
which is a shame because that’s otrl_userstate_create’s error handling mechanism.
This is made worse by hiding the fact that this is a pointer type behind a typedef,
and not documentating that it can be NULL, and not doing any checks in the
tests/example code (which invariably get copied into application code).
It’s unlikely to be a problem here because otrl_userstate_create tends to get called
once, early on, and isn’t generally going to encounter an allocation failure.
This is the first time I’ve seen indentation at 4 columns with tab stops at 8 columns. ↩
Not to mention, embedded devices are usually much easier to push into memory pressure. ↩