kasan: Implementing global variable out of bounds detection

Extracting global variable information using scripts:
kasan_global.py:
1. Extract the global variable information provided by the -- param asan globals=1 option
2. Generate shadow regions for global variable out of bounds detection
Makefile:
1. Implement multiple links, embed the shadow area into the program, and call it by the Kasan module

Signed-off-by: W-M-R <mike_0528@163.com>
This commit is contained in:
W-M-R 2024-04-07 18:20:17 +08:00 committed by Xiang Xiao
parent 2fd73bd82f
commit 0ede3fc377
16 changed files with 566 additions and 48 deletions

View File

@ -180,13 +180,27 @@ $(KBIN): $(OBJS)
board$(DELIM)libboard$(LIBEXT):
$(Q) $(MAKE) -C board libboard$(LIBEXT) EXTRAFLAGS="$(EXTRAFLAGS)"
define LINK_ALLSYMS
# When multiple linking, these two additional linking objects will be included
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)
EXTRA_LIBS += kasan_globals$(OBJEXT)
endif
ifeq ($(CONFIG_ALLSYMS),y)
EXTRA_LIBS += allsyms$(OBJEXT)
endif
define LINK_ALLSYMS_KASAN
$(if $(CONFIG_ALLSYMS),
$(Q) $(TOPDIR)/tools/mkallsyms.py $(NUTTX) allsyms.tmp --orderbyname $(CONFIG_SYMTAB_ORDEREDBYNAME)
$(Q) $(call COMPILE, allsyms.tmp, allsyms$(OBJEXT), -x c)
$(Q) $(call DELFILE, allsyms.tmp))
$(if $(CONFIG_MM_KASAN_GLOBAL),
$(Q) $(TOPDIR)/tools/kasan_global.py -e $(NUTTX) -o kasan_globals.tmp
$(Q) $(call COMPILE, kasan_globals.tmp, kasan_globals$(OBJEXT) -fno-sanitize=kernel-address, -x c)
$(Q) $(call DELFILE, kasan_globals.tmp))
$(Q) $(LD) --entry=__start $(LDFLAGS) $(LIBPATHS) $(EXTRA_LIBPATHS) \
-o $(NUTTX) $(HEAD_OBJ) allsyms$(OBJEXT) $(EXTRA_OBJS) \
-o $(NUTTX) $(HEAD_OBJ) $(EXTRA_OBJS) \
$(LDSTARTGROUP) $(LDLIBS) $(EXTRA_LIBS) $(LDENDGROUP)
$(Q) $(call DELFILE, allsyms.tmp allsyms$(OBJEXT))
endef
$(addsuffix .tmp,$(ARCHSCRIPT)): $(ARCHSCRIPT)
@ -194,17 +208,19 @@ $(addsuffix .tmp,$(ARCHSCRIPT)): $(ARCHSCRIPT)
nuttx$(EXEEXT): $(HEAD_OBJ) board$(DELIM)libboard$(LIBEXT) $(addsuffix .tmp,$(ARCHSCRIPT))
$(Q) echo "LD: nuttx"
ifneq ($(CONFIG_ALLSYMS),y)
ifeq ($(CONFIG_ALLSYMS)$(CONFIG_MM_KASAN_GLOBAL),)
$(Q) $(LD) --entry=__start $(LDFLAGS) $(LIBPATHS) $(EXTRA_LIBPATHS) \
-o $(NUTTX) $(HEAD_OBJ) $(EXTRA_OBJS) \
$(LDSTARTGROUP) $(LDLIBS) $(EXTRA_LIBS) $(LDENDGROUP)
else
$(Q) # Link and generate default table
$(Q) $(if $(wildcard $(shell echo $(NUTTX))),,$(call LINK_ALLSYMS,$^))
$(Q) # Extract all symbols
$(Q) $(call LINK_ALLSYMS, $^)
$(Q) # Extract again since the table offset may changed
$(Q) $(call LINK_ALLSYMS, $^)
$(Q) $(call LINK_ALLSYMS_KASAN)
$(Q) $(call LINK_ALLSYMS_KASAN)
$(Q) $(call LINK_ALLSYMS_KASAN)
$(Q) $(call LINK_ALLSYMS_KASAN)
endif
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)
$(Q) $(OBJCOPY) -R .kasan.global $(NUTTX)
$(Q) $(OBJCOPY) -R .kasan.unused $(NUTTX)
endif
ifneq ($(CONFIG_WINDOWS_NATIVE),y)
$(Q) $(NM) $(NUTTX) | \

