Repository navigation
Conversation
|
Thank you very much, that was quite a lot of work done here! At first: it is C, users shall be allowed to shot themselves in the foot. But serious, not to check for NULL except where necessary and/or useful was an intentional decision. #include <stdlib.h>
#include <tommath.h>
#define DEBUG_PRINT(ERROR_NUMBER, ERROR_GOTO)\
do{\
fprintf(stderr, "%s %d in %s: %s\n",\
__FILE__, __LINE__, __FUNCTION__,\
mp_error_to_string((ERROR_NUMBER)));\
goto ERROR_GOTO;\
}while(0)
static void mp_print(const char *s, const mp_int *a, int radix, FILE *stream)
{
mp_err err;
fputs(s, stream);
err = mp_fwrite(a, radix, stream);
if (err != MP_OKAY) {
fprintf(stderr,"mp_fwrite in mp_print failed. error = %s\n", mp_error_to_string(err));
/* An error from mp_fwrite is almost always fatal, no use to try saving it */
exit(EXIT_FAILURE);
}
fputc('\n',stream);
}
int main(void){
mp_err err = MP_OKAY;
mp_int a, b, c, d;
#ifdef TRY_IT_WITH_A_SLEDGEHAMMER
mp_int *k = NULL;
#endif
if( (err = mp_init_multi(&a, &b, &c, NULL) ) != MP_OKAY) DEBUG_PRINT(err, LTM_ERR);
if( (err = mp_init(&d) ) != MP_OKAY) DEBUG_PRINT(err, LTM_ERR);
mp_clear(&d);
/* Will do what it is supposed to do, allocate memory for "d" because "d" exists */
if( (err = mp_init(&d) ) != MP_OKAY) DEBUG_PRINT(err, LTM_ERR);
mp_set(&a, 123);
mp_set(&b, 3210);
/* Unused */
mp_set(&c, 876);
/* mp_clear sets b.dp to NULL, but the variable exists: no warning from the compiler */
mp_clear(&b);
if( (err = mp_div(&b, &a, NULL, &c) ) != MP_OKAY) DEBUG_PRINT(err, LTM_ERR);
/*
Will print 0 (zero) instead of 12 (twelve).
A wrong result without an error?
Not exactly wrong. mp_clear free()'s the memory and sets the rest as if "d = 0"
so mp_div computes 0/a.
*/
mp_print("3210 % 123 = ", &c, 10, stdout);
/* Double free() is allowed */
mp_clear_multi(&a, &b, &c, &d, NULL);
mp_clear_multi(&a, &b, &c, &d, NULL);
#ifdef TRY_IT_WITH_A_SLEDGEHAMMER
if( (err = mp_init(k) ) != MP_OKAY)
#endif
exit(EXIT_SUCCESS);
LTM_ERR:
fprintf(stderr, "An error occured %s \n",mp_error_to_string(err));
mp_clear_multi(&a, &b, &c, &d, NULL);
exit(EXIT_FAILURE);
}And if we dust our off our sledgehammer? The user needs to make two (honest) mistakes:
User doesn't leave it uninitialized and sets it to So user does as told (because the compiler is always right, isn't it?) and
Gets a segfault as a result. Program received signal SIGSEGV, Segmentation fault.
0x00007ffff7f7a697 in mp_init (a=a@entry=0x0) at mp_init.c:10
------>^^^(Arrow added by me) Caveat: different architectures may have different values for NULL, see C-FAQ at Another point: The error-handling in Libtommath is black and white: in case of an error say what kind of error, clean up and abort the function. There are exceptions, of course, where the error gets used as a flag instead of an error (e.g., This behavior makes backtracking a bit harder if the error happens deep inside a recursion. You might use sth. like the macro above for some kind of rudimentary backtracking, it can save some debugging time. So it all boils down to the questions: how much hand-holding is necessary and am I willing to maintain all of the code involved in the aforementioned hand-holding? |
|
Totally fair and appreciate the detailed breakdown! You're spot on about the non-mp_err functions (like mp_cmp, mp_count_bits) where shoehorning sentinels would be awkward and ambiguous. The initial motivation was mostly defensive ergonomics for higher-level language bindings/FFI where catching a NULL early with MP_VAL is friendlier than an abrupt SIGSEGV. But I completely understand and respect LTM's design philosophy and the maintenance tradeoffs that come with defensive hand-holding in C. If you prefer keeping LTM minimal and leaving pointer validation strictly to the caller, feel totally free to close this out. Alternatively, if you think it's useful to keep guards only on the key mp_err-returning entry points (like mp_init* / mp_read_* / mp_to_*), I'm happy to trim the patch down to just those. Either way, thanks for taking the time to share the project's background and reasoning! |
This change adds defensive NULL pointer checks across public API functions to prevent potential null pointer dereferences when invalid or uninitialized pointers are supplied by caller code: