#!/usr/bin/env bash # SPDX-License-Identifier: Apache-2.0 # # Copyright (C) 2023 The Falco Authors. # # # Licensed 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. # # Simple script that desperately tries to load the kernel instrumentation by # looking for it in a bunch of ways. Convenient when running Falco inside # a container or in other weird environments. # # # Returns 1 if $cos_ver > $base_ver, 0 otherwise # cos_version_greater() { if [[ $cos_ver == "${base_ver}" ]]; then return 0 fi # # COS build numbers are in the format x.y.z # a=$(echo "${cos_ver}" | cut -d. -f1) b=$(echo "${cos_ver}" | cut -d. -f2) c=$(echo "${cos_ver}" | cut -d. -f3) d=$(echo "${base_ver}" | cut -d. -f1) e=$(echo "${base_ver}" | cut -d. -f2) f=$(echo "${base_ver}" | cut -d. -f3) # Test the first component if [[ $a -gt $d ]]; then return 1 elif [[ $d -gt $a ]]; then return 0 fi # Test the second component if [[ $b -gt $e ]]; then return 1 elif [[ $e -gt $b ]]; then return 0 fi # Test the third component if [[ $c -gt $f ]]; then return 1 elif [[ $f -gt $c ]]; then return 0 fi # If we get here, probably malformatted version string? return 0 } get_kernel_config() { if [ -f /proc/config.gz ]; then echo "* Found kernel config at /proc/config.gz" KERNEL_CONFIG_PATH=/proc/config.gz elif [ -f "/boot/config-${KERNEL_RELEASE}" ]; then echo "* Found kernel config at /boot/config-${KERNEL_RELEASE}" KERNEL_CONFIG_PATH=/boot/config-${KERNEL_RELEASE} elif [ -n "${HOST_ROOT}" ] && [ -f "${HOST_ROOT}/boot/config-${KERNEL_RELEASE}" ]; then echo "* Found kernel config at ${HOST_ROOT}/boot/config-${KERNEL_RELEASE}" KERNEL_CONFIG_PATH="${HOST_ROOT}/boot/config-${KERNEL_RELEASE}" elif [ -f "/usr/lib/ostree-boot/config-${KERNEL_RELEASE}" ]; then echo "* Found kernel config at /usr/lib/ostree-boot/config-${KERNEL_RELEASE}" KERNEL_CONFIG_PATH="/usr/lib/ostree-boot/config-${KERNEL_RELEASE}" elif [ -n "${HOST_ROOT}" ] && [ -f "${HOST_ROOT}/usr/lib/ostree-boot/config-${KERNEL_RELEASE}" ]; then echo "* Found kernel config at ${HOST_ROOT}/usr/lib/ostree-boot/config-${KERNEL_RELEASE}" KERNEL_CONFIG_PATH="${HOST_ROOT}/usr/lib/ostree-boot/config-${KERNEL_RELEASE}" elif [ -f "/lib/modules/${KERNEL_RELEASE}/config" ]; then # This code works both for native host and containers assuming that # Dockerfile sets up the desired symlink /lib/modules -> $HOST_ROOT/lib/modules echo "* Found kernel config at /lib/modules/${KERNEL_RELEASE}/config" KERNEL_CONFIG_PATH="/lib/modules/${KERNEL_RELEASE}/config" fi if [ -z "${KERNEL_CONFIG_PATH}" ]; then >&2 echo "Cannot find kernel config" exit 1 fi if [[ "${KERNEL_CONFIG_PATH}" == *.gz ]]; then HASH=$(zcat "${KERNEL_CONFIG_PATH}" | md5sum - | cut -d' ' -f1) else HASH=$(md5sum "${KERNEL_CONFIG_PATH}" | cut -d' ' -f1) fi } get_target_id() { if [ -f "${HOST_ROOT}/etc/os-release" ]; then # freedesktop.org and systemd # shellcheck source=/dev/null source "${HOST_ROOT}/etc/os-release" OS_ID=$ID elif [ -f "${HOST_ROOT}/etc/debian_version" ]; then # Older debian distros # fixme > Can this happen on older Ubuntu? OS_ID=debian elif [ -f "${HOST_ROOT}/etc/centos-release" ]; then # Older CentOS distros OS_ID=centos elif [ -f "${HOST_ROOT}/etc/redhat-release" ]; then # Older RHEL distros OS_ID=rhel else # No target id can be determinand TARGET_ID="undetermined" return fi # Overwrite the OS_ID if /etc/VERSION file is present. # Not sure if there is a better way to detect minikube. if [ -f "${HOST_ROOT}/etc/VERSION" ]; then OS_ID=minikube fi case "${OS_ID}" in ("amzn") case "${VERSION_ID}" in ("2") TARGET_ID="amazonlinux2" ;; ("2022") TARGET_ID="amazonlinux2022" ;; ("2023") TARGET_ID="amazonlinux2023" ;; (*) TARGET_ID="amazonlinux" ;; esac ;; ("debian") # Workaround: debian kernelreleases might now be actual kernel running; # instead, they might be the Debian kernel package # providing the compatible kernel ABI # See https://lists.debian.org/debian-user/2017/03/msg00485.html # Real kernel release is embedded inside the kernel version. # Moreover, kernel arch, when present, is attached to the former, # therefore make sure to properly take it and attach it to the latter. # Moreover, we support 3 flavors for debian kernels: cloud, rt and normal. # KERNEL-RELEASE will have a `-rt`, or `-cloud` if we are in one of these flavors. # Manage it to download the correct driver. # # Example: KERNEL_RELEASE="5.10.0-0.deb10.22-rt-amd64" and `uname -v`="5.10.178-3" # should lead to: KERNEL_RELEASE="5.10.178-3-rt-amd64" TARGET_ID=$(echo "${OS_ID}" | tr '[:upper:]' '[:lower:]') local ARCH_extra="" if [[ $KERNEL_RELEASE =~ -?(rt-|cloud-|)(amd64|arm64) ]]; then ARCH_extra="-${BASH_REMATCH[1]}${BASH_REMATCH[2]}" fi if [[ ${DRIVER_KERNEL_VERSION} =~ ([0-9]+\.[0-9]+\.[0-9]+\-[0-9]+) ]]; then KERNEL_RELEASE="${BASH_REMATCH[1]}${ARCH_extra}" fi ;; ("ubuntu") # Extract the flavor from the kernelrelease # Examples: # 5.0.0-1028-aws-5.0 -> ubuntu-aws # 5.15.0-1009-aws -> ubuntu-aws if [[ $KERNEL_RELEASE =~ -([a-zA-Z]+)(-.*)?$ ]]; then TARGET_ID="ubuntu-${BASH_REMATCH[1]}" else TARGET_ID="ubuntu-generic" fi # In the case that the kernelversion isn't just a number # we keep also the remaining part excluding `-Ubuntu`. # E.g.: # from the following `uname -v` result # `#26~22.04.1-Ubuntu SMP Mon Apr 24 01:58:15 UTC 2023` # we obtain the kernelversion`26~22.04.1` if [[ ${DRIVER_KERNEL_VERSION} =~ (^\#[0-9]+\~[^-]*-Ubuntu .*$) ]]; then KERNEL_VERSION=$(echo "${DRIVER_KERNEL_VERSION}" | sed 's/#\([^-\\ ]*\).*/\1/g') fi ;; ("flatcar") KERNEL_RELEASE="${VERSION_ID}" TARGET_ID=$(echo "${OS_ID}" | tr '[:upper:]' '[:lower:]') ;; ("minikube") TARGET_ID=$(echo "${OS_ID}" | tr '[:upper:]' '[:lower:]') # Extract the minikube version. Ex. With minikube version equal to "v1.26.0-1655407986-14197" the extracted version # will be "1.26.0" if [[ $(cat ${HOST_ROOT}/etc/VERSION) =~ ([0-9]+(\.[0-9]+){2}) ]]; then # kernel version for minikube is always in "1_minikubeversion" format. Ex "1_1.26.0". KERNEL_VERSION="1_${BASH_REMATCH[1]}" else echo "* Unable to extract minikube version from ${HOST_ROOT}/etc/VERSION" exit 1 fi ;; ("bottlerocket") TARGET_ID=$(echo "${OS_ID}" | tr '[:upper:]' '[:lower:]') # variant_id has been sourced from os-release. Get only the first variant part if [[ -n ${VARIANT_ID} ]]; then # take just first part (eg: VARIANT_ID=aws-k8s-1.15 -> aws) VARIANT_ID_CUT=${VARIANT_ID%%-*} fi # version_id has been sourced from os-release. Build a kernel version like: 1_1.11.0-aws KERNEL_VERSION="1_${VERSION_ID}-${VARIANT_ID_CUT}" ;; ("talos") TARGET_ID=$(echo "${OS_ID}" | tr '[:upper:]' '[:lower:]') # version_id has been sourced from os-release. Build a kernel version like: 1_1.4.1 KERNEL_VERSION="1_${VERSION_ID}" ;; (*) TARGET_ID=$(echo "${OS_ID}" | tr '[:upper:]' '[:lower:]') ;; esac } flatcar_relocate_tools() { local -a tools=( scripts/basic/fixdep scripts/mod/modpost tools/objtool/objtool ) local -r hostld=$(ls /host/usr/lib64/ld-linux-*.so.*) local -r kdir=/lib/modules/$(ls /lib/modules/)/build echo "** Found host dl interpreter: ${hostld}" for host_tool in ${tools[@]}; do t=${host_tool} tool=$(basename $t) tool_dir=$(dirname $t) host_tool=${kdir}/${host_tool} if [ ! -f ${host_tool} ]; then continue fi umount ${host_tool} 2>/dev/null || true mkdir -p /tmp/${tool_dir}/ cp -a ${host_tool} /tmp/${tool_dir}/ echo "** Setting host dl interpreter for $host_tool" patchelf --set-interpreter ${hostld} --set-rpath /host/usr/lib64 /tmp/${tool_dir}/${tool} mount -o bind /tmp/${tool_dir}/${tool} ${host_tool} done } load_kernel_module_compile() { # Skip dkms on UEK hosts because it will always fail if [[ ${DRIVER_KERNEL_RELEASE} == *uek* ]]; then >&2 echo "Skipping because the dkms install always fail (on UEK hosts)" return fi if ! hash dkms >/dev/null 2>&1; then >&2 echo "This program requires dkms" return fi if [ "${TARGET_ID}" == "flatcar" ]; then KERNEL_RELEASE=${DRIVER_KERNEL_RELEASE} echo "* Flatcar detected (version ${VERSION_ID}); relocating kernel tools" flatcar_relocate_tools fi # Try to compile using all the available gcc versions for CURRENT_GCC in $(ls "$(dirname "$(which gcc)")"/gcc*); do # Filter away gcc-{ar,nm,...} # Only gcc compiler has `-print-search-dirs` option. ${CURRENT_GCC} -print-search-dirs 2>&1 | grep "install:" if [ "$?" -ne "0" ]; then continue fi echo "* Trying to dkms install ${DRIVER_NAME} module with GCC ${CURRENT_GCC}" echo "#!/usr/bin/env bash" > "${TMPDIR}/falco-dkms-make" echo "make CC=${CURRENT_GCC} \$@" >> "${TMPDIR}/falco-dkms-make" chmod +x "${TMPDIR}/falco-dkms-make" if dkms install --directive="MAKE='${TMPDIR}/falco-dkms-make'" -m "${DRIVER_NAME}" -v "${DRIVER_VERSION}" -k "${KERNEL_RELEASE}" 2>/dev/null; then echo "* ${DRIVER_NAME} module installed in dkms" KO_FILE="/var/lib/dkms/${DRIVER_NAME}/${DRIVER_VERSION}/${KERNEL_RELEASE}/${ARCH}/module/${DRIVER_NAME}" if [ -f "$KO_FILE.ko" ]; then KO_FILE="$KO_FILE.ko" elif [ -f "$KO_FILE.ko.gz" ]; then KO_FILE="$KO_FILE.ko.gz" elif [ -f "$KO_FILE.ko.xz" ]; then KO_FILE="$KO_FILE.ko.xz" elif [ -f "$KO_FILE.ko.zst" ]; then KO_FILE="$KO_FILE.ko.zst" else >&2 echo "${DRIVER_NAME} module file not found" return fi echo "* ${DRIVER_NAME} module found: ${KO_FILE}" echo "* Trying to insmod" chcon -t modules_object_t "$KO_FILE" > /dev/null 2>&1 || true if insmod "$KO_FILE" > /dev/null 2>&1; then echo "* Success: ${DRIVER_NAME} module found and loaded in dkms" exit 0 fi echo "* Unable to insmod ${DRIVER_NAME} module" else DKMS_LOG="/var/lib/dkms/${DRIVER_NAME}/${DRIVER_VERSION}/build/make.log" if [ -f "${DKMS_LOG}" ]; then echo "* Running dkms build failed, dumping ${DKMS_LOG} (with GCC ${CURRENT_GCC})" cat "${DKMS_LOG}" else echo "* Running dkms build failed, couldn't find ${DKMS_LOG} (with GCC ${CURRENT_GCC})" fi fi done } load_kernel_module_download() { local FALCO_KERNEL_MODULE_FILENAME="${DRIVER_NAME}_${TARGET_ID}_${KERNEL_RELEASE}_${KERNEL_VERSION}.ko" local URL=$(echo "${1}/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}" | sed s/+/%2B/g) echo "* Trying to download a prebuilt ${DRIVER_NAME} module from ${URL}" if curl -L --create-dirs ${FALCO_DRIVER_CURL_OPTIONS} -o "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}" "${URL}"; then echo "* Download succeeded" chcon -t modules_object_t "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}" > /dev/null 2>&1 || true if insmod "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}"; then echo "* Success: ${DRIVER_NAME} module found and inserted" exit 0 fi >&2 echo "Unable to insmod the prebuilt ${DRIVER_NAME} module" else >&2 echo "Unable to find a prebuilt ${DRIVER_NAME} module" return fi } print_clean_termination() { echo echo "[SUCCESS] Cleaning phase correctly terminated." echo echo "================ Cleaning phase ================" echo } print_filename_components() { echo " - driver name: ${DRIVER_NAME}" echo " - target identifier: ${TARGET_ID}" echo " - kernel release: ${KERNEL_RELEASE}" echo " - kernel version: ${KERNEL_VERSION}" } print_as_env_vars() { echo "ARCH=\"${ARCH}\"" echo "KERNEL_RELEASE=\"${KERNEL_RELEASE}\"" echo "KERNEL_VERSION=\"${KERNEL_VERSION}\"" echo "ENABLE_COMPILE=\"${ENABLE_COMPILE}\"" echo "ENABLE_DOWNLOAD=\"${ENABLE_DOWNLOAD}\"" echo "TARGET_ID=\"${TARGET_ID}\"" echo "DRIVER=\"${DRIVER}\"" echo "DRIVERS_REPO=\"${DRIVERS_REPO}\"" echo "DRIVER_VERSION=\"${DRIVER_VERSION}\"" echo "DRIVER_NAME=\"${DRIVER_NAME}\"" echo "FALCO_VERSION=\"${FALCO_VERSION}\"" } clean_kernel_module() { echo echo "================ Cleaning phase ================" echo if ! hash lsmod > /dev/null 2>&1; then >&2 echo "This program requires lsmod." exit 1 fi if ! hash rmmod > /dev/null 2>&1; then >&2 echo "This program requires rmmod." exit 1 fi KMOD_NAME=$(echo "${DRIVER_NAME}" | tr "-" "_") echo "* 1. Check if kernel module '${KMOD_NAME}' is still loaded:" if ! lsmod | cut -d' ' -f1 | grep -qx "${KMOD_NAME}"; then echo "- OK! There is no '${KMOD_NAME}' module loaded." echo fi # Wait 50s = MAX_RMMOD_WAIT * 5s MAX_RMMOD_WAIT=10 # Remove kernel module if is still loaded. while lsmod | cut -d' ' -f1 | grep -qx "${KMOD_NAME}" && [ $MAX_RMMOD_WAIT -gt 0 ]; do echo "- Kernel module '${KMOD_NAME}' is still loaded." echo "- Trying to unload it with 'rmmod ${KMOD_NAME}'..." if rmmod ${KMOD_NAME}; then echo "- OK! Unloading '${KMOD_NAME}' module succeeded." echo else echo "- Nothing to do...'falco-driver-loader' will wait until you remove the kernel module to have a clean termination." echo "- Check that no process is using the kernel module with 'lsmod | grep ${KMOD_NAME}'." echo "- Sleep 5 seconds..." echo ((--MAX_RMMOD_WAIT)) sleep 5 fi done if [ ${MAX_RMMOD_WAIT} -eq 0 ]; then echo "[WARNING] '${KMOD_NAME}' module is still loaded, you could have incompatibility issues." echo fi if ! hash dkms >/dev/null 2>&1; then echo "- Skipping dkms remove (dkms not found)." print_clean_termination return fi # Remove all versions of this module from dkms. echo "* 2. Check all versions of kernel module '${KMOD_NAME}' in dkms:" DRIVER_VERSIONS=$(dkms status -m "${KMOD_NAME}" | tr -d "," | tr -d ":" | tr "/" " " | cut -d' ' -f2) if [ -z "${DRIVER_VERSIONS}" ]; then echo "- OK! There are no '${KMOD_NAME}' module versions in dkms." else echo "- There are some versions of '${KMOD_NAME}' module in dkms." echo echo "* 3. Removing all the following versions from dkms:" echo "${DRIVER_VERSIONS}" echo fi for CURRENT_VER in ${DRIVER_VERSIONS}; do echo "- Removing ${CURRENT_VER}..." if dkms remove -m ${KMOD_NAME} -v "${CURRENT_VER}" --all; then echo echo "- OK! Removing '${CURRENT_VER}' succeeded." echo else echo "[WARNING] Removing '${KMOD_NAME}' version '${CURRENT_VER}' failed." fi done print_clean_termination } load_kernel_module() { clean_kernel_module echo "* Looking for a ${DRIVER_NAME} module locally (kernel ${KERNEL_RELEASE})" local FALCO_KERNEL_MODULE_FILENAME="${DRIVER_NAME}_${TARGET_ID}_${KERNEL_RELEASE}_${KERNEL_VERSION}.ko" echo "* Filename '${FALCO_KERNEL_MODULE_FILENAME}' is composed of:" print_filename_components if [ -f "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}" ]; then echo "* Found a prebuilt ${DRIVER_NAME} module at ${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}, loading it" chcon -t modules_object_t "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}" > /dev/null 2>&1 || true insmod "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${FALCO_KERNEL_MODULE_FILENAME}" && echo "* Success: ${DRIVER_NAME} module found and inserted" exit $? fi if [ -n "$ENABLE_DOWNLOAD" ]; then IFS=", " read -r -a urls <<< "${DRIVERS_REPO}" for url in "${urls[@]}"; do load_kernel_module_download $url done fi if [ -n "$ENABLE_COMPILE" ]; then load_kernel_module_compile fi # Last try (might load a previous driver version) echo "* Trying to load a system ${DRIVER_NAME} module, if present" if modprobe "${DRIVER_NAME}" > /dev/null 2>&1; then echo "* Success: ${DRIVER_NAME} module found and loaded with modprobe" exit 0 fi # Not able to download a prebuilt module nor to compile one on-the-fly >&2 echo "Consider compiling your own ${DRIVER_NAME} driver and loading it or getting in touch with the Falco community" exit 1 } load_bpf_probe_compile() { local BPF_KERNEL_SOURCES_URL="" local STRIP_COMPONENTS=1 customize_kernel_build() { if [ -n "${KERNEL_EXTRA_VERSION}" ]; then sed -i "s/LOCALVERSION=\"\"/LOCALVERSION=\"${KERNEL_EXTRA_VERSION}\"/" .config fi make olddefconfig > /dev/null make modules_prepare > /dev/null } if [ "${TARGET_ID}" == "flatcar" ]; then KERNEL_RELEASE=${DRIVER_KERNEL_RELEASE} echo "* Flatcar detected (version ${VERSION_ID}); relocating kernel tools" flatcar_relocate_tools fi if [ "${TARGET_ID}" == "cos" ]; then echo "* COS detected (build ${BUILD_ID}), using COS kernel headers" BPF_KERNEL_SOURCES_URL="https://storage.googleapis.com/cos-tools/${BUILD_ID}/kernel-headers.tgz" KERNEL_EXTRA_VERSION="+" STRIP_COMPONENTS=0 customize_kernel_build() { pushd usr/src/* > /dev/null || exit # Note: this overrides the KERNELDIR set while untarring the tarball KERNELDIR=$(pwd) export KERNELDIR sed -i '/^#define randomized_struct_fields_start struct {$/d' include/linux/compiler-clang.h sed -i '/^#define randomized_struct_fields_end };$/d' include/linux/compiler-clang.h popd > /dev/null || exit # Might need to configure our own sources depending on COS version cos_ver=${BUILD_ID} base_ver=11553.0.0 cos_version_greater greater_ret=$? if [[ greater_ret -eq 1 ]]; then export KBUILD_EXTRA_CPPFLAGS=-DCOS_73_WORKAROUND fi } fi if [ "${TARGET_ID}" == "minikube" ]; then MINIKUBE_VERSION="$(cat "${HOST_ROOT}/etc/VERSION")" echo "* Minikube detected (${MINIKUBE_VERSION}), using linux kernel sources for minikube kernel" local kernel_version kernel_version=${DRIVER_KERNEL_RELEASE} local -r kernel_version_major=$(echo "${kernel_version}" | cut -d. -f1) local -r kernel_version_minor=$(echo "${kernel_version}" | cut -d. -f2) local -r kernel_version_patch=$(echo "${kernel_version}" | cut -d. -f3) if [ "${kernel_version_patch}" == "0" ]; then kernel_version="${kernel_version_major}.${kernel_version_minor}" fi BPF_KERNEL_SOURCES_URL="http://mirrors.edge.kernel.org/pub/linux/kernel/v${kernel_version_major}.x/linux-${kernel_version}.tar.gz" fi if [ -n "${BPF_USE_LOCAL_KERNEL_SOURCES}" ]; then local -r kernel_version_major=$(echo "${DRIVER_KERNEL_RELEASE}" | cut -d. -f1) local -r kernel_version=$(echo "${DRIVER_KERNEL_RELEASE}" | cut -d- -f1) KERNEL_EXTRA_VERSION="-$(echo "${DRIVER_KERNEL_RELEASE}" | cut -d- -f2)" echo "* Using downloaded kernel sources for kernel version ${kernel_version}..." BPF_KERNEL_SOURCES_URL="http://mirrors.edge.kernel.org/pub/linux/kernel/v${kernel_version_major}.x/linux-${kernel_version}.tar.gz" fi if [ -n "${BPF_KERNEL_SOURCES_URL}" ]; then get_kernel_config echo "* Downloading ${BPF_KERNEL_SOURCES_URL}" mkdir -p /tmp/kernel cd /tmp/kernel || exit cd "$(mktemp -d -p /tmp/kernel)" || exit if ! curl -L -o kernel-sources.tgz --create-dirs ${FALCO_DRIVER_CURL_OPTIONS} "${BPF_KERNEL_SOURCES_URL}"; then >&2 echo "Unable to download the kernel sources" return fi echo "* Extracting kernel sources" mkdir kernel-sources && tar xf kernel-sources.tgz -C kernel-sources --strip-components "${STRIP_COMPONENTS}" cd kernel-sources || exit KERNELDIR=$(pwd) export KERNELDIR if [[ "${KERNEL_CONFIG_PATH}" == *.gz ]]; then zcat "${KERNEL_CONFIG_PATH}" > .config else cat "${KERNEL_CONFIG_PATH}" > .config fi echo "* Configuring kernel" customize_kernel_build fi echo "* Trying to compile the eBPF probe (${BPF_PROBE_FILENAME})" make -C "/usr/src/${DRIVER_NAME}-${DRIVER_VERSION}/bpf" > /dev/null mkdir -p "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}" mv "/usr/src/${DRIVER_NAME}-${DRIVER_VERSION}/bpf/probe.o" "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" if [ -n "${BPF_KERNEL_SOURCES_URL}" ]; then rm -r /tmp/kernel fi } load_bpf_probe_download() { local URL URL=$(echo "${1}/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" | sed s/+/%2B/g) echo "* Trying to download a prebuilt eBPF probe from ${URL}" if ! curl -L --create-dirs ${FALCO_DRIVER_CURL_OPTIONS} -o "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" "${URL}"; then >&2 echo "Unable to find a prebuilt ${DRIVER_NAME} eBPF probe" return 1 fi return 0 } load_bpf_probe() { if [ ! -d /sys/kernel/debug/tracing ]; then echo "* Mounting debugfs" mount -t debugfs nodev /sys/kernel/debug fi BPF_PROBE_FILENAME="${DRIVER_NAME}_${TARGET_ID}_${KERNEL_RELEASE}_${KERNEL_VERSION}.o" echo "* Filename '${BPF_PROBE_FILENAME}' is composed of:" print_filename_components if [ -n "$ENABLE_DOWNLOAD" ]; then if [ -f "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" ]; then echo "* Skipping download, eBPF probe is already present in ${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" else IFS=", " read -r -a urls <<< "${DRIVERS_REPO}" for url in "${urls[@]}"; do load_bpf_probe_download $url if [ $? -eq 0 ]; then break fi done fi fi if [ -n "$ENABLE_COMPILE" ]; then if [ -f "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" ]; then echo "* Skipping compilation, eBPF probe is already present in ${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" else load_bpf_probe_compile fi fi if [ -f "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" ]; then echo "* eBPF probe located in ${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" ln -sf "${HOME}/.falco/${DRIVER_VERSION}/${ARCH}/${BPF_PROBE_FILENAME}" "${HOME}/.falco/${DRIVER_NAME}-bpf.o" \ && echo "* Success: eBPF probe symlinked to ${HOME}/.falco/${DRIVER_NAME}-bpf.o" exit $? else >&2 echo "Unable to load the ${DRIVER_NAME} eBPF probe" exit 1 fi } print_usage() { echo "" echo "Usage:" echo " falco-driver-loader [driver] [options]" echo "" echo "Available drivers:" echo " module kernel module (default)" echo " bpf eBPF probe" echo "" echo "Options:" echo " --help show brief help" echo " --clean try to remove an already present driver installation" echo " --compile try to compile the driver locally (default true)" echo " --download try to download a prebuilt driver (default true)" echo " --source-only skip execution and allow sourcing in another script using `. falco-driver-loader`" echo " --print-env skip execution and print env variables for other tools to consume" echo "" echo "Environment variables:" echo " DRIVERS_REPO specify different URL(s) where to look for prebuilt Falco drivers (comma separated)" echo " DRIVER_NAME specify a different name for the driver" echo " DRIVER_INSECURE_DOWNLOAD whether you want to allow insecure downloads or not" echo " DRIVER_CURL_OPTIONS specify additional options to be passed to curl command used to download Falco drivers" echo " DRIVER_KERNEL_RELEASE specify the kernel release for which to download/build the driver in the same format used by 'uname -r' (e.g. '6.1.0-10-cloud-amd64')" echo " DRIVER_KERNEL_VERSION specify the kernel version for which to download/build the driver in the same format used by 'uname -v' (e.g. '#1 SMP PREEMPT_DYNAMIC Debian 6.1.38-2 (2023-07-27)')" echo "" echo "Versions:" echo " Falco version ${FALCO_VERSION}" echo " Driver version ${DRIVER_VERSION}" echo "" } ARCH=$(uname -m) DRIVER_KERNEL_RELEASE=${DRIVER_KERNEL_RELEASE:-$(uname -r)} KERNEL_RELEASE=${DRIVER_KERNEL_RELEASE} if ! hash sed > /dev/null 2>&1; then >&2 echo "This program requires sed" exit 1 fi DRIVER_KERNEL_VERSION=${DRIVER_KERNEL_VERSION:-$(uname -v)} KERNEL_VERSION=$(echo "${DRIVER_KERNEL_VERSION}" | sed 's/#\([[:digit:]]\+\).*/\1/') DRIVERS_REPO=${DRIVERS_REPO:-"@DRIVERS_REPO@"} FALCO_DRIVER_CURL_OPTIONS="-fsS --connect-timeout 5 --max-time 60 --retry 3 --retry-max-time 120" if [ -n "$DRIVER_INSECURE_DOWNLOAD" ] then FALCO_DRIVER_CURL_OPTIONS+=" -k" fi FALCO_DRIVER_CURL_OPTIONS+=" "${DRIVER_CURL_OPTIONS} if [[ -z "$MAX_RMMOD_WAIT" ]]; then MAX_RMMOD_WAIT=60 fi DRIVER_VERSION=${DRIVER_VERSION:-"@DRIVER_VERSION@"} DRIVER_NAME=${DRIVER_NAME:-"@DRIVER_NAME@"} FALCO_VERSION="@FALCO_VERSION@" TARGET_ID= get_target_id DRIVER="module" if [ -v FALCO_BPF_PROBE ]; then DRIVER="bpf" fi TMPDIR=${TMPDIR:-"/tmp"} ENABLE_COMPILE= ENABLE_DOWNLOAD= clean= has_args= has_opts= print_env= source_only= while test $# -gt 0; do case "$1" in module|bpf) if [ -n "$has_args" ]; then >&2 echo "Only one driver per invocation" print_usage exit 1 else DRIVER="$1" has_args="true" shift fi ;; -h|--help) print_usage exit 0 ;; --clean) clean="true" shift ;; --compile) ENABLE_COMPILE="yes" has_opts="true" shift ;; --download) ENABLE_DOWNLOAD="yes" has_opts="true" shift ;; --source-only) source_only="true" shift ;; --print-env) print_env="true" shift ;; --*) >&2 echo "Unknown option: $1" print_usage exit 1 ;; *) >&2 echo "Unknown driver: $1" print_usage exit 1 ;; esac done if [ -z "$has_opts" ]; then ENABLE_COMPILE="yes" ENABLE_DOWNLOAD="yes" fi if [ -n "$source_only" ]; then # Return or exit, depending if we've been sourced. (return 0 2>/dev/null) && return || exit 0 fi if [ -n "$print_env" ]; then print_as_env_vars exit 0 fi echo "* Running falco-driver-loader for: falco version=${FALCO_VERSION}, driver version=${DRIVER_VERSION}, arch=${ARCH}, kernel release=${KERNEL_RELEASE}, kernel version=${KERNEL_VERSION}" if [ "$(id -u)" != 0 ]; then >&2 echo "This program must be run as root (or with sudo)" exit 1 fi if [ "$TARGET_ID" = "undetermined" ]; then if [ -n "$ENABLE_COMPILE" ]; then ENABLE_DOWNLOAD= >&2 echo "Detected an unsupported target system, please get in touch with the Falco community. Trying to compile anyway." else >&2 echo "Detected an unsupported target system, please get in touch with the Falco community." exit 1 fi fi if [ -n "$clean" ]; then if [ -n "$has_opts" ]; then >&2 echo "Cannot use --clean with other options" exit 1 fi echo "* Running falco-driver-loader with: driver=$DRIVER, clean=yes" case $DRIVER in module) clean_kernel_module ;; bpf) >&2 echo "--clean not supported for driver=bpf" exit 1 esac else if ! hash curl > /dev/null 2>&1; then >&2 echo "This program requires curl" exit 1 fi echo "* Running falco-driver-loader with: driver=$DRIVER, compile=${ENABLE_COMPILE:-"no"}, download=${ENABLE_DOWNLOAD:-"no"}" case $DRIVER in module) load_kernel_module ;; bpf) load_bpf_probe ;; esac fi