View File

@ -78,6 +78,10 @@ ifeq ($(CONFIG_MM_KASAN_ALL),y)
ARCHOPTIMIZATION += -fsanitize=kernel-address
endif
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)
ARCHOPTIMIZATION += --param asan-globals=1
endif
ifeq ($(CONFIG_MM_KASAN_DISABLE_READS_CHECK),y)
ARCHOPTIMIZATION += --param asan-instrument-reads=0
endif

View File

@ -156,13 +156,27 @@ $(KBIN): $(OBJS)
board$(DELIM)libboard$(LIBEXT):
$(Q) $(MAKE) -C board libboard$(LIBEXT) EXTRAFLAGS="$(EXTRAFLAGS)"
define LINK_ALLSYMS
# When multiple linking, these two additional linking objects will be included
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)
EXTRA_LIBS += kasan_globals$(OBJEXT)
endif
ifeq ($(CONFIG_ALLSYMS),y)
EXTRA_LIBS += allsyms$(OBJEXT)
endif
define LINK_ALLSYMS_KASAN
$(if $(CONFIG_ALLSYMS),
$(Q) $(TOPDIR)/tools/mkallsyms.py $(NUTTX) allsyms.tmp --orderbyname $(CONFIG_SYMTAB_ORDEREDBYNAME)
$(Q) $(call COMPILE, allsyms.tmp, allsyms$(OBJEXT), -x c)
$(Q) $(call DELFILE, allsyms.tmp))
$(if $(CONFIG_MM_KASAN_GLOBAL),
$(Q) $(TOPDIR)/tools/kasan_global.py -e $(NUTTX) -o kasan_globals.tmp
$(Q) $(call COMPILE, kasan_globals.tmp, kasan_globals$(OBJEXT) -fno-sanitize=kernel-address, -x c)
$(Q) $(call DELFILE, kasan_globals.tmp))
$(Q) $(LD) --entry=__start $(LDFLAGS) $(LIBPATHS) $(EXTRA_LIBPATHS) \
-o $(NUTTX) $(HEAD_OBJ) allsyms$(OBJEXT) $(EXTRA_OBJS) \
-o $(NUTTX) $(HEAD_OBJ) $(EXTRA_OBJS) \
$(LDSTARTGROUP) $(LDLIBS) $(EXTRA_LIBS) $(LDENDGROUP)
$(Q) $(call DELFILE, allsyms.tmp allsyms$(OBJEXT))
endef
$(addsuffix .tmp,$(ARCHSCRIPT)): $(ARCHSCRIPT)
@ -170,17 +184,19 @@ $(addsuffix .tmp,$(ARCHSCRIPT)): $(ARCHSCRIPT)
nuttx$(EXEEXT): $(HEAD_OBJ) board$(DELIM)libboard$(LIBEXT) $(addsuffix .tmp,$(ARCHSCRIPT))
$(Q) echo "LD: nuttx"
ifneq ($(CONFIG_ALLSYMS),y)
ifeq ($(CONFIG_ALLSYMS)$(CONFIG_MM_KASAN_GLOBAL),)
$(Q) $(LD) --entry=__start $(LDFLAGS) $(LIBPATHS) $(EXTRA_LIBPATHS) \
-o $(NUTTX) $(HEAD_OBJ) $(EXTRA_OBJS) \
$(LDSTARTGROUP) $(LDLIBS) $(EXTRA_LIBS) $(LDENDGROUP)
else
$(Q) # Link and generate default table
$(Q) $(if $(wildcard $(shell echo $(NUTTX))),,$(call LINK_ALLSYMS,$^))
$(Q) # Extract all symbols
$(Q) $(call LINK_ALLSYMS, $^)
$(Q) # Extract again since the table offset may changed
$(Q) $(call LINK_ALLSYMS, $^)
$(Q) $(call LINK_ALLSYMS_KASAN)
$(Q) $(call LINK_ALLSYMS_KASAN)
$(Q) $(call LINK_ALLSYMS_KASAN)
$(Q) $(call LINK_ALLSYMS_KASAN)
endif
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)
$(Q) $(OBJCOPY) -R .kasan.global $(NUTTX)
$(Q) $(OBJCOPY) -R .kasan.unused $(NUTTX)
endif
ifneq ($(CONFIG_WINDOWS_NATIVE),y)
$(Q) $(NM) $(NUTTX) | \

