#!/usr/bin/env bash # # Copyright (C) 2019 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 [ ! -z "${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 [ ! -z "${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 agent container 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 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 } load_kernel_probe() { if ! hash lsmod > /dev/null 2>&1; then echo "This program requires lsmod" exit 1 fi if ! hash modprobe > /dev/null 2>&1; then echo "This program requires modprobe" exit 1 fi if ! hash rmmod > /dev/null 2>&1; then echo "This program requires rmmod" exit 1 fi echo "* Unloading ${PROBE_NAME}, if present" rmmod "${PROBE_NAME}" 2>/dev/null WAIT_TIME=0 KMOD_NAME=$(echo "${PROBE_NAME}" | tr "-" "_") while lsmod | grep "${KMOD_NAME}" > /dev/null 2>&1 && [ $WAIT_TIME -lt $MAX_RMMOD_WAIT ]; do if rmmod "${PROBE_NAME}" 2>/dev/null; then echo "* Unloading ${PROBE_NAME} succeeded after ${WAIT_TIME}s" break fi ((++WAIT_TIME)) if (( $WAIT_TIME % 5 == 0 )); then echo "* ${PROBE_NAME} still loaded, waited ${WAIT_TIME}s (max wait ${MAX_RMMOD_WAIT}s)" fi sleep 1 done if lsmod | grep "${KMOD_NAME}" > /dev/null 2>&1; then echo "* ${PROBE_NAME} seems to still be loaded, hoping the best" exit 0 fi # skip dkms on UEK hosts because it will always fail if [[ $(uname -r) == *uek* ]]; then echo "* Skipping dkms install for UEK host" else echo "* Running dkms install for ${PACKAGE_NAME}" if dkms install -m "${PACKAGE_NAME}" -v "${FALCO_VERSION}" -k "${KERNEL_RELEASE}"; then echo "* Trying to load a dkms ${PROBE_NAME}, if present" if insmod "/var/lib/dkms/${PACKAGE_NAME}/${FALCO_VERSION}/${KERNEL_RELEASE}/${ARCH}/module/${PROBE_NAME}.ko" > /dev/null 2>&1; then echo "${PROBE_NAME} found and loaded in dkms" exit 0 elif insmod "/var/lib/dkms/${PACKAGE_NAME}/${FALCO_VERSION}/${KERNEL_RELEASE}/${ARCH}/module/${PROBE_NAME}.ko.xz" > /dev/null 2>&1; then echo "${PROBE_NAME} found and loaded in dkms (xz)" exit 0 else echo "* Unable to insmod" fi else DKMS_LOG="/var/lib/dkms/${PACKAGE_NAME}/${FALCO_VERSION}/build/make.log" if [ -f "${DKMS_LOG}" ]; then echo "* Running dkms build failed, dumping ${DKMS_LOG}" cat "${DKMS_LOG}" else echo "* Running dkms build failed, couldn't find ${DKMS_LOG}" fi fi fi echo "* Trying to load a system ${PROBE_NAME}, if present" if modprobe "${PROBE_NAME}" > /dev/null 2>&1; then echo "${PROBE_NAME} found and loaded with modprobe" exit 0 fi echo "* Trying to find precompiled ${PROBE_NAME} for ${KERNEL_RELEASE}" get_kernel_config local FALCO_PROBE_FILENAME="${PROBE_NAME}-${FALCO_VERSION}-${ARCH}-${KERNEL_RELEASE}-${HASH}.ko" if [ -f "${HOME}/.falco/${FALCO_PROBE_FILENAME}" ]; then echo "Found precompiled module at ~/.falco/${FALCO_PROBE_FILENAME}, loading module" insmod "${HOME}/.falco/${FALCO_PROBE_FILENAME}" exit $? fi local URL URL=$(echo "${PROBE_URL}/${PACKAGES_REPOSITORY}/sysdig-probe-binaries/${FALCO_PROBE_FILENAME}" | sed s/+/%2B/g) echo "* Trying to download precompiled module from ${URL}" if curl --create-dirs "${FALCO_PROBE_CURL_OPTIONS}" -o "${HOME}/.falco/${FALCO_PROBE_FILENAME}" "${URL}"; then echo "Download succeeded, loading module" insmod "${HOME}/.falco/${FALCO_PROBE_FILENAME}" exit $? else echo "Download failed, consider compiling your own ${PROBE_NAME} and loading it or getting in touch with the Falco community" exit 1 fi } load_bpf_probe() { echo "* Mounting debugfs" if [ ! -d /sys/kernel/debug/tracing ]; then mount -t debugfs nodev /sys/kernel/debug fi get_kernel_config if [ ! -z "${HOST_ROOT}" ] && [ -f "${HOST_ROOT}/etc/os-release" ]; then . "${HOST_ROOT}/etc/os-release" if [ "${ID}" == "cos" ]; then COS=1 fi fi if [ ! -z "${HOST_ROOT}" ] && [ -f "${HOST_ROOT}/etc/VERSION" ]; then MINIKUBE=1 MINIKUBE_VERSION="$(cat ${HOST_ROOT}/etc/VERSION)" fi local BPF_PROBE_FILENAME="${BPF_PROBE_NAME}-${FALCO_VERSION}-${ARCH}-${KERNEL_RELEASE}-${HASH}.o" if [ ! -f "${HOME}/.falco/${BPF_PROBE_FILENAME}" ]; then 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 [ -n "${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 # Note: this overrides the KERNELDIR set while untarring the tarball export KERNELDIR=`pwd` 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 # 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 [ -n "${MINIKUBE}" ]; then echo "* Minikube detected (${MINIKUBE_VERSION}), using linux kernel sources for minikube kernel" local kernel_version=$(uname -r) 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=$(uname -r | cut -d. -f1) local -r kernel_version=$(uname -r | cut -d- -f1) KERNEL_EXTRA_VERSION="-$(uname -r | 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 echo "* Downloading ${BPF_KERNEL_SOURCES_URL}" mkdir -p /tmp/kernel cd /tmp/kernel cd `mktemp -d -p /tmp/kernel` if ! curl -o kernel-sources.tgz --create-dirs "${FALCO_PROBE_CURL_OPTIONS}" "${BPF_KERNEL_SOURCES_URL}"; then exit 1; fi echo "* Extracting kernel sources" mkdir kernel-sources && tar xf kernel-sources.tgz -C kernel-sources --strip-components "${STRIP_COMPONENTS}" cd kernel-sources export KERNELDIR=`pwd` 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 BPF probe ${BPF_PROBE_NAME} (${BPF_PROBE_FILENAME})" make -C "/usr/src/${PACKAGE_NAME}-${FALCO_VERSION}/bpf" > /dev/null mkdir -p ~/.falco mv "/usr/src/${PACKAGE_NAME}-${FALCO_VERSION}/bpf/probe.o" "${HOME}/.falco/${BPF_PROBE_FILENAME}" if [ -n "${BPF_KERNEL_SOURCES_URL}" ]; then rm -r /tmp/kernel fi fi if [ ! -f "${HOME}/.falco/${BPF_PROBE_FILENAME}" ]; then local URL URL=$(echo "${PROBE_URL}/${PACKAGES_REPOSITORY}/sysdig-probe-binaries/${BPF_PROBE_FILENAME}" | sed s/+/%2B/g) echo "* Trying to download precompiled BPF probe from ${URL}" curl --create-dirs "${FALCO_PROBE_CURL_OPTIONS}" -o "${HOME}/.falco/${BPF_PROBE_FILENAME}" "${URL}" fi if [ -f "${HOME}/.falco/${BPF_PROBE_FILENAME}" ]; then if [ ! -f /proc/sys/net/core/bpf_jit_enable ]; then echo "**********************************************************" echo "** BPF doesn't have JIT enabled, performance might be **" echo "** degraded. Please ensure to run on a kernel with **" echo "** CONFIG_BPF_JIT enabled and/or use --net=host if **" echo "** running inside a container. **" echo "**********************************************************" fi echo "* BPF probe located, it's now possible to start sysdig" ln -sf "${HOME}/.falco/${BPF_PROBE_FILENAME}" "${HOME}/.falco/${BPF_PROBE_NAME}.o" exit $? else echo "* Failure to find a BPF probe" exit 1 fi } ARCH=$(uname -m) KERNEL_RELEASE=$(uname -r) SCRIPT_NAME=$(basename "${0}") PROBE_URL=${PROBE_URL:-https://s3.amazonaws.com/download.draios.com} if [ -n "$PROBE_INSECURE_DOWNLOAD" ] then FALCO_PROBE_CURL_OPTIONS=-fsSk else FALCO_PROBE_CURL_OPTIONS=-fsS fi MAX_RMMOD_WAIT=60 if [[ $# -ge 1 ]]; then MAX_RMMOD_WAIT=$1 fi if [ -z "${PACKAGES_REPOSITORY}" ]; then PACKAGES_REPOSITORY="stable" fi if [ "${SCRIPT_NAME}" = "falco-probe-loader" ]; then if [ -z "$FALCO_VERSION" ]; then FALCO_VERSION=$(falco --version | cut -d' ' -f3) fi PROBE_NAME="falco-probe" BPF_PROBE_NAME="falco-probe-bpf" PACKAGE_NAME="falco" else echo "This script must be called as falco-probe-loader" exit 1 fi if [ "$(id -u)" != 0 ]; then echo "Installer must be run as root (or with sudo)." exit 1 fi if ! hash curl > /dev/null 2>&1; then echo "This program requires curl" exit 1 fi if [ -v BPF_PROBE ] || [ "${1}" = "bpf" ]; then load_bpf_probe else load_kernel_probe fi