Package: emacs;
Reported by: Pip Cet <pipcet <at> protonmail.com>
Date: Sun, 25 Aug 2024 13:15:02 UTC
Severity: normal
Found in version 31.0.50
Done: Felix Lechner <felix.lechner <at> lease-up.com>
Bug is archived. No further changes may be made.
View this message in rfc822 format
From: Pip Cet <pipcet <at> protonmail.com> To: 72802 <at> debbugs.gnu.org Subject: bug#72802: 31.0.50; Crash in (equal sub-char-table-a sub-char-table-b) Date: Sun, 25 Aug 2024 13:13:34 +0000
Summary: Comparing sub char tables can lead to crashes in equal when they are read with their read syntax; using high-level char table manipulation routines and comparing char tables (not sub char tables directly) is almost certain to result in rare crashes as well. The code in internal_equal compares sub-char-tables incorrectly and segfaults on my machine (little-endian 64-bit words, LSB tags, 3 == Lisp_Cons) when evaluating this code: (setq a #^^[3 2597376 (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3)]) (setq b #^^[3 2597504 (3) (3) (3) (3) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (3) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2) (2)]) (equal a b) This happened to me while working on pdumper code for the no-purespace branch and trying to compare dumped sub char tables, but it can happen when reading sub char tables using their read syntax, too. I'm almost certain the bug can actually happen when manipulating char tables using higher-level routines, and comparing char tables, not sub char tables. Comparing char tables definitely results in nonsensical (but identical) arguments 'o1' and 'o2' being passed to 'internal_equal', and put into the hash table 'ht' in internal_equal. This is almost definitely a crashable bug, but a very rare one (it relies on conservative stack marking marking our hash table and trying to mark the invalid conses in it), without using the read syntax for sub char tables. On 32-bit machines, the crashes might be much more common. The problem is this code: for (ptrdiff_t i = 0; i < size; i++) { Lisp_Object v1, v2; v1 = AREF (o1, i); v2 = AREF (o2, i); if (!internal_equal (v1, v2, equal_kind, depth + 1, ht)) return false; } which assumes sub char tables are ordinary pseudovectors and can be compared by comparing XVECTOR (o1)->contents to XVECTOR (o2)->contents. However, sub char tables should be compared by comparing XSUB_CHAR_TABLE (o1)->contents to XSUB_CHAR_TABLE (o2)->contents, after checking that 'depth' and 'min_char' also match. The memory layout of sub char tables is: struct Lisp_Sub_Char_Table { /* HEADER.SIZE is the vector's size field, which also holds the pseudovector type information. It holds the size, too. */ union vectorlike_header header; /* Depth of this sub char-table. It should be 1, 2, or 3. A sub char-table of depth 1 contains 16 elements, and each element covers 4096 (128*32) characters. A sub char-table of depth 2 contains 32 elements, and each element covers 128 characters. A sub char-table of depth 3 contains 128 elements, and each element is for one character. */ int depth; /* Minimum character covered by the sub char-table. */ int min_char; /* Use set_sub_char_table_contents to set this. */ Lisp_Object contents[FLEXIBLE_ARRAY_MEMBER]; } GCALIGNED_STRUCT; So the first 64-bit word after the header has 'min_char' in the high bits, 3 in the low bits, in the above example. In my case, we end up calling internal_equal (o1=XIL(0x27a20000000003), o2=XIL(0x27a28000000003), equal_kind=EQUAL_PLAIN, depth=1, ht=XIL(0)) at fns.c:2887 with the nonsensical Lisp words o1 = 0x27a20000000003 (depth = 3, min_char = 0x27a200) and o2 = 0x27a28000000003 (depth = 3, min_char = 0x27a280); these are interpreted as Lisp conses and we attempt to dereference them, which leads to the segfault. Relevant section of the backtrace: (gdb) bt full #0 0x0000555555838ea7 in internal_equal (o1=XIL(0x27a20000000003), o2=XIL(0x27a28000000003), equal_kind=EQUAL_PLAIN, depth=1, ht=XIL(0)) at fns.c:2887 li = { tortoise = XIL(0x27a20000000003), max = 2, n = 0, q = 2 } #1 0x00005555558393c8 in internal_equal (o1=XIL(0x555557718945), o2=XIL(0x55555677bda5), equal_kind=EQUAL_PLAIN, depth=0, ht=XIL(0)) at fns.c:2963 v1 = XIL(0x27a20000000003) v2 = XIL(0x27a28000000003) i = 0 size = 129 #2 0x0000555555838a01 in Fequal (o1=XIL(0x555557718945), o2=XIL(0x55555677bda5)) at fns.c:2783 (gdb) l 2958 for (ptrdiff_t i = 0; i < size; i++) 2959 { 2960 Lisp_Object v1, v2; 2961 v1 = AREF (o1, i); 2962 v2 = AREF (o2, i); 2963 if (!internal_equal (v1, v2, equal_kind, depth + 1, ht)) 2964 return false; 2965 } 2966 return true; 2967 } (gdb) p SUB_CHAR_TABLE_P (o1) $38 = true (gdb) p SUB_CHAR_TABLE_P (o2) $39 = true (gdb) p *XSUB_CHAR_TABLE (o1) $40 = { header = { size = 4611686018981036161 }, depth = 3, min_char = 2597376, contents = 0x555557718950 } (gdb) p *XSUB_CHAR_TABLE (o2) $41 = { header = { size = 4611686018981036161 }, depth = 3, min_char = 2597504, contents = 0x55555677bdb0 } (gdb) p AREF (o1, 0) $42 = (struct Lisp_X *) 0x27a20000000003 (gdb) p AREF (o2, 0) $43 = (struct Lisp_X *) 0x27a28000000003 (gdb) p *XVECTOR (o1) $44 = { header = { size = 4611686018981036161 }, contents = 0x555557718948 } (gdb) p *XVECTOR (o2) $45 = { header = { size = 4611686018981036161 }, contents = 0x55555677bda8 } Fix coming up once this has a bug number.
GNU bug tracking system
Copyright (C) 1999 Darren O. Benham,
1997,2003 nCipher Corporation Ltd,
1994-97 Ian Jackson.