View File

@ -84,6 +84,10 @@ ifeq ($(CONFIG_MM_KASAN_ALL),y)
ARCHOPTIMIZATION += -fsanitize=kernel-address
endif
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)
ARCHOPTIMIZATION += --param asan-globals=1
endif
# Instrumentation options
ifeq ($(CONFIG_ARCH_INSTRUMENT_ALL),y)

View File

@ -162,13 +162,27 @@ $(KBIN): $(OBJS)
board/libboard$(LIBEXT):
$(Q) $(MAKE) -C board libboard$(LIBEXT) EXTRAFLAGS="$(EXTRAFLAGS)"
define LINK_ALLSYMS
# When multiple linking, these two additional linking objects will be included
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)
EXTRA_LIBS += kasan_globals$(OBJEXT)
endif
ifeq ($(CONFIG_ALLSYMS),y)
EXTRA_LIBS += allsyms$(OBJEXT)
endif
define LINK_ALLSYMS_KASAN
$(if $(CONFIG_ALLSYMS),
$(Q) $(TOPDIR)/tools/mkallsyms.py $(NUTTX) allsyms.tmp --orderbyname $(CONFIG_SYMTAB_ORDEREDBYNAME)
$(Q) $(call COMPILE, allsyms.tmp, allsyms$(OBJEXT), -x c)
$(Q) $(call DELFILE, allsyms.tmp))
$(if $(CONFIG_MM_KASAN_GLOBAL),
$(Q) $(TOPDIR)/tools/kasan_global.py -e $(NUTTX) -o kasan_globals.tmp
$(Q) $(call COMPILE, kasan_globals.tmp, kasan_globals$(OBJEXT) -fno-sanitize=kernel-address, -x c)
$(Q) $(call DELFILE, kasan_globals.tmp))
$(Q) $(LD) --entry=__start $(LDFLAGS) $(LIBPATHS) $(EXTRA_LIBPATHS) \
-o $(NUTTX) $(HEAD_OBJ) allsyms$(OBJEXT) $(EXTRA_OBJS) \
-o $(NUTTX) $(HEAD_OBJ) $(EXTRA_OBJS) \
$(LDSTARTGROUP) $(LDLIBS) $(EXTRA_LIBS) $(LDENDGROUP)
$(Q) $(call DELFILE, allsyms.tmp allsyms$(OBJEXT))
endef
$(addsuffix .tmp,$(ARCHSCRIPT)): $(ARCHSCRIPT)
@ -176,17 +190,19 @@ $(addsuffix .tmp,$(ARCHSCRIPT)): $(ARCHSCRIPT)
nuttx$(EXEEXT): $(HEAD_OBJ) board/libboard$(LIBEXT) $(addsuffix .tmp,$(ARCHSCRIPT))
$(Q) echo "LD: nuttx"
ifneq ($(CONFIG_ALLSYMS),y)
ifeq ($(CONFIG_ALLSYMS)$(CONFIG_MM_KASAN_GLOBAL),)
$(Q) $(LD) --entry=__start $(LDFLAGS) $(LIBPATHS) $(EXTRA_LIBPATHS) \
-o $(NUTTX) $(HEAD_OBJ) $(EXTRA_OBJS) \
$(LDSTARTGROUP) $(LDLIBS) $(EXTRA_LIBS) $(LDENDGROUP)
else
$(Q) # Link and generate default table
$(Q) $(if $(wildcard $(shell echo $(NUTTX))),,$(call LINK_ALLSYMS,$^))
$(Q) # Extract all symbols
$(Q) $(call LINK_ALLSYMS, $^)
$(Q) # Extract again since the table offset may changed
$(Q) $(call LINK_ALLSYMS, $^)
$(Q) $(call LINK_ALLSYMS_KASAN)
$(Q) $(call LINK_ALLSYMS_KASAN)
$(Q) $(call LINK_ALLSYMS_KASAN)
$(Q) $(call LINK_ALLSYMS_KASAN)
endif
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)
$(Q) $(OBJCOPY) -R .kasan.global $(NUTTX)
$(Q) $(OBJCOPY) -R .kasan.unused $(NUTTX)
endif
ifneq ($(CONFIG_WINDOWS_NATIVE),y)
$(Q) $(NM) $(NUTTX) | \

