^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 1) // SPDX-License-Identifier: GPL-2.0-or-later
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 2) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 3) * flexible mmap layout support
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 4) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 5) * Copyright 2003-2004 Red Hat Inc., Durham, North Carolina.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 6) * All Rights Reserved.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 7) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 8) * Started by Ingo Molnar <mingo@elte.hu>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 9) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 10)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 11) #include <linux/personality.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 12) #include <linux/mm.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 13) #include <linux/random.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 14) #include <linux/sched/signal.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 15) #include <linux/sched/mm.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 16) #include <linux/elf-randomize.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 17) #include <linux/security.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 18) #include <linux/mman.h>
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 19)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 20) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 21) * Top of mmap area (just below the process stack).
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 22) *
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 23) * Leave at least a ~128 MB hole.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 24) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 25) #define MIN_GAP (128*1024*1024)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 26) #define MAX_GAP (TASK_SIZE/6*5)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 27)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 28) static inline int mmap_is_legacy(struct rlimit *rlim_stack)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 29) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 30) if (current->personality & ADDR_COMPAT_LAYOUT)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 31) return 1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 32)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 33) if (rlim_stack->rlim_cur == RLIM_INFINITY)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 34) return 1;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 35)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 36) return sysctl_legacy_va_layout;
^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) unsigned long arch_mmap_rnd(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 40) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 41) unsigned long shift, rnd;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 42)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 43) shift = mmap_rnd_bits;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 44) #ifdef CONFIG_COMPAT
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 45) if (is_32bit_task())
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 46) shift = mmap_rnd_compat_bits;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 47) #endif
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 48) rnd = get_random_long() % (1ul << shift);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 49)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 50) return rnd << PAGE_SHIFT;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 51) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 52)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 53) static inline unsigned long stack_maxrandom_size(void)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 54) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 55) if (!(current->flags & PF_RANDOMIZE))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 56) return 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 57)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 58) /* 8MB for 32bit, 1GB for 64bit */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 59) if (is_32bit_task())
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 60) return (1<<23);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 61) else
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 62) return (1<<30);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 63) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 64)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 65) static inline unsigned long mmap_base(unsigned long rnd,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 66) struct rlimit *rlim_stack)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 67) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 68) unsigned long gap = rlim_stack->rlim_cur;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 69) unsigned long pad = stack_maxrandom_size() + stack_guard_gap;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 70)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 71) /* Values close to RLIM_INFINITY can overflow. */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 72) if (gap + pad > gap)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 73) gap += pad;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 74)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 75) if (gap < MIN_GAP)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 76) gap = MIN_GAP;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 77) else if (gap > MAX_GAP)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 78) gap = MAX_GAP;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 79)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 80) return PAGE_ALIGN(DEFAULT_MAP_WINDOW - gap - rnd);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 81) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 82)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 83) #ifdef CONFIG_PPC_RADIX_MMU
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 84) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 85) * Same function as generic code used only for radix, because we don't need to overload
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 86) * the generic one. But we will have to duplicate, because hash select
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 87) * HAVE_ARCH_UNMAPPED_AREA
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 88) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 89) static unsigned long
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 90) radix__arch_get_unmapped_area(struct file *filp, unsigned long addr,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 91) unsigned long len, unsigned long pgoff,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 92) unsigned long flags)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 93) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 94) struct mm_struct *mm = current->mm;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 95) struct vm_area_struct *vma;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 96) int fixed = (flags & MAP_FIXED);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 97) unsigned long high_limit;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 98) struct vm_unmapped_area_info info;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 99)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 100) high_limit = DEFAULT_MAP_WINDOW;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 101) if (addr >= high_limit || (fixed && (addr + len > high_limit)))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 102) high_limit = TASK_SIZE;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 103)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 104) if (len > high_limit)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 105) return -ENOMEM;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 106)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 107) if (fixed) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 108) if (addr > high_limit - len)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 109) return -ENOMEM;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 110) return addr;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 111) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 112)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 113) if (addr) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 114) addr = PAGE_ALIGN(addr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 115) vma = find_vma(mm, addr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 116) if (high_limit - len >= addr && addr >= mmap_min_addr &&
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 117) (!vma || addr + len <= vm_start_gap(vma)))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 118) return addr;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 119) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 120)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 121) info.flags = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 122) info.length = len;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 123) info.low_limit = mm->mmap_base;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 124) info.high_limit = high_limit;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 125) info.align_mask = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 126)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 127) return vm_unmapped_area(&info);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 128) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 129)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 130) static unsigned long
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 131) radix__arch_get_unmapped_area_topdown(struct file *filp,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 132) const unsigned long addr0,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 133) const unsigned long len,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 134) const unsigned long pgoff,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 135) const unsigned long flags)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 136) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 137) struct vm_area_struct *vma;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 138) struct mm_struct *mm = current->mm;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 139) unsigned long addr = addr0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 140) int fixed = (flags & MAP_FIXED);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 141) unsigned long high_limit;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 142) struct vm_unmapped_area_info info;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 143)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 144) high_limit = DEFAULT_MAP_WINDOW;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 145) if (addr >= high_limit || (fixed && (addr + len > high_limit)))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 146) high_limit = TASK_SIZE;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 147)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 148) if (len > high_limit)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 149) return -ENOMEM;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 150)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 151) if (fixed) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 152) if (addr > high_limit - len)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 153) return -ENOMEM;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 154) return addr;
^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) if (addr) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 158) addr = PAGE_ALIGN(addr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 159) vma = find_vma(mm, addr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 160) if (high_limit - len >= addr && addr >= mmap_min_addr &&
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 161) (!vma || addr + len <= vm_start_gap(vma)))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 162) return addr;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 163) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 164)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 165) info.flags = VM_UNMAPPED_AREA_TOPDOWN;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 166) info.length = len;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 167) info.low_limit = max(PAGE_SIZE, mmap_min_addr);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 168) info.high_limit = mm->mmap_base + (high_limit - DEFAULT_MAP_WINDOW);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 169) info.align_mask = 0;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 170)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 171) addr = vm_unmapped_area(&info);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 172) if (!(addr & ~PAGE_MASK))
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 173) return addr;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 174) VM_BUG_ON(addr != -ENOMEM);
^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) * A failed mmap() very likely causes application failure,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 178) * so fall back to the bottom-up function here. This scenario
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 179) * can happen with large stack limits and large mmap()
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 180) * allocations.
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 181) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 182) return radix__arch_get_unmapped_area(filp, addr0, len, pgoff, flags);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 183) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 184)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 185) static void radix__arch_pick_mmap_layout(struct mm_struct *mm,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 186) unsigned long random_factor,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 187) struct rlimit *rlim_stack)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 188) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 189) if (mmap_is_legacy(rlim_stack)) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 190) mm->mmap_base = TASK_UNMAPPED_BASE;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 191) mm->get_unmapped_area = radix__arch_get_unmapped_area;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 192) } else {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 193) mm->mmap_base = mmap_base(random_factor, rlim_stack);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 194) mm->get_unmapped_area = radix__arch_get_unmapped_area_topdown;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 195) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 196) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 197) #else
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 198) /* dummy */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 199) extern void radix__arch_pick_mmap_layout(struct mm_struct *mm,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 200) unsigned long random_factor,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 201) struct rlimit *rlim_stack);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 202) #endif
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 203) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 204) * This function, called very early during the creation of a new
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 205) * process VM image, sets up which VM layout function to use:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 206) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 207) void arch_pick_mmap_layout(struct mm_struct *mm, struct rlimit *rlim_stack)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 208) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 209) unsigned long random_factor = 0UL;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 210)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 211) if (current->flags & PF_RANDOMIZE)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 212) random_factor = arch_mmap_rnd();
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 213)
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 214) if (radix_enabled())
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 215) return radix__arch_pick_mmap_layout(mm, random_factor,
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 216) rlim_stack);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 217) /*
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 218) * Fall back to the standard layout if the personality
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 219) * bit is set, or if the expected stack growth is unlimited:
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 220) */
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 221) if (mmap_is_legacy(rlim_stack)) {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 222) mm->mmap_base = TASK_UNMAPPED_BASE;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 223) mm->get_unmapped_area = arch_get_unmapped_area;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 224) } else {
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 225) mm->mmap_base = mmap_base(random_factor, rlim_stack);
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 226) mm->get_unmapped_area = arch_get_unmapped_area_topdown;
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 227) }
^8f3ce5b39 (kx 2023-10-28 12:00:06 +0300 228) }