From 29966549fa490aac58cf8087c2398eca316d493e Mon Sep 17 00:00:00 2001 From: KPD Date: Mon, 7 Apr 2025 01:58:56 +0000 Subject: [PATCH 1/3] adding ci files --- .github/workflows/kdevops-cleanup.yml | 60 +++++++++ .github/workflows/kdevops-generic.yml | 37 +++++ .github/workflows/kdevops-init.yml | 187 ++++++++++++++++++++++++++ 3 files changed, 284 insertions(+) create mode 100644 .github/workflows/kdevops-cleanup.yml create mode 100644 .github/workflows/kdevops-generic.yml create mode 100644 .github/workflows/kdevops-init.yml diff --git a/.github/workflows/kdevops-cleanup.yml b/.github/workflows/kdevops-cleanup.yml new file mode 100644 index 00000000000000..d7e69ac48e6c92 --- /dev/null +++ b/.github/workflows/kdevops-cleanup.yml @@ -0,0 +1,60 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# This can be used towards the end of your action. All tasks here run even if +# any of the previous tasks failed. + +name: Kdevops cleanup workflow + +on: + workflow_call: # Makes this workflow reusable + +jobs: + cleanup: + name: Archive results and cleanup + runs-on: [self-hosted, Linux, X64] + steps: + - name: Set Linux kdevops development path + if: ${{ job.status != 'cancelled' }} + run: echo "LINUX_KDEVOPS_PATH=$GITHUB_WORKSPACE" >> $GITHUB_ENV + + - name: Get systemd journal files + if: ${{ job.status != 'cancelled' }} + run: | + if [[ ! -d kdevops ]]; then + exit 0 + fi + cd kdevops + make journal-dump + + - name: Start SSH Agent + if: ${{ job.status != 'cancelled' }} + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + - name: Build our kdevops archive results + if: ${{ job.status != 'cancelled' }} + run: | + if [[ ! -d kdevops ]]; then + exit 0 + fi + cd kdevops + make ci-archive + + - name: Upload our kdevops results archive + if: ${{ job.status != 'cancelled' }} + uses: actions/upload-artifact@v4 + with: + name: kdevops-ci-results + path: ${{ env.LINUX_KDEVOPS_PATH }}/kdevops/archive/*.zip + + - name: Run kdevops make destroy + if: always() + run: | + if [[ ! -d kdevops ]]; then + exit 0 + fi + cd kdevops + make destroy + cd .. + rm -rf kdevops diff --git a/.github/workflows/kdevops-generic.yml b/.github/workflows/kdevops-generic.yml new file mode 100644 index 00000000000000..8ca2006c1e1d22 --- /dev/null +++ b/.github/workflows/kdevops-generic.yml @@ -0,0 +1,37 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# Most simple Linux kernel subsystems can be tested with this target +# test setup. For more elaborates tests look for a topic branch under the +# kdevops-ci tree. For example to test a filesystem look at the fstests +# branch. + +name: Run generic kdevops CI tests + +on: + push: + branches: ['**'] + pull_request: + branches: ['**'] + workflow_dispatch: # Allow manual triggering + +jobs: + setup: + uses: ./.github/workflows/kdevops-init.yml + secrets: inherit + + run-tests: + needs: setup + name: Run CI tests + runs-on: [self-hosted, Linux, X64] + steps: + - name: Run CI tests + run: | + cd kdevops + make ci-test + echo "ok" > ci.result + + cleanup: + needs: [run-tests, setup] # Add setup as a dependency to ensure proper ordering + if: always() # This ensures cleanup runs even if run-tests fails + uses: ./.github/workflows/kdevops-cleanup.yml + secrets: inherit diff --git a/.github/workflows/kdevops-init.yml b/.github/workflows/kdevops-init.yml new file mode 100644 index 00000000000000..9a265c2dbc1f84 --- /dev/null +++ b/.github/workflows/kdevops-init.yml @@ -0,0 +1,187 @@ +# SPDX-License-Identifier: GPL-2.0 +# +# This can be used as a initialization workflow for most Linux kernel +# development environments. This takes care of: +# +# - Checks out and re-using a local mirror for your kernel tree +# - Looks for a defconfig in kdevops to use for your kernel tree +# - Sets up CI metadata for kdevops-results-archive +# - Ensures your kernel tree at least builds with defconfig +# - Brings up target DUTs nodes +# - Installs your Linux kernel tree on them +# - Builds all of your test requirements for your Linux kernel tree + +name: Base kdevops workflow + +on: + workflow_call: # Makes this workflow reusable + inputs: + kdevops_defconfig: + required: false + type: string + +jobs: + setup: + name: Setup kdevops environment + runs-on: [self-hosted, Linux, X64] + steps: + - name: Verify we won't expect user input interactions on the host key + run: | + mkdir -p ~/.ssh + if ! grep -q "StrictHostKeyChecking no" ~/.ssh/config 2>/dev/null; then + echo "StrictHostKeyChecking no" >> ~/.ssh/config + fi + + - name: Start SSH Agent for initial test + uses: webfactory/ssh-agent@v0.9.0 + with: + ssh-private-key: ${{ secrets.SSH_PRIVATE_KEY }} + + # Modify the repo here if you have a custom or private URL for the archive + # This can also just be a repo variable later. + - name: Verify our ssh connection will work + run: | + if ! git ls-remote git@github.com:linux-kdevops/kdevops-results-archive.git HEAD; then + echo "Cannot access kdevops-results-archive repository" + exit 1 + fi + + - name: Configure git + run: | + git config --global --add safe.directory '*' + git config --global user.name "kdevops" + git config --global user.email "kdevops@lists.linux.dev" + + - name: Checkout kdevops + run: | + rm -rf kdevops + git clone /mirror/kdevops.git kdevops + + - name: Make sure our repo kdevops defconfig exists + run: | + cd kdevops + if [[ -z "${{ inputs.kdevops_defconfig }}" ]]; then + KDEVOPS_DEFCONFIG=$(basename ${{ github.repository }}) + else + KDEVOPS_DEFCONFIG="${{ inputs.kdevops_defconfig }}" + fi + + if [[ ! -f defconfigs/$KDEVOPS_DEFCONFIG ]]; then + echo "kdevops lacks a defconfig for this repository, expected to find: defconfigs/$KDEVOPS_DEFCONFIG" + exit 1 + fi + + echo "KDEVOPS_DEFCONFIG=$KDEVOPS_DEFCONFIG" >> $GITHUB_ENV + + - name: Checkout custom branch with delta on kdevops/linux + run: | + LINUX_TREE="https://github.com/${{ github.repository }}" + LINUX_TREE_REF="${{ github.event_name == 'pull_request' && github.event.pull_request.head.sha || github.sha }}" + cd kdevops + git clone $LINUX_TREE --reference /mirror/linux.git/ --depth=5 linux + cd linux + git fetch origin $LINUX_TREE_REF + git checkout $LINUX_TREE_REF + git log -1 + + - name: Initialize CI metadata for kdevops-results-archive for linux + run: | + cd kdevops/linux + echo "$(basename ${{ github.repository }})" > ../ci.trigger + + # This supports using kdevops github actions using two different + # approaches: + # + # 1) Commit the .github/ directory onto a Linux tree before your + # kernel changes. This approach is used for example for + # testing patches posted on the mailing list with patchwork, + # this is the strategy kernel-patch-deaemon uses. Since the + # patches are ephemeral there is not important git history to + # maintain. + # + # 2) Merge the .github/ directory at the end of your development + # tree. This is useful for kernel developers wishing to test + # existing trees. + # + # So this checks to see if the last commit (top of the tree) *added* + # the .github directory. If the last commit added it, then we assume + # the commit prior to it was the one we'd like to document as the main + # test point. + if git diff-tree --no-commit-id --name-only --diff-filter=A -r HEAD | grep -q "^\.github/"; then + git log -2 --skip=1 --pretty=format:"%s" -1 > ../ci.subject + git describe --exact-match --tags HEAD^ 2>/dev/null || git rev-parse --short HEAD^ > ../ci.ref + else + git log -1 --pretty=format:"%s" > ../ci.subject + git describe --exact-match --tags HEAD 2>/dev/null || git rev-parse --short HEAD > ../ci.ref + fi + + RELEVANT_GIT_TAG=$(cat ../ci.ref) + RELEVANT_GIT_REF=$(git rev-parse --short=12 $RELEVANT_GIT_TAG) + + echo "LINUX_GIT_REF=$RELEVANT_GIT_REF" >> $GITHUB_ENV + echo "LINUX_GIT_TAG=$RELEVANT_GIT_TAG" >> $GITHUB_ENV + + # Start out pessimistic + echo "unknown" > ../ci.result + echo "Nothing to write home about." > ../ci.commit_extra + + - name: Run a quick Linux kernel defconfig build test + run: | + cd kdevops/linux + git reset --hard ${{ env.LINUX_GIT_TAG }} + make defconfig + make -j$(nproc) + + - name: Run kdevops make defconfig-repo + run: | + LINUX_TREE="https://github.com/${{ github.repository }}" + LINUX_TREE_REF="${{ env.LINUX_GIT_TAG }}" + + # We make the compromise here to use a relevant git tag for the + # host prefix so that folks can easily tell what exact kernel tree + # is being tested by using the relevant git ref. That is, if you + # pushed a tree with the .github/ directory as the top of the tree, + # that commit will not be used, we'll use the last one as that is + # the relevant git ref we want to annotate a test for. + # + # The compromise here we use special KDEVOPS to separete the + # commit ID and github.run_id. Exotic things likes UTF characters + # and dots have problems. + KDEVOPS_HOSTS_PREFIX="${{ env.LINUX_GIT_REF }}KDEVOPS${{ github.run_id }}" + + echo "Going to use defconfig-${{ env.KDEVOPS_DEFCONFIG }}" + + echo "Linux tree: $LINUX_TREE" + echo "Linux trigger ref: $LINUX_TREE_REF" + echo "Linux tag: ${{ env.LINUX_GIT_TAG }}" + echo "Runner ID: ${{ github.run_id }}" + echo "kdevops host prefix: $KDEVOPS_HOSTS_PREFIX" + echo "kdevops defconfig: defconfig-${{ env.KDEVOPS_DEFCONFIG }}" + + KDEVOPS_ARGS="KDEVOPS_HOSTS_PREFIX=$KDEVOPS_HOSTS_PREFIX LINUX_TREE=$LINUX_TREE LINUX_TREE_REF=$LINUX_TREE_REF defconfig-${{ env.KDEVOPS_DEFCONFIG }}" + echo "Going to run:" + echo "make $KDEVOPS_ARGS" + + cd kdevops + make $KDEVOPS_ARGS + + - name: Run kdevops make + run: | + cd kdevops + make -j$(nproc) + + - name: Run kdevops make bringup + run: | + cd kdevops + ls -ld linux + make bringup + + - name: Build linux and boot test nodes on test kernel + run: | + cd kdevops + make linux + + - name: Build required ci tests + run: | + cd kdevops + make ci-build-test From a5f28f3c1dec6c74a3c6d3a6553713e3d8427044 Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Wed, 22 Jan 2025 14:11:13 +0100 Subject: [PATCH 2/3] module: allow for module error injection Allow to test kernel module initialization with eBPF error injection by forcing errors when any of the below annotated functions with ALLOW_ERROR_INJECTION() return. Allow to debug and test module load error behaviour when: complete_formation(): when module initialization switches from MODULE_STATE_UNFORMED to MODULE_STATE_COMING stage. do_init_module(): when an error occurs during module initialization and before we switch from MODULE_STATE_COMING to MODULE_STATE_LIVE stage. module_enable_rodata_ro_after_init(): when an error occurs while setting memory to RO after module initialization. This is when module initialization reaches MODULE_STATE_LIVE stage. Signed-off-by: Daniel Gomez --- kernel/module/main.c | 3 +++ kernel/module/strict_rwx.c | 2 ++ 2 files changed, 5 insertions(+) diff --git a/kernel/module/main.c b/kernel/module/main.c index 9195849e13c4d8..5955be48d59d6c 100644 --- a/kernel/module/main.c +++ b/kernel/module/main.c @@ -59,6 +59,7 @@ #include #include #include +#include #include #include "internal.h" @@ -3076,6 +3077,7 @@ static noinline int do_init_module(struct module *mod) return ret; } +ALLOW_ERROR_INJECTION(do_init_module, ERRNO); static int may_init_module(void) { @@ -3212,6 +3214,7 @@ static int complete_formation(struct module *mod, struct load_info *info) mutex_unlock(&module_mutex); return err; } +ALLOW_ERROR_INJECTION(complete_formation, ERRNO); static int prepare_coming_module(struct module *mod) { diff --git a/kernel/module/strict_rwx.c b/kernel/module/strict_rwx.c index 74834ba15615fa..21936f5cc1e679 100644 --- a/kernel/module/strict_rwx.c +++ b/kernel/module/strict_rwx.c @@ -9,6 +9,7 @@ #include #include #include +#include #include "internal.h" static int module_set_memory(const struct module *mod, enum mod_mem_type type, @@ -71,6 +72,7 @@ int module_enable_rodata_ro_after_init(const struct module *mod) return module_set_memory(mod, MOD_RO_AFTER_INIT, set_memory_ro); } +ALLOW_ERROR_INJECTION(module_enable_rodata_ro_after_init, ERRNO); int module_enable_data_nx(const struct module *mod) { From 33a50be58b656f019f4077a9d268041e121987cf Mon Sep 17 00:00:00 2001 From: Daniel Gomez Date: Wed, 22 Jan 2025 14:11:14 +0100 Subject: [PATCH 3/3] moderr: add module error injection tool Add support for a module error injection tool. The tool can inject errors in the annotated module kernel functions such as complete_formation(), do_init_module() and module_enable_rodata_after_init(). Module name and module function are required parameters to have control over the error injection. Example: Inject error -22 to module_enable_rodata_ro_after_init for brd module: sudo moderr --modname=brd --modfunc=module_enable_rodata_ro_after_init \ --error=-22 --trace Monitoring module error injection... Hit Ctrl-C to end. MODULE ERROR FUNCTION brd -22 module_enable_rodata_after_init() Kernel messages: [ 89.463690] brd: module loaded [ 89.463855] brd: module_enable_rodata_ro_after_init() returned -22, ro_after_init data might still be writable Signed-off-by: Daniel Gomez --- tools/bpf/Makefile | 13 +- tools/bpf/moderr/.gitignore | 2 + tools/bpf/moderr/Makefile | 95 ++++++++++++++ tools/bpf/moderr/moderr.bpf.c | 127 ++++++++++++++++++ tools/bpf/moderr/moderr.c | 236 ++++++++++++++++++++++++++++++++++ tools/bpf/moderr/moderr.h | 40 ++++++ 6 files changed, 510 insertions(+), 3 deletions(-) create mode 100644 tools/bpf/moderr/.gitignore create mode 100644 tools/bpf/moderr/Makefile create mode 100644 tools/bpf/moderr/moderr.bpf.c create mode 100644 tools/bpf/moderr/moderr.c create mode 100644 tools/bpf/moderr/moderr.h diff --git a/tools/bpf/Makefile b/tools/bpf/Makefile index 062bbd6cd048e9..a015af7601086f 100644 --- a/tools/bpf/Makefile +++ b/tools/bpf/Makefile @@ -32,7 +32,7 @@ FEATURE_TESTS = libbfd disassembler-four-args disassembler-init-styled FEATURE_DISPLAY = libbfd check_feat := 1 -NON_CHECK_FEAT_TARGETS := clean bpftool_clean runqslower_clean resolve_btfids_clean +NON_CHECK_FEAT_TARGETS := clean bpftool_clean moderr_clean runqslower_clean resolve_btfids_clean ifdef MAKECMDGOALS ifeq ($(filter-out $(NON_CHECK_FEAT_TARGETS),$(MAKECMDGOALS)),) check_feat := 0 @@ -70,7 +70,7 @@ $(OUTPUT)%.lex.o: $(OUTPUT)%.lex.c PROGS = $(OUTPUT)bpf_jit_disasm $(OUTPUT)bpf_dbg $(OUTPUT)bpf_asm -all: $(PROGS) bpftool runqslower +all: $(PROGS) bpftool moderr runqslower $(OUTPUT)bpf_jit_disasm: CFLAGS += -DPACKAGE='bpf_jit_disasm' $(OUTPUT)bpf_jit_disasm: $(OUTPUT)bpf_jit_disasm.o @@ -86,7 +86,7 @@ $(OUTPUT)bpf_exp.lex.c: $(OUTPUT)bpf_exp.yacc.c $(OUTPUT)bpf_exp.yacc.o: $(OUTPUT)bpf_exp.yacc.c $(OUTPUT)bpf_exp.lex.o: $(OUTPUT)bpf_exp.lex.c -clean: bpftool_clean runqslower_clean resolve_btfids_clean +clean: bpftool_clean moderr_clean runqslower_clean resolve_btfids_clean $(call QUIET_CLEAN, bpf-progs) $(Q)$(RM) -r -- $(OUTPUT)*.o $(OUTPUT)bpf_jit_disasm $(OUTPUT)bpf_dbg \ $(OUTPUT)bpf_asm $(OUTPUT)bpf_exp.yacc.* $(OUTPUT)bpf_exp.lex.* @@ -112,6 +112,12 @@ bpftool_install: bpftool_clean: $(call descend,bpftool,clean) +moderr: + $(call descend,moderr) + +moderr_clean: + $(call descend,moderr,clean) + runqslower: $(call descend,runqslower) @@ -125,5 +131,6 @@ resolve_btfids_clean: $(call descend,resolve_btfids,clean) .PHONY: all install clean bpftool bpftool_install bpftool_clean \ + moderr moderr_clean \ runqslower runqslower_clean \ resolve_btfids resolve_btfids_clean diff --git a/tools/bpf/moderr/.gitignore b/tools/bpf/moderr/.gitignore new file mode 100644 index 00000000000000..ffdb70230c8bc3 --- /dev/null +++ b/tools/bpf/moderr/.gitignore @@ -0,0 +1,2 @@ +# SPDX-License-Identifier: GPL-2.0-only +/.output diff --git a/tools/bpf/moderr/Makefile b/tools/bpf/moderr/Makefile new file mode 100644 index 00000000000000..e6331179f7800e --- /dev/null +++ b/tools/bpf/moderr/Makefile @@ -0,0 +1,95 @@ +# SPDX-License-Identifier: (LGPL-2.1 OR BSD-2-Clause) +include ../../scripts/Makefile.include +include ../../scripts/Makefile.arch + +OUTPUT ?= $(abspath .output)/ + +BPFTOOL_OUTPUT := $(OUTPUT)bpftool/ +DEFAULT_BPFTOOL := $(BPFTOOL_OUTPUT)bootstrap/bpftool +BPFTOOL ?= $(DEFAULT_BPFTOOL) +LIBBPF_SRC := $(abspath ../../lib/bpf) +BPFOBJ_OUTPUT := $(OUTPUT)libbpf/ +BPFOBJ := $(BPFOBJ_OUTPUT)libbpf.a +BPF_DESTDIR := $(BPFOBJ_OUTPUT) +BPF_INCLUDE := $(BPF_DESTDIR)include +INCLUDES := -I$(OUTPUT) -I$(BPF_INCLUDE) -I$(abspath ../../include/uapi) +CFLAGS := -g -Wall $(CLANG_CROSS_FLAGS) +CFLAGS += $(EXTRA_CFLAGS) +LDFLAGS += $(EXTRA_LDFLAGS) +LDLIBS += -lelf -lz + +# Try to detect best kernel BTF source +KERNEL_REL := $(shell uname -r) +VMLINUX_BTF_PATHS := $(if $(O),$(O)/vmlinux) \ + $(if $(KBUILD_OUTPUT),$(KBUILD_OUTPUT)/vmlinux) \ + ../../../vmlinux /sys/kernel/btf/vmlinux \ + /boot/vmlinux-$(KERNEL_REL) +VMLINUX_BTF_PATH := $(or $(VMLINUX_BTF),$(firstword \ + $(wildcard $(VMLINUX_BTF_PATHS)))) + +ifeq ($(V),1) +Q = +else +Q = @ +MAKEFLAGS += --no-print-directory +submake_extras := feature_display=0 +endif + +.DELETE_ON_ERROR: + +.PHONY: all clean moderr libbpf_hdrs +all: moderr + +moderr: $(OUTPUT)moderr + +clean: + $(call QUIET_CLEAN, moderr) + $(Q)$(RM) -r $(BPFOBJ_OUTPUT) $(BPFTOOL_OUTPUT) + $(Q)$(RM) $(OUTPUT)*.o $(OUTPUT)*.d + $(Q)$(RM) $(OUTPUT)*.skel.h $(OUTPUT)vmlinux.h + $(Q)$(RM) $(OUTPUT)moderr + $(Q)$(RM) -r .output + +libbpf_hdrs: $(BPFOBJ) + +$(OUTPUT)moderr: $(OUTPUT)moderr.o $(BPFOBJ) + $(QUIET_LINK)$(CC) $(CFLAGS) $(LDFLAGS) $^ $(LDLIBS) -o $@ + +$(OUTPUT)moderr.o: moderr.h $(OUTPUT)moderr.skel.h \ + $(OUTPUT)moderr.bpf.o | libbpf_hdrs + +$(OUTPUT)moderr.bpf.o: $(OUTPUT)vmlinux.h moderr.h | libbpf_hdrs + +$(OUTPUT)%.skel.h: $(OUTPUT)%.bpf.o | $(BPFTOOL) + $(QUIET_GEN)$(BPFTOOL) gen skeleton $< > $@ + +$(OUTPUT)%.bpf.o: %.bpf.c $(BPFOBJ) | $(OUTPUT) + $(QUIET_GEN)$(CLANG) -g -O2 --target=bpf $(INCLUDES) \ + -D__TARGET_ARCH_$(SRCARCH) \ + -c $(filter %.c,$^) -o $@ && \ + $(LLVM_STRIP) -g $@ + +$(OUTPUT)%.o: %.c | $(OUTPUT) + $(QUIET_CC)$(CC) $(CFLAGS) $(INCLUDES) -c $(filter %.c,$^) -o $@ + +$(OUTPUT) $(BPFOBJ_OUTPUT) $(BPFTOOL_OUTPUT): + $(QUIET_MKDIR)mkdir -p $@ + +$(OUTPUT)vmlinux.h: $(VMLINUX_BTF_PATH) | $(OUTPUT) $(BPFTOOL) +ifeq ($(VMLINUX_H),) + $(Q)if [ ! -e "$(VMLINUX_BTF_PATH)" ] ; then \ + echo "Couldn't find kernel BTF; set VMLINUX_BTF to" \ + "specify its location." >&2; \ + exit 1;\ + fi + $(QUIET_GEN)$(BPFTOOL) btf dump file $(VMLINUX_BTF_PATH) format c > $@ +else + $(Q)cp "$(VMLINUX_H)" $@ +endif + +$(BPFOBJ): $(wildcard $(LIBBPF_SRC)/*.[ch] $(LIBBPF_SRC)/Makefile) | $(BPFOBJ_OUTPUT) + $(Q)$(MAKE) $(submake_extras) -C $(LIBBPF_SRC) OUTPUT=$(BPFOBJ_OUTPUT) \ + DESTDIR=$(BPFOBJ_OUTPUT) prefix= $(abspath $@) install_headers + +$(DEFAULT_BPFTOOL): | $(BPFTOOL_OUTPUT) + $(Q)$(MAKE) $(submake_extras) -C ../bpftool OUTPUT=$(BPFTOOL_OUTPUT) bootstrap diff --git a/tools/bpf/moderr/moderr.bpf.c b/tools/bpf/moderr/moderr.bpf.c new file mode 100644 index 00000000000000..1c5d03336dd87a --- /dev/null +++ b/tools/bpf/moderr/moderr.bpf.c @@ -0,0 +1,127 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Samsung */ +#include "vmlinux.h" +#include +#include +#include +#include "moderr.h" + +const volatile bool filter_modname = false; +const volatile char targ_modname[MODULE_NAME_LEN]; +const volatile bool set_errinj = false; +const volatile int targ_errinj = 0; +const volatile bool filter_modfunc = false; +const volatile int targ_modfunc = 0; + +char LICENSE[] SEC("license") = "GPL"; + +struct { + __uint(type, BPF_MAP_TYPE_RINGBUF); + __uint(max_entries, 2097152); +} rb SEC(".maps"); + +static __always_inline bool filter_module_name(struct module *mod) +{ + char modname[MODULE_NAME_LEN]; + + bpf_probe_read_str(modname, sizeof(modname), mod->name); + + if (!filter_modname || + filter_modname && bpf_strncmp(modname, MODULE_NAME_LEN, + (const char *)targ_modname) != 0) + return false; + + return true; +} + +static __always_inline bool filter_module_func(enum modfunc fc) +{ + if (!filter_modfunc || filter_modfunc && targ_modfunc != fc) + return false; + + return true; +} + +static __always_inline bool +generate_errinj_event(struct pt_regs *ctx, struct module *mod, enum modfunc fc) +{ + struct event *e; + + e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0); + if (!e) + return false; + + e->err = 0; + e->func = fc; + bpf_probe_read_str(e->modname, sizeof(e->modname), mod->name); + + if (set_errinj) { + bpf_override_return(ctx, targ_errinj); + e->err = targ_errinj; + } + + bpf_ringbuf_submit(e, 0); + return true; +} + +static __always_inline bool generate_debug_event(struct pt_regs *ctx, + struct module *mod, + enum modfunc fc, + const char *fmt) +{ + struct event *e; + + e = bpf_ringbuf_reserve(&rb, sizeof(*e), 0); + if (!e) + return false; + + e->dbg = BPF_SNPRINTF(e->msg, sizeof(e->msg), "[%s:%s]: %s", mod->name, + modfunc_to_string(fc), fmt); + + bpf_ringbuf_submit(e, 0); + return true; +} + +static __always_inline int +module_error_injection(struct pt_regs *ctx, struct module *mod, enum modfunc fc) +{ + if (!filter_module_name(mod)) { + generate_debug_event(ctx, mod, fc, + "Target module does not match"); + return 0; + } + + if (!filter_module_func(fc)) { + generate_debug_event(ctx, mod, fc, + "Target function does not match"); + return 0; + } + + if (!generate_errinj_event(ctx, mod, fc)) { + generate_debug_event( + ctx, mod, fc, + "Error injection event cannot be generated"); + return 0; + } + + return 0; +} + +SEC("kprobe/complete_formation") +int BPF_KPROBE(complete_formation, struct module *mod, struct load_info *info) +{ + return module_error_injection(ctx, mod, COMPLETE_FORMATION); +} + +SEC("kprobe/do_init_module") +int BPF_KPROBE(do_init_module, struct module *mod, struct load_info *info) +{ + return module_error_injection(ctx, mod, DO_INIT_MODULE); +} + +SEC("kprobe/module_enable_rodata_ro_after_init") +int BPF_KPROBE(module_enable_rodata_ro_after_init, struct module *mod) +{ + return module_error_injection(ctx, mod, + MODULE_ENABLE_RODATA_AFTER_INIT); +} diff --git a/tools/bpf/moderr/moderr.c b/tools/bpf/moderr/moderr.c new file mode 100644 index 00000000000000..dce18b02b55d1a --- /dev/null +++ b/tools/bpf/moderr/moderr.c @@ -0,0 +1,236 @@ +// SPDX-License-Identifier: GPL-2.0 +/* Copyright (c) 2025 Samsung */ +#include +#include +#include +#include +#include +#include +#include +#include +#include "moderr.h" +#include "moderr.skel.h" + +static struct env { + bool verbose; + char modname[MODULE_NAME_LEN]; + enum modfunc func; + bool trace; + int errinj; +} env; + +const char *argp_program_version = "moderr 0.1"; +const char *argp_program_bug_address = ""; +const char argp_program_doc[] = +"BPF moderr application.\n" +"\n" +"It injects errors in module initialization\n" +"\nUSAGE: " +"moderr [-m ] [-f ] [-e ]\n"; + +static volatile bool exiting = false; + +#define ARRAY_SIZE(x) (sizeof(x) / sizeof(*(x))) + +static const struct argp_option opts[] = { + { "verbose", 'v', NULL, 0, "Verbose debug output" }, + { "trace", 't', NULL, 0, "Enable trace output", 0 }, + { "modname", 'm', "MODNAME", 0, "Trace this module name only", 0 }, + { "modfunc", 'f', "MODFUNC", 0, "Trace this module function only", 0 }, + { "list", 'l', NULL, 0, "List available module functions", 0 }, + { "error", 'e', "ERROR", 0, "Inject this error", 0 }, + { NULL, 'h', NULL, OPTION_HIDDEN, "Show the full help", 0 }, + {}, +}; + +static void help_modfunc(void) +{ + printf("\nAvailable modfunc options are:\n" + "- complete_formation\n" + "- do_init_module\n" + "- module_enable_rodata_ro_after_init\n\n"); +} + +static enum modfunc string_to_modfunc(char *arg) +{ + if (strncmp(arg, "complete_formation", strlen(arg)) == 0) + return COMPLETE_FORMATION; + + if (strncmp(arg, "do_init_module", strlen(arg)) == 0) + return DO_INIT_MODULE; + + if (strncmp(arg, "module_enable_rodata_ro_after_init", strlen(arg)) == + 0) + return MODULE_ENABLE_RODATA_AFTER_INIT; + + return UNKNOWN; +} + +static error_t parse_arg(int key, char *arg, struct argp_state *state) +{ + switch (key) { + case 'h': + argp_state_help(state, stderr, ARGP_HELP_STD_HELP); + break; + case 'l': + help_modfunc(); + argp_usage(state); + break; + case 'v': + env.verbose = true; + break; + case 'm': + if (strlen(arg) + 1 > MODULE_NAME_LEN) { + fprintf(stderr, "module name error\n"); + argp_usage(state); + } + strncpy(env.modname, arg, sizeof(env.modname) - 1); + break; + case 'f': + if (strlen(arg) + 1 > MODULE_FUNC_LEN) { + fprintf(stderr, "module function too long\n"); + argp_usage(state); + } + env.func = string_to_modfunc(arg); + if (!env.func) { + fprintf(stderr, "invalid module function\n"); + help_modfunc(); + argp_usage(state); + } + break; + case 'e': + env.errinj = atoi(arg); + break; + case 't': + env.trace = true; + break; + case ARGP_KEY_ARG: + argp_usage(state); + break; + default: + return ARGP_ERR_UNKNOWN; + } + return 0; +} + +static const struct argp argp = { + .options = opts, + .parser = parse_arg, + .doc = argp_program_doc, +}; + +static int libbpf_print_fn(enum libbpf_print_level level, const char *format, + va_list args) +{ + if (level == LIBBPF_DEBUG && !env.verbose) + return 0; + return vfprintf(stderr, format, args); +} + +static void sig_handler(int sig) +{ + exiting = true; +} + +static int handle_event(void *ctx, void *data, size_t data_sz) +{ + const struct event *e = data; + + if (!env.trace) + return 0; + + if (e->dbg) { + if (env.verbose) + printf("%s\n", e->msg); + return 0; + } + + printf("%-10s %-5d %-20s\n", e->modname, e->err, + modfunc_to_string(e->func)); + + return 0; +} + +int main(int argc, char **argv) +{ + struct ring_buffer *rb = NULL; + struct moderr_bpf *obj; + int err; + + err = argp_parse(&argp, argc, argv, 0, NULL, NULL); + if (err) + return err; + + if (!strlen(env.modname) || !env.func) { + fprintf(stderr, "missing arguments\n"); + return EXIT_FAILURE; + } + + libbpf_set_print(libbpf_print_fn); + + signal(SIGINT, sig_handler); + signal(SIGTERM, sig_handler); + + obj = moderr_bpf__open(); + if (!obj) { + fprintf(stderr, "failed to open and load BPF object\n"); + return 1; + } + + obj->rodata->filter_modname = true; + strncpy(obj->rodata->targ_modname, env.modname, MODULE_NAME_LEN - 1); + obj->rodata->targ_modname[MODULE_NAME_LEN - 1] = '\0'; + + obj->rodata->filter_modfunc = true; + obj->rodata->targ_modfunc = env.func; + + if (env.errinj) { + obj->rodata->set_errinj = true; + obj->rodata->targ_errinj = env.errinj; + } + + err = moderr_bpf__load(obj); + if (err) { + fprintf(stderr, "failed to load and verify BPF object\n"); + goto cleanup; + } + + err = moderr_bpf__attach(obj); + if (err) { + fprintf(stderr, "failed to attach BPF object\n"); + goto cleanup; + } + + printf("Monitoring module error injection... Hit Ctrl-C to end.\n"); + + rb = ring_buffer__new(bpf_map__fd(obj->maps.rb), handle_event, NULL, + NULL); + if (!rb) { + err = -1; + fprintf(stderr, "failed to create ring buffer\n"); + goto cleanup; + } + + if (env.trace) + printf("%-10s %-5s %-20s\n", "MODULE", "ERROR", "FUNCTION"); + + while (!exiting) { + err = ring_buffer__poll(rb, 100); + if (err == -EINTR) { + err = 0; + break; + } + if (err < 0) { + fprintf(stderr, "error polling ring buffer: %d\n", err); + break; + } + } + + printf("\n"); + +cleanup: + ring_buffer__free(rb); + moderr_bpf__destroy(obj); + + return err < 0 ? -err : 0; +} diff --git a/tools/bpf/moderr/moderr.h b/tools/bpf/moderr/moderr.h new file mode 100644 index 00000000000000..e17440cf4bd5fe --- /dev/null +++ b/tools/bpf/moderr/moderr.h @@ -0,0 +1,40 @@ +/* SPDX-License-Identifier: GPL-2.0 */ +/* Copyright (c) 2025 Samsung */ +#ifndef __MODERR_H +#define __MODERR_H + +#define MAX_PARAM_PREFIX_LEN (64 - sizeof(unsigned long)) +#define MODULE_NAME_LEN MAX_PARAM_PREFIX_LEN +#define MODULE_FUNC_LEN 128 +#define MESSAGE_LEN 128 + +enum modfunc { + UNKNOWN, + COMPLETE_FORMATION = 1, + DO_INIT_MODULE, + MODULE_ENABLE_RODATA_AFTER_INIT, +}; + +struct event { + char modname[MODULE_NAME_LEN]; + int err; + int func; + char msg[MESSAGE_LEN]; + int dbg; +}; + +static inline const char *modfunc_to_string(enum modfunc fc) +{ + switch (fc) { + case COMPLETE_FORMATION: + return "complete_formation()"; + case DO_INIT_MODULE: + return "do_init_module()"; + case MODULE_ENABLE_RODATA_AFTER_INIT: + return "module_enable_rodata_after_init()"; + default: + return "unknown"; + } +} + +#endif /* __MODERR_H */