View File

@ -266,6 +266,10 @@ ifeq ($(CONFIG_MM_KASAN_ALL),y)
ARCHOPTIMIZATION += -fsanitize=kernel-address
endif
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)
ARCHOPTIMIZATION += --param asan-globals=1
endif
ifeq ($(CONFIG_MM_KASAN_DISABLE_READS_CHECK),y)
ARCHOPTIMIZATION += --param asan-instrument-reads=0
endif

View File

@ -351,17 +351,32 @@ board/libboard$(LIBEXT):
nuttx-names.dat: nuttx-names.in
$(call PREPROCESS, nuttx-names.in, nuttx-names.dat)
define LINK_ALLSYMS
$(if $(CONFIG_HOST_MACOS), \
$(Q) $(TOPDIR)/tools/mkallsyms.sh noconst $(NUTTX) $(CROSSDEV) > allsyms.tmp, \
$(Q) $(TOPDIR)/tools/mkallsyms.py $(NUTTX) allsyms.tmp --orderbyname $(CONFIG_SYMTAB_ORDEREDBYNAME))
$(Q) $(call COMPILE, allsyms.tmp, allsyms$(OBJEXT), -x c)
# When multiple linking, these two additional linking objects will be included
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)
EXTRALD_OBJ += kasan_globals$(OBJEXT)
endif
ifeq ($(CONFIG_ALLSYMS),y)
EXTRALD_OBJ += allsyms$(OBJEXT)
endif
define LINK_ALLSYMS_KASAN
$(if $(CONFIG_ALLSYMS), \
$(if $(CONFIG_HOST_MACOS), \
$(Q) $(TOPDIR)/tools/mkallsyms.sh noconst $(NUTTX) $(CROSSDEV) > allsyms.tmp, \
$(Q) $(TOPDIR)/tools/mkallsyms.py $(NUTTX) allsyms.tmp --orderbyname $(CONFIG_SYMTAB_ORDEREDBYNAME)))
$(if $(CONFIG_ALLSYMS), \
$(Q) $(call COMPILE, allsyms.tmp, allsyms$(OBJEXT), -x c)
$(Q) $(call DELFILE, allsyms.tmp))
$(if $(CONFIG_MM_KASAN_GLOBAL),
$(Q) $(TOPDIR)/tools/kasan_global.py -e $(NUTTX) -o kasan_globals.tmp
$(Q) $(call COMPILE, kasan_globals.tmp, kasan_globals$(OBJEXT) -fno-sanitize=kernel-address, -x c)
$(Q) $(call DELFILE, kasan_globals.tmp))
$(if $(CONFIG_HAVE_CXX),\
$(Q) "$(CXX)" $(CFLAGS) $(LDFLAGS) -o $(NUTTX) \
$(HEADOBJ) nuttx.rel $(HOSTOBJS) $(STDLIBS) allsyms$(OBJEXT),\
$(HEADOBJ) nuttx.rel $(HOSTOBJS) $(STDLIBS) $(EXTRALD_OBJ),\
$(Q) "$(CC)" $(CFLAGS) $(LDFLAGS) -o $(NUTTX) \
$(HEADOBJ) nuttx.rel $(HOSTOBJS) $(STDLIBS) allsyms$(OBJEXT))
$(Q) $(call DELFILE, allsyms.tmp allsyms$(OBJEXT))
$(HEADOBJ) nuttx.rel $(HOSTOBJS) $(STDLIBS) $(EXTRALD_OBJ))
endef
# Note: Use objcopy for Linux because for some reasons visibility=hidden
@ -382,17 +397,20 @@ ifneq ($(CONFIG_HOST_MACOS),y)
-e 's/__fini_array_start/_sfini/g' -e 's/__fini_array_end/_efini/g' >nuttx.ld
$(Q) echo "__init_array_start = .; __init_array_end = .; __fini_array_start = .; __fini_array_end = .;" >>nuttx.ld
endif
ifneq ($(CONFIG_ALLSYMS),y)
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)
$(Q) sed -i 's/\s*\.interp\s*:\s*{\s*\*(\.interp)\s*}/ \
.kasan.global : {KEEP(*(.data..LASAN0)) KEEP (*(.data.rel.local..LASAN0)) }\n \
.interp : {*(.interp)}/g' nuttx.ld
endif
ifeq ($(CONFIG_ALLSYMS)$(CONFIG_MM_KASAN_GLOBAL),)
$(if $(CONFIG_HAVE_CXX),\
$(Q) "$(CXX)" $(CFLAGS) $(LDFLAGS) -o $(TOPDIR)/$@ $(HEADOBJ) nuttx.rel $(HOSTOBJS) $(STDLIBS),\
$(Q) "$(CC)" $(CFLAGS) $(LDFLAGS) -o $(TOPDIR)/$@ $(HEADOBJ) nuttx.rel $(HOSTOBJS) $(STDLIBS))
else
$(Q) # Link and generate default table
$(Q) $(if $(wildcard $(shell echo $(NUTTX))),,$(call LINK_ALLSYMS, $@))
$(Q) # Extract all symbols
$(Q) $(call LINK_ALLSYMS, $^)
$(Q) # Extract again since the table offset may changed
$(Q) $(call LINK_ALLSYMS, $^)
$(Q) $(call LINK_ALLSYMS_KASAN)
$(Q) $(call LINK_ALLSYMS_KASAN)
$(Q) $(call LINK_ALLSYMS_KASAN)
$(Q) $(call LINK_ALLSYMS_KASAN)
endif
$(Q) $(NM) $(TOPDIR)/$@ | \
grep -v '\(compiled\)\|\(\.o$$\)\|\( [aUw] \)\|\(\.\.ng$$\)\|\(LASH[RL]DI\)' | \

