| Title | janet-lang janet 4dd08a4 Heap-based Buffer Overflow |
|---|
| Description | ### Description
We discovered a Heap-buffer-overflow vulnerability in Janet. The crash occurs in the handleattr function during the compilation phase, specifically when processing definition attributes.
The ASAN report indicates a READ violation of size 8, occurring exactly at the boundary (0 bytes after) of a 40-byte allocated region (likely a Tuple of size 5).
Vendor confirmed and fixed this vulnerability in commit [2fabc80](https://github.com/janet-lang/janet/commit/2fabc80151a2b8834ee59cda8a70453f848b40e5).
### Environment
- OS: Linux x86_64
- Complier: Clang
- Build Configuration: Release mode with ASan enabled.
### Vulnerability Details
- Target: Janet (janet-lang)
- Vulnerability Type: CWE-125: Out-of-bounds Read
- Function: handleattr
- Location: src/core/specials.c:311 (Called by janetc_def)
- Root Cause Analysis: The function handleattr parses attributes for definitions (likely accessing elements of a source tuple). The ASAN report shows:
```
0x504000021138 is located 0 bytes after 40-byte region
```
This implies handleattr is accessing an index equal to the size of the tuple (e.g., accessing index 5 of a size-5 tuple), causing an off-by-one read overflow. This typically happens when the compiler expects a specific syntax structure (like a key-value pair or a specific number of arguments) but the input provides a malformed structure that passes initial checks but fails during specific attribute handling.
### Reproduce
1. Build janet and harness with Release optimization and ASAN enabled.
<details>
<summary>harness.c</summary>
```
#include "janet.h"
#include <stdio.h>
#include <stdlib.h>
#include <unistd.h>
#include <stdint.h>
#include <string.h>
int main(int argc, char **argv) {
if (argc < 2) {
return 1;
}
janet_init();
JanetTable *env = janet_core_env(NULL);
FILE *f = fopen(argv[1], "rb");
if (!f) {
janet_deinit();
return 1;
}
fseek(f, 0, SEEK_END);
long len = ftell(f);
fseek(f, 0, SEEK_SET);
unsigned char *buf = (unsigned char *)malloc(len + 1);
if (!buf) {
fclose(f);
janet_deinit();
return 1;
}
if (fread(buf, 1, len, f) != len) {
free(buf);
fclose(f);
janet_deinit();
return 1;
}
fclose(f);
buf[len] = '\0';
if (len >= 1) {
Janet retval;
janet_dostring(env, (const char *)buf, NULL, &retval);
janet_gcroot(janet_wrap_nil());
}
free(buf);
janet_deinit();
return 0;
}
```
</details>
2. Run with the crashing [file](https://github.com/oneafter/0123/blob/main/ja1/repro):
```
./harness repro
```
<details>
<summary>ASAN report</summary>
```
==50040==ERROR: AddressSanitizer: heap-buffer-overflow on address 0x504000021138 at pc 0x55b2717ba589 bp 0x7ffeee6fe8a0 sp 0x7ffeee6fe898
READ of size 8 at 0x504000021138 thread T0
#0 0x55b2717ba588 in handleattr /src/janet/src/core/specials.c:311:32
#1 0x55b2717a8959 in janetc_def /src/janet/src/core/specials.c:545:30
#2 0x55b2715ee736 in janetc_value /src/janet/src/core/compile.c:822:15
#3 0x55b2717aed2b in janetc_fn /src/janet/src/core/specials.c:1131:13
#4 0x55b2715ee736 in janetc_value /src/janet/src/core/compile.c:822:15
#5 0x55b2716ffdce in dohead_destructure /src/janet/src/core/specials.c:400:21
#6 0x55b2717a8b0a in janetc_def /src/janet/src/core/specials.c:552:12
#7 0x55b2715ee736 in janetc_value /src/janet/src/core/compile.c:822:15
#8 0x55b2715fb35e in janet_compile_lint /src/janet/src/core/compile.c:1081:5
#9 0x55b2716fc3dd in janet_compile /src/janet/src/core/compile.c:1099:12
#10 0x55b2716fc3dd in janet_dobytes /src/janet/src/core/run.c:51:39
#11 0x55b27159d0f3 in main /src/janet/harness.c:44:9
#12 0x7f4026cc51c9 (/lib/x86_64-linux-gnu/libc.so.6+0x2a1c9) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e)
#13 0x7f4026cc528a in __libc_start_main (/lib/x86_64-linux-gnu/libc.so.6+0x2a28a) (BuildId: 274eec488d230825a136fa9c4d85370fed7a0a5e)
#14 0x55b2714bba94 in _start (/src/janet/harness+0x54a94) (BuildId: 99073a3a75c69a7f87afa4e4c777fe183943214e)
0x504000021138 is located 0 bytes after 40-byte region [0x504000021110,0x504000021138)
allocated by thread T0 here:
#0 0x55b27155b8c3 in malloc (/src/janet/harness+0xf48c3) (BuildId: 99073a3a75c69a7f87afa4e4c777fe183943214e)
#1 0x55b27159dcd2 in janet_gcalloc /src/janet/src/core/gc.c:536:11
SUMMARY: AddressSanitizer: heap-buffer-overflow /src/janet/src/core/specials.c:311:32 in handleattr
Shadow bytes around the buggy address:
0x504000020e80: fa fa 00 00 00 00 00 00 fa fa 00 00 00 00 00 00
0x504000020f00: fa fa 00 00 00 00 00 00 fa fa 00 00 00 00 00 00
0x504000020f80: fa fa 00 00 00 00 00 00 fa fa 00 00 00 00 00 00
0x504000021000: fa fa 00 00 00 00 03 fa fa fa 00 00 00 00 00 00
0x504000021080: fa fa fd fd fd fd fd fd fa fa 00 00 00 00 00 fa
=>0x504000021100: fa fa 00 00 00 00 00[fa]fa fa 00 00 00 00 00 00
0x504000021180: fa fa 00 00 00 00 00 fa fa fa 00 00 00 00 00 00
0x504000021200: fa fa 00 00 00 00 00 00 fa fa fa fa fa fa fa fa
0x504000021280: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x504000021300: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
0x504000021380: fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa fa
Shadow byte legend (one shadow byte represents 8 application bytes):
Addressable: 00
Partially addressable: 01 02 03 04 05 06 07
Heap left redzone: fa
Freed heap region: fd
Stack left redzone: f1
Stack mid redzone: f2
Stack right redzone: f3
Stack after return: f5
Stack use after scope: f8
Global redzone: f9
Global init order: f6
Poisoned by user: f7
Container overflow: fc
Array cookie: ac
Intra object redzone: bb
ASan internal: fe
Left alloca redzone: ca
Right alloca redzone: cb
==50040==ABORTING
```
</details> |
|---|
| Source | ⚠️ https://github.com/janet-lang/janet/issues/1699 |
|---|
| User | Oneafter (UID 92781) |
|---|
| Submission | 02/09/2026 11:03 (2 months ago) |
|---|
| Moderation | 02/20/2026 15:34 (11 days later) |
|---|
| Status | Accepted |
|---|
| VulDB entry | 347106 [janet-lang janet up to 1.40.1 handleattr src/core/specials.c janetc_varset out-of-bounds] |
|---|
| Points | 20 |
|---|