diff --git a/scripts/falco-probe-loader b/scripts/falco-probe-loader new file mode 100755 index 00000000..d2b77a3d --- /dev/null +++ b/scripts/falco-probe-loader @@ -0,0 +1,411 @@ +#!/bin/bash +# +# Copyright (C) 2013-2018 Draios Inc dba Sysdig. +# +# This file is part of sysdig . +# +# 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 sysdig-probe looking +# for it in a bunch of ways. Convenient when running sysdig 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 "${SYSDIG_HOST_ROOT}" ] && [ -f "${SYSDIG_HOST_ROOT}/boot/config-${KERNEL_RELEASE}" ]; then + echo "Found kernel config at ${SYSDIG_HOST_ROOT}/boot/config-${KERNEL_RELEASE}" + KERNEL_CONFIG_PATH="${SYSDIG_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 "${SYSDIG_HOST_ROOT}" ] && [ -f "${SYSDIG_HOST_ROOT}/usr/lib/ostree-boot/config-${KERNEL_RELEASE}" ]; then + echo "Found kernel config at ${SYSDIG_HOST_ROOT}/usr/lib/ostree-boot/config-${KERNEL_RELEASE}" + KERNEL_CONFIG_PATH="${SYSDIG_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 -> $SYSDIG_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 "${SYSDIG_VERSION}" -k "${KERNEL_RELEASE}"; then + echo "* Trying to load a dkms ${PROBE_NAME}, if present" + + if insmod "/var/lib/dkms/${PACKAGE_NAME}/${SYSDIG_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}/${SYSDIG_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}/${SYSDIG_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 SYSDIG_PROBE_FILENAME="${PROBE_NAME}-${SYSDIG_VERSION}-${ARCH}-${KERNEL_RELEASE}-${HASH}.ko" + + if [ -f "${HOME}/.sysdig/${SYSDIG_PROBE_FILENAME}" ]; then + echo "Found precompiled module at ~/.sysdig/${SYSDIG_PROBE_FILENAME}, loading module" + insmod "${HOME}/.sysdig/${SYSDIG_PROBE_FILENAME}" + exit $? + fi + + local URL + URL=$(echo "${SYSDIG_PROBE_URL}/${SYSDIG_REPOSITORY}/sysdig-probe-binaries/${SYSDIG_PROBE_FILENAME}" | sed s/+/%2B/g) + + echo "* Trying to download precompiled module from ${URL}" + if curl --create-dirs "${SYSDIG_PROBE_CURL_OPTIONS}" -o "${HOME}/.sysdig/${SYSDIG_PROBE_FILENAME}" "${URL}"; then + echo "Download succeeded, loading module" + insmod "${HOME}/.sysdig/${SYSDIG_PROBE_FILENAME}" + exit $? + else + echo "Download failed, consider compiling your own ${PROBE_NAME} and loading it or getting in touch with the sysdig 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 "${SYSDIG_HOST_ROOT}" ] && [ -f "${SYSDIG_HOST_ROOT}/etc/os-release" ]; then + . "${SYSDIG_HOST_ROOT}/etc/os-release" + + if [ "${ID}" == "cos" ]; then + COS=1 + fi + fi + + if [ ! -z "${SYSDIG_HOST_ROOT}" ] && [ -f "${SYSDIG_HOST_ROOT}/etc/VERSION" ]; then + MINIKUBE=1 + MINIKUBE_VERSION="$(cat ${SYSDIG_HOST_ROOT}/etc/VERSION)" + fi + + local BPF_PROBE_FILENAME="${BPF_PROBE_NAME}-${SYSDIG_VERSION}-${ARCH}-${KERNEL_RELEASE}-${HASH}.o" + + if [ ! -f "${HOME}/.sysdig/${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 "${SYSDIG_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 "${SYSDIG_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}-${SYSDIG_VERSION}/bpf" > /dev/null + + mkdir -p ~/.sysdig + mv "/usr/src/${PACKAGE_NAME}-${SYSDIG_VERSION}/bpf/probe.o" "${HOME}/.sysdig/${BPF_PROBE_FILENAME}" + + if [ -n "${BPF_KERNEL_SOURCES_URL}" ]; then + rm -r /tmp/kernel + fi + fi + + if [ ! -f "${HOME}/.sysdig/${BPF_PROBE_FILENAME}" ]; then + local URL + URL=$(echo "${SYSDIG_PROBE_URL}/${SYSDIG_REPOSITORY}/sysdig-probe-binaries/${BPF_PROBE_FILENAME}" | sed s/+/%2B/g) + + echo "* Trying to download precompiled BPF probe from ${URL}" + + curl --create-dirs "${SYSDIG_PROBE_CURL_OPTIONS}" -o "${HOME}/.sysdig/${BPF_PROBE_FILENAME}" "${URL}" + fi + + if [ -f "${HOME}/.sysdig/${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}/.sysdig/${BPF_PROBE_FILENAME}" "${HOME}/.sysdig/${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}") +SYSDIG_PROBE_URL=${SYSDIG_PROBE_URL:-https://s3.amazonaws.com/download.draios.com} +if [ -n "$SYSDIG_PROBE_INSECURE_DOWNLOAD" ] +then + SYSDIG_PROBE_CURL_OPTIONS=-fsSk +else + SYSDIG_PROBE_CURL_OPTIONS=-fsS +fi + +MAX_RMMOD_WAIT=60 +if [[ $# -ge 1 ]]; then + MAX_RMMOD_WAIT=$1 +fi + +if [ -z "${SYSDIG_REPOSITORY}" ]; then + SYSDIG_REPOSITORY="stable" +fi + +if [ "${SCRIPT_NAME}" = "falco-probe-loader" ]; then + if [ -z "$SYSDIG_VERSION" ]; then + SYSDIG_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 SYSDIG_BPF_PROBE ] || [ "${1}" = "bpf" ]; then + load_bpf_probe +else + load_kernel_probe +fi