^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 1) // SPDX-License-Identifier: GPL-2.0
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 2) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 3) * This is for all the tests related to validating kernel memory
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 4) * permissions: non-executable regions, non-writable regions, and
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 5) * even non-readable regions.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 6) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 7) #include "lkdtm.h"
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 8) #include <linux/slab.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 9) #include <linux/vmalloc.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 10) #include <linux/mman.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 11) #include <linux/uaccess.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 12) #include <asm/cacheflush.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 13)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 14) /* Whether or not to fill the target memory area with do_nothing(). */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 15) #define CODE_WRITE true
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 16) #define CODE_AS_IS false
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 17)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 18) /* How many bytes to copy to be sure we've copied enough of do_nothing(). */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 19) #define EXEC_SIZE 64
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 20)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 21) /* This is non-const, so it will end up in the .data section. */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 22) static u8 data_area[EXEC_SIZE];
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 23)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 24) /* This is cost, so it will end up in the .rodata section. */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 25) static const unsigned long rodata = 0xAA55AA55;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 26)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 27) /* This is marked __ro_after_init, so it should ultimately be .rodata. */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 28) static unsigned long ro_after_init __ro_after_init = 0x55AA5500;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 29)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 30) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 31) * This just returns to the caller. It is designed to be copied into
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 32) * non-executable memory regions.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 33) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 34) static void do_nothing(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 35) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 36) return;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 37) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 38)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 39) /* Must immediately follow do_nothing for size calculuations to work out. */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 40) static void do_overwritten(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 41) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 42) pr_info("do_overwritten wasn't overwritten!\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 43) return;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 44) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 45)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 46) static noinline void execute_location(void *dst, bool write)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 47) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 48) void (*func)(void) = dst;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 49)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 50) pr_info("attempting ok execution at %px\n", do_nothing);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 51) do_nothing();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 52)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 53) if (write == CODE_WRITE) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 54) memcpy(dst, do_nothing, EXEC_SIZE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 55) flush_icache_range((unsigned long)dst,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 56) (unsigned long)dst + EXEC_SIZE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 57) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 58) pr_info("attempting bad execution at %px\n", func);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 59) func();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 60) pr_err("FAIL: func returned\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 61) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 62)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 63) static void execute_user_location(void *dst)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 64) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 65) int copied;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 66)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 67) /* Intentionally crossing kernel/user memory boundary. */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 68) void (*func)(void) = dst;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 69)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 70) pr_info("attempting ok execution at %px\n", do_nothing);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 71) do_nothing();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 72)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 73) copied = access_process_vm(current, (unsigned long)dst, do_nothing,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 74) EXEC_SIZE, FOLL_WRITE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 75) if (copied < EXEC_SIZE)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 76) return;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 77) pr_info("attempting bad execution at %px\n", func);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 78) func();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 79) pr_err("FAIL: func returned\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 80) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 81)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 82) void lkdtm_WRITE_RO(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 83) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 84) /* Explicitly cast away "const" for the test and make volatile. */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 85) volatile unsigned long *ptr = (unsigned long *)&rodata;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 86)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 87) pr_info("attempting bad rodata write at %px\n", ptr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 88) *ptr ^= 0xabcd1234;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 89) pr_err("FAIL: survived bad write\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 90) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 91)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 92) void lkdtm_WRITE_RO_AFTER_INIT(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 93) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 94) volatile unsigned long *ptr = &ro_after_init;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 95)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 96) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 97) * Verify we were written to during init. Since an Oops
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 98) * is considered a "success", a failure is to just skip the
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 99) * real test.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101) if ((*ptr & 0xAA) != 0xAA) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102) pr_info("%p was NOT written during init!?\n", ptr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103) return;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106) pr_info("attempting bad ro_after_init write at %px\n", ptr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107) *ptr ^= 0xabcd1234;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108) pr_err("FAIL: survived bad write\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111) void lkdtm_WRITE_KERN(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113) size_t size;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114) volatile unsigned char *ptr;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116) size = (unsigned long)do_overwritten - (unsigned long)do_nothing;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117) ptr = (unsigned char *)do_overwritten;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119) pr_info("attempting bad %zu byte write at %px\n", size, ptr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120) memcpy((void *)ptr, (unsigned char *)do_nothing, size);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121) flush_icache_range((unsigned long)ptr, (unsigned long)(ptr + size));
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) pr_err("FAIL: survived bad write\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124) do_overwritten();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 127) void lkdtm_EXEC_DATA(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 128) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 129) execute_location(data_area, CODE_WRITE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 130) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 131)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 132) void lkdtm_EXEC_STACK(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 133) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 134) u8 stack_area[EXEC_SIZE];
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 135) execute_location(stack_area, CODE_WRITE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 136) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 137)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 138) void lkdtm_EXEC_KMALLOC(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 139) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 140) u32 *kmalloc_area = kmalloc(EXEC_SIZE, GFP_KERNEL);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 141) execute_location(kmalloc_area, CODE_WRITE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 142) kfree(kmalloc_area);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 143) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 144)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 145) void lkdtm_EXEC_VMALLOC(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 146) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 147) u32 *vmalloc_area = vmalloc(EXEC_SIZE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 148) execute_location(vmalloc_area, CODE_WRITE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 149) vfree(vmalloc_area);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 150) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 151)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 152) void lkdtm_EXEC_RODATA(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 153) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 154) execute_location(lkdtm_rodata_do_nothing, CODE_AS_IS);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 155) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 156)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 157) void lkdtm_EXEC_USERSPACE(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 158) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 159) unsigned long user_addr;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 160)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 161) user_addr = vm_mmap(NULL, 0, PAGE_SIZE,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 162) PROT_READ | PROT_WRITE | PROT_EXEC,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 163) MAP_ANONYMOUS | MAP_PRIVATE, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 164) if (user_addr >= TASK_SIZE) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 165) pr_warn("Failed to allocate user memory\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 166) return;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 167) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 168) execute_user_location((void *)user_addr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 169) vm_munmap(user_addr, PAGE_SIZE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 170) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 171)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 172) void lkdtm_EXEC_NULL(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 173) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 174) execute_location(NULL, CODE_AS_IS);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 175) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 176)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 177) void lkdtm_ACCESS_USERSPACE(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 178) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 179) unsigned long user_addr, tmp = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 180) unsigned long *ptr;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 181)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 182) user_addr = vm_mmap(NULL, 0, PAGE_SIZE,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 183) PROT_READ | PROT_WRITE | PROT_EXEC,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 184) MAP_ANONYMOUS | MAP_PRIVATE, 0);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 185) if (user_addr >= TASK_SIZE) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 186) pr_warn("Failed to allocate user memory\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 187) return;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 188) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 189)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 190) if (copy_to_user((void __user *)user_addr, &tmp, sizeof(tmp))) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 191) pr_warn("copy_to_user failed\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 192) vm_munmap(user_addr, PAGE_SIZE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 193) return;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 194) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 195)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 196) ptr = (unsigned long *)user_addr;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 197)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 198) pr_info("attempting bad read at %px\n", ptr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 199) tmp = *ptr;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 200) tmp += 0xc0dec0de;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 201) pr_err("FAIL: survived bad read\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 202)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 203) pr_info("attempting bad write at %px\n", ptr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 204) *ptr = tmp;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 205) pr_err("FAIL: survived bad write\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 206)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 207) vm_munmap(user_addr, PAGE_SIZE);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 208) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 209)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 210) void lkdtm_ACCESS_NULL(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 211) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 212) unsigned long tmp;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 213) volatile unsigned long *ptr = (unsigned long *)NULL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 214)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 215) pr_info("attempting bad read at %px\n", ptr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 216) tmp = *ptr;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 217) tmp += 0xc0dec0de;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 218) pr_err("FAIL: survived bad read\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 219)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 220) pr_info("attempting bad write at %px\n", ptr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 221) *ptr = tmp;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 222) pr_err("FAIL: survived bad write\n");
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 223) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 224)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 225) void __init lkdtm_perms_init(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 226) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 227) /* Make sure we can write to __ro_after_init values during __init */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 228) ro_after_init |= 0xAA;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 229) }