1f08c3bdfSopenharmony_ci// SPDX-License-Identifier: GPL-2.0-or-later 2f08c3bdfSopenharmony_ci/* 3f08c3bdfSopenharmony_ci * Copyright (c) 2017 Pavel Boldin <pboldin@cloudlinux.com> 4f08c3bdfSopenharmony_ci * Copyright (c) 2023 Rick Edgecombe <rick.p.edgecombe@intel.com> 5f08c3bdfSopenharmony_ci * Copyright (c) Linux Test Project, 2017-2023 6f08c3bdfSopenharmony_ci */ 7f08c3bdfSopenharmony_ci 8f08c3bdfSopenharmony_ci/*\ 9f08c3bdfSopenharmony_ci * [Description] 10f08c3bdfSopenharmony_ci * 11f08c3bdfSopenharmony_ci * This is a regression test of the Stack Clash [1] vulnerability. This tests 12f08c3bdfSopenharmony_ci * that there is at least 256 PAGE_SIZE of stack guard gap which is considered 13f08c3bdfSopenharmony_ci * hard to hop above. Code adapted from the Novell's bugzilla [2]. 14f08c3bdfSopenharmony_ci * 15f08c3bdfSopenharmony_ci * The code `mmap(2)`s region close to the stack end. The code then allocates 16f08c3bdfSopenharmony_ci * memory on stack until it hits guard page and SIGSEGV or SIGBUS is generated 17f08c3bdfSopenharmony_ci * by the kernel. The signal handler checks that fault address is further than 18f08c3bdfSopenharmony_ci * THRESHOLD from the mmapped area. 19f08c3bdfSopenharmony_ci * 20f08c3bdfSopenharmony_ci * We read /proc/self/maps to examine exact top of the stack and `mmap(2)` 21f08c3bdfSopenharmony_ci * our region exactly GAP_PAGES * PAGE_SIZE away. We read /proc/cmdline to 22f08c3bdfSopenharmony_ci * see if a different stack_guard_gap size is configured. We set stack limit 23f08c3bdfSopenharmony_ci * to infinity and preallocate REQ_STACK_SIZE bytes of stack so that no calls 24f08c3bdfSopenharmony_ci * after `mmap` are moving stack further. 25f08c3bdfSopenharmony_ci * 26f08c3bdfSopenharmony_ci * If the architecture meets certain requirements (only x86_64 is verified) 27f08c3bdfSopenharmony_ci * then the test also tests that new mmap()s can't be placed in the stack's 28f08c3bdfSopenharmony_ci * guard gap. This part of the test works by forcing a bottom up search. The 29f08c3bdfSopenharmony_ci * assumptions are that the stack grows down (start gap) and either: 30f08c3bdfSopenharmony_ci * 31f08c3bdfSopenharmony_ci * 1. The default search is top down, and will switch to bottom up if 32f08c3bdfSopenharmony_ci * space is exhausted. 33f08c3bdfSopenharmony_ci * 2. The default search is bottom up and the stack is above mmap base. 34f08c3bdfSopenharmony_ci * 35f08c3bdfSopenharmony_ci * [1] https://blog.qualys.com/securitylabs/2017/06/19/the-stack-clash 36f08c3bdfSopenharmony_ci * [2] https://bugzilla.novell.com/show_bug.cgi?id=CVE-2017-1000364 37f08c3bdfSopenharmony_ci */ 38f08c3bdfSopenharmony_ci 39f08c3bdfSopenharmony_ci#include <sys/wait.h> 40f08c3bdfSopenharmony_ci#include <stdio.h> 41f08c3bdfSopenharmony_ci#include <unistd.h> 42f08c3bdfSopenharmony_ci#include <alloca.h> 43f08c3bdfSopenharmony_ci#include <signal.h> 44f08c3bdfSopenharmony_ci#include <stdlib.h> 45f08c3bdfSopenharmony_ci 46f08c3bdfSopenharmony_ci#include "tst_test.h" 47f08c3bdfSopenharmony_ci#include "tst_safe_stdio.h" 48f08c3bdfSopenharmony_ci#include "lapi/mmap.h" 49f08c3bdfSopenharmony_ci 50f08c3bdfSopenharmony_cistatic unsigned long page_size; 51f08c3bdfSopenharmony_cistatic unsigned long page_mask; 52f08c3bdfSopenharmony_cistatic unsigned long GAP_PAGES = 256; 53f08c3bdfSopenharmony_cistatic unsigned long THRESHOLD; 54f08c3bdfSopenharmony_cistatic int STACK_GROWSDOWN; 55f08c3bdfSopenharmony_ci 56f08c3bdfSopenharmony_ci#define SIGNAL_STACK_SIZE (1UL<<20) 57f08c3bdfSopenharmony_ci#define FRAME_SIZE 1024 58f08c3bdfSopenharmony_ci#define REQ_STACK_SIZE (1024 * 1024) 59f08c3bdfSopenharmony_ci 60f08c3bdfSopenharmony_ci#define EXIT_TESTBROKE TBROK 61f08c3bdfSopenharmony_ci 62f08c3bdfSopenharmony_civoid exhaust_stack_into_sigsegv(void) 63f08c3bdfSopenharmony_ci{ 64f08c3bdfSopenharmony_ci volatile char * ptr = alloca(FRAME_SIZE - sizeof(long)); 65f08c3bdfSopenharmony_ci *ptr = '\0'; 66f08c3bdfSopenharmony_ci exhaust_stack_into_sigsegv(); 67f08c3bdfSopenharmony_ci} 68f08c3bdfSopenharmony_ci 69f08c3bdfSopenharmony_ci#define MAPPED_LEN page_size 70f08c3bdfSopenharmony_cistatic unsigned long mapped_addr; 71f08c3bdfSopenharmony_ci 72f08c3bdfSopenharmony_civoid segv_handler(int sig, siginfo_t *info, void *data LTP_ATTRIBUTE_UNUSED) 73f08c3bdfSopenharmony_ci{ 74f08c3bdfSopenharmony_ci unsigned long fault_addr = (unsigned long)info->si_addr; 75f08c3bdfSopenharmony_ci unsigned long mmap_end = mapped_addr + MAPPED_LEN; 76f08c3bdfSopenharmony_ci ssize_t diff; 77f08c3bdfSopenharmony_ci 78f08c3bdfSopenharmony_ci if (sig != SIGSEGV && sig != SIGBUS) 79f08c3bdfSopenharmony_ci return; 80f08c3bdfSopenharmony_ci 81f08c3bdfSopenharmony_ci if (STACK_GROWSDOWN) 82f08c3bdfSopenharmony_ci diff = fault_addr - mmap_end; 83f08c3bdfSopenharmony_ci else 84f08c3bdfSopenharmony_ci diff = mapped_addr - fault_addr; 85f08c3bdfSopenharmony_ci 86f08c3bdfSopenharmony_ci tst_res(TINFO, 87f08c3bdfSopenharmony_ci "mmap = [%lx, %lx), addr = %lx, diff = %zx, THRESHOLD = %lx", 88f08c3bdfSopenharmony_ci mapped_addr, mmap_end, fault_addr, diff, THRESHOLD); 89f08c3bdfSopenharmony_ci if (diff < 0 || (unsigned long)diff < THRESHOLD) 90f08c3bdfSopenharmony_ci _exit(EXIT_FAILURE); 91f08c3bdfSopenharmony_ci else 92f08c3bdfSopenharmony_ci _exit(EXIT_SUCCESS); 93f08c3bdfSopenharmony_ci} 94f08c3bdfSopenharmony_ci 95f08c3bdfSopenharmony_cistatic void force_bottom_up(void) 96f08c3bdfSopenharmony_ci{ 97f08c3bdfSopenharmony_ci FILE *fh; 98f08c3bdfSopenharmony_ci char buf[1024]; 99f08c3bdfSopenharmony_ci unsigned long start, end, size, lastend = 0; 100f08c3bdfSopenharmony_ci 101f08c3bdfSopenharmony_ci /* start filling from mmap_min_addr */ 102f08c3bdfSopenharmony_ci SAFE_FILE_SCANF("/proc/sys/vm/mmap_min_addr", "%lu", &lastend); 103f08c3bdfSopenharmony_ci 104f08c3bdfSopenharmony_ci fh = SAFE_FOPEN("/proc/self/maps", "r"); 105f08c3bdfSopenharmony_ci 106f08c3bdfSopenharmony_ci while (!feof(fh)) { 107f08c3bdfSopenharmony_ci if (fgets(buf, sizeof(buf), fh) == NULL) 108f08c3bdfSopenharmony_ci goto out; 109f08c3bdfSopenharmony_ci 110f08c3bdfSopenharmony_ci if (sscanf(buf, "%lx-%lx", &start, &end) != 2) { 111f08c3bdfSopenharmony_ci tst_brk(TBROK | TERRNO, "sscanf"); 112f08c3bdfSopenharmony_ci goto out; 113f08c3bdfSopenharmony_ci } 114f08c3bdfSopenharmony_ci 115f08c3bdfSopenharmony_ci size = start - lastend; 116f08c3bdfSopenharmony_ci 117f08c3bdfSopenharmony_ci /* Skip the PROT_NONE that was just added (!size). */ 118f08c3bdfSopenharmony_ci if (!size) { 119f08c3bdfSopenharmony_ci lastend = end; 120f08c3bdfSopenharmony_ci continue; 121f08c3bdfSopenharmony_ci } 122f08c3bdfSopenharmony_ci 123f08c3bdfSopenharmony_ci /* If the next area is the stack, quit. */ 124f08c3bdfSopenharmony_ci if (!!strstr(buf, "[stack]")) 125f08c3bdfSopenharmony_ci break; 126f08c3bdfSopenharmony_ci 127f08c3bdfSopenharmony_ci /* This is not cleaned up. */ 128f08c3bdfSopenharmony_ci SAFE_MMAP((void *)lastend, size, PROT_NONE, 129f08c3bdfSopenharmony_ci MAP_ANON|MAP_PRIVATE|MAP_FIXED_NOREPLACE, -1, 0); 130f08c3bdfSopenharmony_ci 131f08c3bdfSopenharmony_ci lastend = end; 132f08c3bdfSopenharmony_ci } 133f08c3bdfSopenharmony_ci 134f08c3bdfSopenharmony_ciout: 135f08c3bdfSopenharmony_ci SAFE_FCLOSE(fh); 136f08c3bdfSopenharmony_ci} 137f08c3bdfSopenharmony_ci 138f08c3bdfSopenharmony_ciunsigned long read_stack_addr_from_proc(unsigned long *stack_size) 139f08c3bdfSopenharmony_ci{ 140f08c3bdfSopenharmony_ci FILE *fh; 141f08c3bdfSopenharmony_ci char buf[1024]; 142f08c3bdfSopenharmony_ci unsigned long stack_top = -1UL, start, end; 143f08c3bdfSopenharmony_ci 144f08c3bdfSopenharmony_ci fh = SAFE_FOPEN("/proc/self/maps", "r"); 145f08c3bdfSopenharmony_ci 146f08c3bdfSopenharmony_ci while (!feof(fh)) { 147f08c3bdfSopenharmony_ci if (fgets(buf, sizeof(buf), fh) == NULL) { 148f08c3bdfSopenharmony_ci tst_brk(TBROK | TERRNO, "fgets"); 149f08c3bdfSopenharmony_ci goto out; 150f08c3bdfSopenharmony_ci } 151f08c3bdfSopenharmony_ci 152f08c3bdfSopenharmony_ci if (!strstr(buf, "[stack")) 153f08c3bdfSopenharmony_ci continue; 154f08c3bdfSopenharmony_ci 155f08c3bdfSopenharmony_ci if (sscanf(buf, "%lx-%lx", &start, &end) != 2) { 156f08c3bdfSopenharmony_ci tst_brk(TBROK | TERRNO, "sscanf"); 157f08c3bdfSopenharmony_ci goto out; 158f08c3bdfSopenharmony_ci } 159f08c3bdfSopenharmony_ci 160f08c3bdfSopenharmony_ci *stack_size = end - start; 161f08c3bdfSopenharmony_ci 162f08c3bdfSopenharmony_ci if (STACK_GROWSDOWN) 163f08c3bdfSopenharmony_ci stack_top = start; 164f08c3bdfSopenharmony_ci else 165f08c3bdfSopenharmony_ci stack_top = end; 166f08c3bdfSopenharmony_ci break; 167f08c3bdfSopenharmony_ci } 168f08c3bdfSopenharmony_ci 169f08c3bdfSopenharmony_ciout: 170f08c3bdfSopenharmony_ci SAFE_FCLOSE(fh); 171f08c3bdfSopenharmony_ci return stack_top; 172f08c3bdfSopenharmony_ci} 173f08c3bdfSopenharmony_ci 174f08c3bdfSopenharmony_civoid dump_proc_self_maps(void) 175f08c3bdfSopenharmony_ci{ 176f08c3bdfSopenharmony_ci static char buf[64]; 177f08c3bdfSopenharmony_ci static const char *cmd[] = {"cat", buf, NULL}; 178f08c3bdfSopenharmony_ci sprintf(buf, "/proc/%d/maps", getpid()); 179f08c3bdfSopenharmony_ci tst_cmd(cmd, NULL, NULL, 0); 180f08c3bdfSopenharmony_ci} 181f08c3bdfSopenharmony_ci 182f08c3bdfSopenharmony_civoid __attribute__((noinline)) preallocate_stack(unsigned long required) 183f08c3bdfSopenharmony_ci{ 184f08c3bdfSopenharmony_ci volatile char *garbage; 185f08c3bdfSopenharmony_ci 186f08c3bdfSopenharmony_ci garbage = alloca(required); 187f08c3bdfSopenharmony_ci garbage[0] = garbage[required - 1] = '\0'; 188f08c3bdfSopenharmony_ci} 189f08c3bdfSopenharmony_ci 190f08c3bdfSopenharmony_cistatic void do_mmap_placement_test(unsigned long stack_addr, unsigned long gap) 191f08c3bdfSopenharmony_ci{ 192f08c3bdfSopenharmony_ci void *map_test_gap; 193f08c3bdfSopenharmony_ci 194f08c3bdfSopenharmony_ci force_bottom_up(); 195f08c3bdfSopenharmony_ci 196f08c3bdfSopenharmony_ci /* 197f08c3bdfSopenharmony_ci * force_bottom_up() used up all the spaces below the stack. The search down 198f08c3bdfSopenharmony_ci * path should fail, and search up might take a look at the guard gap 199f08c3bdfSopenharmony_ci * region. If it avoids it, the allocation will be above the stack. If it 200f08c3bdfSopenharmony_ci * uses it, the allocation will be in the gap and the test should fail. 201f08c3bdfSopenharmony_ci */ 202f08c3bdfSopenharmony_ci map_test_gap = SAFE_MMAP(0, MAPPED_LEN, 203f08c3bdfSopenharmony_ci PROT_READ|PROT_WRITE, MAP_ANON|MAP_PRIVATE, 0, 0); 204f08c3bdfSopenharmony_ci 205f08c3bdfSopenharmony_ci if (stack_addr - gap <= (unsigned long)map_test_gap && 206f08c3bdfSopenharmony_ci (unsigned long)map_test_gap <= stack_addr) { 207f08c3bdfSopenharmony_ci tst_res(TFAIL, "New mmap was placed in the guard gap."); 208f08c3bdfSopenharmony_ci SAFE_MUNMAP(map_test_gap, MAPPED_LEN); 209f08c3bdfSopenharmony_ci } 210f08c3bdfSopenharmony_ci} 211f08c3bdfSopenharmony_ci 212f08c3bdfSopenharmony_civoid do_child(void) 213f08c3bdfSopenharmony_ci{ 214f08c3bdfSopenharmony_ci unsigned long stack_addr, stack_size; 215f08c3bdfSopenharmony_ci stack_t signal_stack; 216f08c3bdfSopenharmony_ci struct sigaction segv_sig = {.sa_sigaction = segv_handler, .sa_flags = SA_ONSTACK|SA_SIGINFO}; 217f08c3bdfSopenharmony_ci void *map; 218f08c3bdfSopenharmony_ci unsigned long gap = GAP_PAGES * page_size; 219f08c3bdfSopenharmony_ci struct rlimit rlimit; 220f08c3bdfSopenharmony_ci 221f08c3bdfSopenharmony_ci rlimit.rlim_cur = rlimit.rlim_max = RLIM_INFINITY; 222f08c3bdfSopenharmony_ci SAFE_SETRLIMIT(RLIMIT_STACK, &rlimit); 223f08c3bdfSopenharmony_ci 224f08c3bdfSopenharmony_ci preallocate_stack(REQ_STACK_SIZE); 225f08c3bdfSopenharmony_ci 226f08c3bdfSopenharmony_ci stack_addr = read_stack_addr_from_proc(&stack_size); 227f08c3bdfSopenharmony_ci if (stack_addr == -1UL) { 228f08c3bdfSopenharmony_ci tst_brk(TBROK, "can't read stack top from /proc/self/maps"); 229f08c3bdfSopenharmony_ci return; 230f08c3bdfSopenharmony_ci } 231f08c3bdfSopenharmony_ci 232f08c3bdfSopenharmony_ci if (STACK_GROWSDOWN) 233f08c3bdfSopenharmony_ci mapped_addr = stack_addr - gap - MAPPED_LEN; 234f08c3bdfSopenharmony_ci else 235f08c3bdfSopenharmony_ci mapped_addr = stack_addr + gap; 236f08c3bdfSopenharmony_ci 237f08c3bdfSopenharmony_ci mapped_addr &= page_mask; 238f08c3bdfSopenharmony_ci map = SAFE_MMAP((void *)mapped_addr, MAPPED_LEN, 239f08c3bdfSopenharmony_ci PROT_READ|PROT_WRITE, 240f08c3bdfSopenharmony_ci MAP_ANON|MAP_PRIVATE|MAP_FIXED, -1, 0); 241f08c3bdfSopenharmony_ci tst_res(TINFO, "Stack:0x%lx+0x%lx mmap:%p+0x%lx", 242f08c3bdfSopenharmony_ci stack_addr, stack_size, map, MAPPED_LEN); 243f08c3bdfSopenharmony_ci 244f08c3bdfSopenharmony_ci signal_stack.ss_sp = SAFE_MALLOC(SIGNAL_STACK_SIZE); 245f08c3bdfSopenharmony_ci signal_stack.ss_size = SIGNAL_STACK_SIZE; 246f08c3bdfSopenharmony_ci signal_stack.ss_flags = 0; 247f08c3bdfSopenharmony_ci if (sigaltstack(&signal_stack, NULL) == -1) { 248f08c3bdfSopenharmony_ci tst_brk(TBROK | TERRNO, "sigaltstack"); 249f08c3bdfSopenharmony_ci return; 250f08c3bdfSopenharmony_ci } 251f08c3bdfSopenharmony_ci if (sigaction(SIGSEGV, &segv_sig, NULL) == -1 || 252f08c3bdfSopenharmony_ci sigaction(SIGBUS, &segv_sig, NULL) == -1) { 253f08c3bdfSopenharmony_ci tst_brk(TBROK | TERRNO, "sigaction"); 254f08c3bdfSopenharmony_ci return; 255f08c3bdfSopenharmony_ci } 256f08c3bdfSopenharmony_ci 257f08c3bdfSopenharmony_ci#ifdef DEBUG 258f08c3bdfSopenharmony_ci dump_proc_self_maps(); 259f08c3bdfSopenharmony_ci#endif 260f08c3bdfSopenharmony_ci 261f08c3bdfSopenharmony_ci#ifdef __x86_64__ 262f08c3bdfSopenharmony_ci do_mmap_placement_test(stack_addr, gap); 263f08c3bdfSopenharmony_ci#endif 264f08c3bdfSopenharmony_ci 265f08c3bdfSopenharmony_ci /* Now see if it can grow too close to an adjacent region. */ 266f08c3bdfSopenharmony_ci exhaust_stack_into_sigsegv(); 267f08c3bdfSopenharmony_ci} 268f08c3bdfSopenharmony_ci 269f08c3bdfSopenharmony_civoid setup(void) 270f08c3bdfSopenharmony_ci{ 271f08c3bdfSopenharmony_ci char buf[4096], *p; 272f08c3bdfSopenharmony_ci 273f08c3bdfSopenharmony_ci page_size = sysconf(_SC_PAGESIZE); 274f08c3bdfSopenharmony_ci page_mask = ~(page_size - 1); 275f08c3bdfSopenharmony_ci 276f08c3bdfSopenharmony_ci buf[4095] = '\0'; 277f08c3bdfSopenharmony_ci SAFE_FILE_SCANF("/proc/cmdline", "%4095[^\n]", buf); 278f08c3bdfSopenharmony_ci 279f08c3bdfSopenharmony_ci if ((p = strstr(buf, "stack_guard_gap=")) != NULL) { 280f08c3bdfSopenharmony_ci if (sscanf(p, "stack_guard_gap=%ld", &GAP_PAGES) != 1) { 281f08c3bdfSopenharmony_ci tst_brk(TBROK | TERRNO, "sscanf"); 282f08c3bdfSopenharmony_ci return; 283f08c3bdfSopenharmony_ci } 284f08c3bdfSopenharmony_ci tst_res(TINFO, "stack_guard_gap = %ld", GAP_PAGES); 285f08c3bdfSopenharmony_ci } 286f08c3bdfSopenharmony_ci 287f08c3bdfSopenharmony_ci THRESHOLD = (GAP_PAGES - 1) * page_size; 288f08c3bdfSopenharmony_ci 289f08c3bdfSopenharmony_ci { 290f08c3bdfSopenharmony_ci volatile int *a = alloca(128); 291f08c3bdfSopenharmony_ci 292f08c3bdfSopenharmony_ci { 293f08c3bdfSopenharmony_ci volatile int *b = alloca(128); 294f08c3bdfSopenharmony_ci 295f08c3bdfSopenharmony_ci STACK_GROWSDOWN = a > b; 296f08c3bdfSopenharmony_ci tst_res(TINFO, "STACK_GROWSDOWN = %d == %p > %p", STACK_GROWSDOWN, a, b); 297f08c3bdfSopenharmony_ci } 298f08c3bdfSopenharmony_ci } 299f08c3bdfSopenharmony_ci} 300f08c3bdfSopenharmony_ci 301f08c3bdfSopenharmony_civoid stack_clash_test(void) 302f08c3bdfSopenharmony_ci{ 303f08c3bdfSopenharmony_ci int status; 304f08c3bdfSopenharmony_ci pid_t pid; 305f08c3bdfSopenharmony_ci 306f08c3bdfSopenharmony_ci pid = SAFE_FORK(); 307f08c3bdfSopenharmony_ci if (!pid) { 308f08c3bdfSopenharmony_ci do_child(); 309f08c3bdfSopenharmony_ci exit(EXIT_TESTBROKE); 310f08c3bdfSopenharmony_ci return; 311f08c3bdfSopenharmony_ci } 312f08c3bdfSopenharmony_ci 313f08c3bdfSopenharmony_ci SAFE_WAITPID(pid, &status, 0); 314f08c3bdfSopenharmony_ci 315f08c3bdfSopenharmony_ci if (WIFEXITED(status)) { 316f08c3bdfSopenharmony_ci switch (WEXITSTATUS(status)) { 317f08c3bdfSopenharmony_ci case EXIT_FAILURE: 318f08c3bdfSopenharmony_ci tst_res(TFAIL, "stack is too close to the mmaped area"); 319f08c3bdfSopenharmony_ci return; 320f08c3bdfSopenharmony_ci case EXIT_SUCCESS: 321f08c3bdfSopenharmony_ci tst_res(TPASS, "stack is far enough from mmaped area"); 322f08c3bdfSopenharmony_ci return; 323f08c3bdfSopenharmony_ci default: 324f08c3bdfSopenharmony_ci case EXIT_TESTBROKE: 325f08c3bdfSopenharmony_ci break; 326f08c3bdfSopenharmony_ci } 327f08c3bdfSopenharmony_ci } 328f08c3bdfSopenharmony_ci 329f08c3bdfSopenharmony_ci tst_brk(TBROK, "Child %s", tst_strstatus(status)); 330f08c3bdfSopenharmony_ci} 331f08c3bdfSopenharmony_ci 332f08c3bdfSopenharmony_cistatic struct tst_test test = { 333f08c3bdfSopenharmony_ci .forks_child = 1, 334f08c3bdfSopenharmony_ci .needs_root = 1, 335f08c3bdfSopenharmony_ci .setup = setup, 336f08c3bdfSopenharmony_ci .test_all = stack_clash_test, 337f08c3bdfSopenharmony_ci .tags = (const struct tst_tag[]) { 338f08c3bdfSopenharmony_ci {"CVE", "2017-1000364"}, 339f08c3bdfSopenharmony_ci {"linux-git", "58c5d0d6d522"}, 340f08c3bdfSopenharmony_ci {} 341f08c3bdfSopenharmony_ci } 342f08c3bdfSopenharmony_ci}; 343