Hello! Most ctype(3) functions usage abuses an undefined behavior of passing values outside integers outside EOF or the range representable as unsigned char. On NetBSD 11 and newer such incorrect behaviors crashes applications violating that and due that Dillo easily crash by just visiting, e.g. <https://www.NetBSD.org/>. For possible further information please give a look to <https://man.NetBSD.org/ctype.3#CAVEATS>. I have grep-ed all such uses and added the relevant cast to uchar_t similar to other ctype(3) calls already present and in that way I can also confirm that Dillo no longer crashes. Attached in this email you can find a patch to address that. Thank you!
Hi, On Mon, Jan 12, 2026 at 11:05:17AM +0100, Leonardo Taccari wrote:
Hello!
Most ctype(3) functions usage abuses an undefined behavior of passing values outside integers outside EOF or the range representable as unsigned char.
On NetBSD 11 and newer such incorrect behaviors crashes applications violating that and due that Dillo easily crash by just visiting, e.g. <https://www.NetBSD.org/>.
For possible further information please give a look to <https://man.NetBSD.org/ctype.3#CAVEATS>.
Interesting!
I have grep-ed all such uses and added the relevant cast to uchar_t similar to other ctype(3) calls already present and in that way I can also confirm that Dillo no longer crashes.
Thanks for the patch and tests. We have wrappers that do just that for isspace() and isalnum() in dlib/dlib.h: https://git.dillo-browser.org/dillo/tree/dlib/dlib.h?id=7c6ccebe610779021607... I would be inclined to add the extra wrappers there and use the dIsspace, dIsalnum... macros instead of doing the explicit casting. Additionally, I think it would be safer to also switch all these ctype macros to static inline functions so we cover accidental castings from unexpected types with an error instead of a warning: static inline int dIsalnum(unsigned char c) { return isalnum(c); } Example: % cat foo.c #include <ctype.h> static inline int dIsalnum(unsigned char c) { return isalnum(c); } #define dIsalnum_unsafe(c) isalnum((unsigned char) (c)) int main(void) { char *a = "a"; dIsalnum(a); dIsalnum_unsafe(a); return 0; } % gcc foo.c -o foo foo.c: In function ‘main’: foo.c:8:18: error: passing argument 1 of ‘dIsalnum’ makes integer from pointer without a cast [-Wint-conversion] 8 | dIsalnum(a); | ^ | | | char * foo.c:2:42: note: expected ‘unsigned char’ but argument is of type ‘char *’ 2 | static inline int dIsalnum(unsigned char c) { return isalnum(c); } | ~~~~~~~~~~~~~~^ In file included from foo.c:1: foo.c:3:36: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] 3 | #define dIsalnum_unsafe(c) isalnum((unsigned char) (c)) | ^ foo.c:9:9: note: in expansion of macro ‘dIsalnum_unsafe’ 9 | dIsalnum_unsafe(a); | ^~~~~~~~~~~~~~~ Best, Rodrigo.
Hello Rodrigo, Rodrigo Arias writes:
Hi,
On Mon, Jan 12, 2026 at 11:05:17AM +0100, Leonardo Taccari wrote:
Hello!
Most ctype(3) functions usage abuses an undefined behavior of passing values outside integers outside EOF or the range representable as unsigned char.
On NetBSD 11 and newer such incorrect behaviors crashes applications violating that and due that Dillo easily crash by just visiting, e.g. <https://www.NetBSD.org/>.
For possible further information please give a look to <https://man.NetBSD.org/ctype.3#CAVEATS>.
Interesting!
I have grep-ed all such uses and added the relevant cast to uchar_t similar to other ctype(3) calls already present and in that way I can also confirm that Dillo no longer crashes.
Thanks for the patch and tests.
We have wrappers that do just that for isspace() and isalnum() in dlib/dlib.h:
https://git.dillo-browser.org/dillo/tree/dlib/dlib.h?id=7c6ccebe610779021607...
I would be inclined to add the extra wrappers there and use the dIsspace, dIsalnum... macros instead of doing the explicit casting.
Additionally, I think it would be safer to also switch all these ctype macros to static inline functions so we cover accidental castings from unexpected types with an error instead of a warning:
static inline int dIsalnum(unsigned char c) { return isalnum(c); }
Example:
% cat foo.c #include <ctype.h> static inline int dIsalnum(unsigned char c) { return isalnum(c); } #define dIsalnum_unsafe(c) isalnum((unsigned char) (c))
int main(void) { char *a = "a"; dIsalnum(a); dIsalnum_unsafe(a);
return 0; }
% gcc foo.c -o foo foo.c: In function ‘main’: foo.c:8:18: error: passing argument 1 of ‘dIsalnum’ makes integer from pointer without a cast [-Wint-conversion] 8 | dIsalnum(a); | ^ | | | char * foo.c:2:42: note: expected ‘unsigned char’ but argument is of type ‘char *’ 2 | static inline int dIsalnum(unsigned char c) { return isalnum(c); } | ~~~~~~~~~~~~~~^ In file included from foo.c:1: foo.c:3:36: warning: cast from pointer to integer of different size [-Wpointer-to-int-cast] 3 | #define dIsalnum_unsafe(c) isalnum((unsigned char) (c)) | ^ foo.c:9:9: note: in expansion of macro ‘dIsalnum_unsafe’ 9 | dIsalnum_unsafe(a); | ^~~~~~~~~~~~~~~ [...]
Good idea! I will then add possible missing d*() helper ctype(3) functions and adapt all direct ctype(3) users to switch to such d*() helper functions. I hope to be able to share possible patches for that in 48 hours or so! Thank you!
participants (2)
-
Leonardo Taccari -
Rodrigo Arias