View File

@ -26,6 +26,20 @@ PHDRS
SECTIONS
{
. = 0x40101000;
/* where the global variable out-of-bounds detection information located */
#ifdef CONFIG_MM_KASAN_GLOBAL
.kasan.unused :
{
*(.data..LASANLOC*)
} > ROM
.kasan.global :
{
KEEP (*(.data..LASAN0))
KEEP (*(.data.rel.local..LASAN0))
} > ROM
#endif
.text : {
_stext = .; /* Text section */
*(.vectors)

View File

@ -18,6 +18,8 @@
*
****************************************************************************/
#include <nuttx/config.h>
OUTPUT_ARCH(aarch64)
ENTRY(__start)
@ -30,6 +32,20 @@ PHDRS
SECTIONS
{
. = 0x40280000; /* uboot load address */
/* where the global variable out-of-bounds detection information located */
#ifdef CONFIG_MM_KASAN_GLOBAL
.kasan.unused :
{
*(.data..LASANLOC*)
}
.kasan.global :
{
KEEP (*(.data..LASAN0))
KEEP (*(.data.rel.local..LASAN0))
}
#endif
_start = .;
.text : {
_stext = .; /* Text section */

View File

@ -44,6 +44,19 @@ SECTIONS
{
. = 0x80000000;
/* where the global variable out-of-bounds detection information located */
#ifdef CONFIG_MM_KASAN_GLOBAL
.kasan.unused :
{
*(.data..LASANLOC*)
}
.kasan.global :
{
KEEP (*(.data..LASAN0))
KEEP (*(.data.rel.local..LASAN0))
}
#endif
.text :
{
_stext = . ;

View File

@ -44,6 +44,19 @@ SECTIONS
{
. = 0x80000000;
/* where the global variable out-of-bounds detection information located */
#ifdef CONFIG_MM_KASAN_GLOBAL
.kasan.unused :
{
*(.data..LASANLOC*)
}
.kasan.global :
{
KEEP (*(.data..LASAN0))
KEEP (*(.data.rel.local..LASAN0))
}
#endif
.text :
{
_stext = . ;

View File

@ -22,6 +22,19 @@ SECTIONS
{
. = 0x80000000;
/* where the global variable out-of-bounds detection information located */
#ifdef CONFIG_MM_KASAN_GLOBAL
.kasan.unused :
{
*(.data..LASANLOC*)
}
.kasan.global :
{
KEEP (*(.data..LASAN0))
KEEP (*(.data.rel.local..LASAN0))
}
#endif
.text :
{
_stext = . ;

View File

@ -83,6 +83,10 @@ else ifeq ($(CONFIG_MM_KASAN_ALL),y)
ARCHOPTIMIZATION += -fsanitize=kernel-address
endif
ifeq ($(CONFIG_MM_KASAN_GLOBAL),y)
ARCHOPTIMIZATION += --param asan-globals=1
endif
ifeq ($(CONFIG_SIM_UBSAN),y)
ARCHOPTIMIZATION += -fsanitize=undefined
else

View File

@ -281,6 +281,21 @@ config MM_KASAN_DISABLE_WRITES_CHECK
---help---
This option disable kasan writes check.
config MM_KASAN_GLOBAL
bool "Enable global data check"
depends on MM_KASAN
default n
---help---
This option enables KASan global data check.
It's used to extract segments in the linker script.
Two new segments need to be created, one being
".kasan.unused: { *(.data..LASANLOC*) }",
used to eliminate excess data generated.
One is ".kasan.global:{
KEEP ( *(. data.. LASAN0))
KEEP ( *(. data. rel. local.. LASAN0))
}", used to extract data generated by the compiler
config MM_UBSAN
bool "Undefined Behavior Sanitizer"
default n

View File

@ -50,6 +50,16 @@
#define KASAN_REGION_SIZE(size) \
(sizeof(struct kasan_region_s) + KASAN_SHADOW_SIZE(size))
#ifdef CONFIG_MM_KASAN_GLOBAL
# define KASAN_GLOBAL_SHADOW_SCALE (32)
# define KASAN_GLOBAL_NEXT_REGION(region) \
(FAR struct kasan_region_s *) \
((FAR char *)region->shadow + (size_t)region->next)
#endif
#define KASAN_INIT_VALUE 0xDEADCAFE
/****************************************************************************
@ -59,9 +69,9 @@
struct kasan_region_s
{
FAR struct kasan_region_s *next;
uintptr_t begin;
uintptr_t end;
uintptr_t shadow[1];
uintptr_t begin;
uintptr_t end;
uintptr_t shadow[1];
};
/****************************************************************************
@ -72,6 +82,14 @@ static spinlock_t g_lock;
static FAR struct kasan_region_s *g_region;
static uint32_t g_region_init;
/****************************************************************************
* Public Data
****************************************************************************/
#ifdef CONFIG_MM_KASAN_GLOBAL
extern const unsigned char g_globals_region[];
#endif
/****************************************************************************
* Private Functions
****************************************************************************/
@ -99,6 +117,22 @@ static FAR uintptr_t *kasan_mem_to_shadow(FAR const void *ptr, size_t size,
}
}
#ifdef CONFIG_MM_KASAN_GLOBAL
for (region = (FAR struct kasan_region_s *)g_globals_region;
region->next;
region = KASAN_GLOBAL_NEXT_REGION(region))
{
if (addr >= region->begin && addr < region->end)
{
DEBUGASSERT(addr + size <= region->end);
addr -= region->begin;
addr /= KASAN_GLOBAL_SHADOW_SCALE;
*bit = addr % KASAN_BITS_PER_WORD;
return &region->shadow[addr / KASAN_BITS_PER_WORD];
}
}
#endif
return NULL;
}
@ -314,3 +348,15 @@ DEFINE_ASAN_LOAD_STORE(2)
DEFINE_ASAN_LOAD_STORE(4)
DEFINE_ASAN_LOAD_STORE(8)
DEFINE_ASAN_LOAD_STORE(16)
#ifdef CONFIG_MM_KASAN_GLOBAL
void __asan_register_globals(void *ptr, ssize_t size)
{
/* Shut up compiler complaints */
}
void __asan_unregister_globals(void *ptr, ssize_t size)
{
/* Shut up compiler complaints */
}
#endif

306
tools/kasan_global.py Executable file
View File

@ -0,0 +1,306 @@
#!/usr/bin/env python3
############################################################################
# tools/kasan_global.py
#
# Licensed to the Apache Software Foundation (ASF) under one or more
# contributor license agreements. See the NOTICE file distributed with
# this work for additional information regarding copyright ownership. The
# ASF licenses this file to you under the Apache License, Version 2.0 (the
# "License"); you may not use this file except in compliance with the
# License. You may obtain a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
############################################################################
import argparse
import os
from construct import Int32ul, Int64ul, Struct
from elftools.elf.elffile import ELFFile
debug = False
# N-byte aligned shadow area 1 bit
KASAN_GLOBAL_ALIGN = 32
# The maximum gap that two data segments can tolerate
KASAN_MAX_DATA_GAP = 1 << 16
# The section where the global variable descriptor
# generated by the compiler is located
KASAN_SECTION = ".kasan.global"
# The structure of parsing strings required for 32-bit and 64 bit
KASAN_GLOBAL_STRUCT_32 = Struct(
"beg" / Int32ul,
"size" / Int32ul,
"size_with_redzone" / Int32ul,
"name" / Int32ul,
"module_name" / Int32ul,
"has_dynamic_init" / Int32ul,
"location" / Int32ul,
"odr_indicator" / Int32ul,
)
KASAN_GLOBAL_STRUCT_64 = Struct(
"beg" / Int64ul,
"size" / Int64ul,
"size_with_redzone" / Int64ul,
"name" / Int64ul,
"module_name" / Int64ul,
"has_dynamic_init" / Int64ul,
"location" / Int64ul,
"odr_indicator" / Int64ul,
)
# Global configuration information
class Config:
def __init__(self, outpath, elf, ldscript):
self.outpath = outpath
self.elf = elf
self.ldscript = ldscript
if self.elf is None or os.path.exists(self.elf) is False:
self.elf = None
return
with open(self.elf, "rb") as file:
elf_file = ELFFile(file)
elf_header = elf_file.header
# Obtain bit width
bitness = elf_header["e_ident"]["EI_CLASS"]
if bitness == "ELFCLASS32":
self.bitwides = 32
elif bitness == "ELFCLASS64":
self.bitwides = 64
# Big and little end
endianness = elf_header["e_ident"]["EI_DATA"]
if endianness == "ELFDATA2LSB":
self.endian = "little"
elif endianness == "ELFDATA2MSB":
self.endian = "big"
class KASanRegion:
def __init__(self, start, end) -> None:
self.start = start
self.end = end
self.size = int((end - start) // KASAN_GLOBAL_ALIGN // 8) + 1
self.shadow = bytearray(b"\x00" * self.size)
def mark_bit(self, index, nbits):
self.shadow[index] |= 1 << nbits
def poison(self, dict):
dict_size = dict["size"]
if dict_size % 32:
dict_size = int((dict_size + 31) // 32) * 32
distance = (dict["beg"] + dict_size - self.start) // KASAN_GLOBAL_ALIGN
index = int(distance // 8)
nbits = distance % 8
if debug:
print(
"regin: %08x addr: %08x size: %d bits: %d || poison index: %d nbits: %d"
% (
self.start,
dict["beg"],
dict["size"],
int(dict["size_with_redzone"] // KASAN_GLOBAL_ALIGN),
index,
nbits,
)
)
# Using 32bytes: with 1bit alignment,
# only one bit of inaccessible area exists for each pair of global variables.
self.mark_bit(index, nbits)
class KASanInfo:
def __init__(self) -> None:
# Record the starting position of the merged data block
self.data_sections = []
# Record the kasan region corresponding to each data block
self.regions: list[KASanRegion] = []
def merge_ranges(self, dict):
if len(self.data_sections) == 0:
self.data_sections.append(
[dict["beg"], dict["beg"] + dict["size_with_redzone"]]
)
return
start = dict["beg"]
end = dict["beg"] + dict["size_with_redzone"]
if start - self.data_sections[-1][1] <= KASAN_MAX_DATA_GAP:
self.data_sections[-1][1] = end
else:
self.data_sections.append([start, end])
def create_region(self):
for i in self.data_sections:
start = i[0]
end = i[1]
if debug:
print("KAsan Shadow Block: %08x ---- %08x" % (start, end))
self.regions.append(KASanRegion(start, end))
def mark_shadow(self, dict):
for i in self.regions:
start = i.start
end = i.end
if start <= dict["beg"] and dict["beg"] <= end:
i.poison(dict)
break
# Global variable descriptor
def get_global_dict(GLOBAL_STRUCT: Struct, bytes: bytes):
dict = GLOBAL_STRUCT.parse(bytes)
return {
"beg": dict.beg,
"size": dict.size,
"size_with_redzone": dict.size_with_redzone,
}
def get_elf_section(elf, section) -> bytes:
with open(elf, "rb") as file:
elf = ELFFile(file)
for i in elf.iter_sections():
if i.name == section:
return i.data()
return None
def long_to_bytestring(bitwides, endian, value: int) -> str:
res = ""
byte_array = value.to_bytes(length=int(bitwides / 8), byteorder=endian)
for i in byte_array:
res += "0x%02x, " % (i)
return res
def create_kasan_file(config: Config, region_list=[]):
region: KASanRegion = None
with open(config.outpath, "w") as file:
file.write("const unsigned char\ng_globals_region[] = {\n")
for i in range(len(region_list)):
region = region_list[i]
# Fill the array of regions
# The filling order is as follows, from mm/kasan/generic.c
# The data set to 0 is assigned by the program body
# 1. FAR struct kasan_region_s *next;
# This type will be used to record the size of the shadow area
# to facilitate the program to traverse the array.
# 2. uintptr_t begin;
# 3. uintptr_t end;
# 4. uintptr_t shadow[1];
file.write(
"%s\n"
% (long_to_bytestring(config.bitwides, config.endian, region.size))
)
file.write(
"%s\n"
% (long_to_bytestring(config.bitwides, config.endian, region.start))
)
file.write(
"%s\n"
% (long_to_bytestring(config.bitwides, config.endian, region.end))
)
for j in range(len(region.shadow)):
if j % 8 == 0:
file.write("\n")
file.write("0x%02x, " % (region.shadow[j]))
file.write("\n")
file.write("0x00, 0x00, 0x00, 0x00,0x00, 0x00,0x00, 0x00\n")
file.write("\n};")
# Error extraction section processing to enable the program to compile successfully
def handle_error(config: Config, string=None):
if string:
print(string)
create_kasan_file(config)
exit(0)
def parse_args() -> Config:
global debug
parser = argparse.ArgumentParser(description="Build kasan global variable region")
parser.add_argument("-o", "--outpath", help="outpath")
parser.add_argument("-d", "--ldscript", help="ld script path(Only support sim)")
parser.add_argument("-e", "--elffile", help="elffile")
parser.add_argument(
"--debug",
action="store_true",
default=False,
help="if enabled, it will show more logs.",
)
args = parser.parse_args()
debug = args.debug
return Config(args.outpath, args.elffile, args.ldscript)
def main():
config = parse_args()
if config.elf is None:
handle_error(config)
# Identify the segment information that needs to be extracted
section = get_elf_section(config.elf, KASAN_SECTION)
if section is None:
handle_error(
config,
"Please update the link script, section ['%s'] cannot be found"
% (KASAN_SECTION),
)
# List of global variable descriptors
dict_list = []
# Extract all global variable descriptors within the
if config.bitwides == 32:
global_struct = KASAN_GLOBAL_STRUCT_32
elif config.bitwides == 64:
global_struct = KASAN_GLOBAL_STRUCT_64
step = global_struct.sizeof()
for i in range(0, len(section), step):
dict = get_global_dict(global_struct, section[i : i + step])
dict_list.append(dict)
dict_list = sorted(dict_list, key=lambda item: item["beg"])
# Merge all global variables to obtain several segments of the distribution range
kasan = KASanInfo()
for i in dict_list:
kasan.merge_ranges(i)
# Create empty shadow zone
kasan.create_region()
# Mark on the shadow area
for i in dict_list:
kasan.mark_shadow(i)
create_kasan_file(config, kasan.regions)
if __name__ == "__main__":
main()