| Server IP : 52.9.100.119 / Your IP : 216.73.216.194 Web Server : Apache/2.4.61 (Amazon) OpenSSL/1.0.2k-fips PHP/7.3.30 Phusion_Passenger/5.1.5 System : Linux ip-172-31-9-35 4.14.355-196.618.amzn1.x86_64 #1 SMP Mon Apr 7 17:09:50 UTC 2025 x86_64 User : root ( 0) PHP Version : 7.3.30 Disable Function : exec,passthru,shell_exec,system,popen,proc_open MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : OFF Directory : /usr/bin/ |
Upload File : |
#!/bin/bash
# This reduces hotpatch usage from 75 MiB RSS to 11 MiB
JVMOPTS="-Xint -XX:+UseSerialGC -Dlog4jFixerVerbose=false"
MIN_WAIT=0
WAIT_DELAY=0
# sbin is not included when run as a cron job
PATH="/sbin:/usr/sbin:$PATH"
function log() {
echo "[log4j-hotpatch] $@"
# Do not fail if syslog is being wonky
logger -d -p user.notice "$*" || true
}
function extract_proc_status() {
PID="$1"
REGEX=$2
UIDS=$(grep $REGEX /proc/$PID/status | cut -f2- -d:)
RC=$?
if test "$RC" != 0; then
return $RC
fi
echo $UIDS | cut -f 2 -d' '
}
function extract_euid() {
extract_proc_status $1 '^Uid:'
}
function extract_egid() {
extract_proc_status $1 '^Gid:'
}
function pidof_sorted() {
ALL_PIDS=$(pidof "$@")
RC=$?
if test "$RC" -ne 0; then
return $RC
fi
echo $(for pid in $ALL_PIDS; do echo $pid; done | sort)
}
function now() {
date "+%s"
}
function jvm_is_ready() {
pid=$1
if [ ! -d /proc/$pid ]; then
# Process may have exited
return 0
fi
if $(grep -q ReduceSignalUsage /proc/$pid/cmdline); then
# ReduceSignalUsage means signal will never be registered
# So we can look at thread names to see
if test $(ps -T -p $pid | egrep -c '(Signal Dispatcher|Attach Listener)') -gt 0; then
return 0;
fi
return 1;
fi
JVM_SIG_HANDLERS="0x$(grep SigCgt /proc/$pid/status|cut -f 2)"
# We look at 4 as it's bit 3, and SigCgt is a bitmask of signals
# and we're looking for signal 3.
if test $(( $JVM_SIG_HANDLERS & 0x4 )) -ne 0 ; then
return 0;
fi
return 2;
}
function wait_jvm_ready() {
pid=$1
timeout=$2
while test $timeout -gt 0; do
if jvm_is_ready $pid; then
break;
else
log "JVM $pid not started yet, waiting..."
sleep 1
fi
timeout=$(expr $timeout - 1 )
done
}
# Usage: get_java_starttime <pid>
#
# Get the start time of java process <pid>
# Note: returns 1 if not a java process
function get_java_starttime() {
local pid=$1
local STAT="$(cat /proc/${pid}/stat)"
local COMM="$(echo $STAT | cut -d ' ' -f 2)"
local STARTTIME="$(echo $STAT | cut -d ' ' -f 22)"
if [ $(echo $STAT | awk -F ' ' '{print NF}') -ne 52 ]; then
# There should be 52 fields in stat. Either the layout of stat
# has changed of there are spaces in the comm field, either way
# bail out.
return 1
fi
if [ "$COMM" != "(java)" ]; then
return 1
fi
echo "$STARTTIME"
return 0
}
# Usage: run_in_cgroup <pid> <starttime> command <args>
#
# pid: pid who's cgroups are to be matched
# starttime: expected start time for that process
# Note: this doesn't restore the cgroups after execing the command and so
# exits after so as to avoid misuse.
# Also since this is run in a separate shell any functions it calls
# must be declared
function run_in_cgroup() {
local pid=$1
local starttime=$2
shift 2
local MYPID=$$
# Move ourselves into all the cgroups of the target process
# /proc/<pid>/cgroup in the form:
# <hierarchy>:<controller(s)>:<cgroup>
while read -r line; do
controller="$(echo $line | cut -d ':' -f 2)"
if [ -z "$controller" ]; then
controller="cgroup2"
fi
group="$(echo $line | cut -d ':' -f 3)"
# / is the root group which all processes belong to
if [ "$group" != "/" ]; then
# Find the mountpoint of the cgroup
if [ "$controller" == "cgroup2" ]; then
options="-t cgroup2"
else
options="-O ${controller} -t cgroup"
fi
mountpoint="$(findmnt -r -u -n -f -o target ${options})"
RC=$?
if [ $RC -ne 0 ]; then
return $RC
fi
# Add ourselves to the cgroup
# NOTE: we might already be in it
echo $MYPID > ${mountpoint}${group}/cgroup.procs
fi
done </proc/${pid}/cgroup
# We've read everything about the process we're going to before we
# exec it. Check one last time that it's the process we expect
# by verifying the start time.
TIME="$(get_java_starttime $pid)"
if [ $? -ne 0 ]; then
exit 1
fi
if [ "$starttime" != "$TIME" ]; then
exit 1
fi
# Run the command
"$@"
exit $?
}
# Usage: gen_capsh_cmd <pid> <ambient>
#
# Generates a capsh command to run to match the bounding, inherited and (maybe)
# ambient capability sets of the target <pid>
function gen_capsh_cmd() {
local PID=$1
local AMB=$2
local CMD=$3
local MASK=0xffffffff
local OPTIONS=""
# determine the highest supported capability
for i in $(seq 0 63); do
cap="$(capsh --decode=$(printf '%x\n' $((1 << i))) | cut -d '=' -f 2)"
if ! sudo capsh --drop="$cap" 2>/dev/null; then
MASK=$(((1 << i) - 1))
break
fi
done
# Does our bounding set exceed theirs?
CAPBND=$(extract_proc_status $$ '^CapBnd:')
CAPBND_TARGET=$(extract_proc_status ${PID} '^CapBnd:')
DROPSET=$((0x${CAPBND} & ~0x${CAPBND_TARGET}))
if [ $DROPSET -ne 0 ]; then
DROPCAPS="$(capsh --decode=$(printf '%x\n' $((${MASK} & ${DROPSET}))) | cut -d '=' -f 2)"
OPTIONS="${OPTIONS}--drop=${DROPCAPS} "
fi
# Does our inherited (and/or ambient) set exceed theirs?
CAPINH=$(extract_proc_status $$ '^CapInh:')
CAPINH_TARGET=$(extract_proc_status ${PID} '^CapInh:')
if [ $AMB -eq 1 ]; then
CAPAMB=$(extract_proc_status $$ '^CapAmb:')
CAPAMB_TARGET=$(extract_proc_status ${PID} '^CapAmb:')
else
CAPAMB=0
CAPAMB_TARGET=0
fi
DROPSET=$(($((0x${CAPINH} & ~0x${CAPINH_TARGET})) | $((0x${CAPAMB} & ~0x${CAPAMB_TARGET}))))
if [ $DROPSET -ne 0 ]; then
DROPCAPS="$(capsh --decode=$(printf '%x\n' $((${MASK} & ${DROPSET}))) | cut -d '=' -f 2)"
CURRENTCAPS="$(capsh --print | grep "Current: " | cut -d ' ' -f 1 --complement)"
OPTIONS="${OPTIONS}--caps=\"${CURRENTCAPS} ${DROPCAPS}-i\" "
fi
echo "${OPTIONS} "
}
function apparmor_constrained() {
local pid=$1
if [ -e /proc/$pid/attr/apparmor/current ] &&
cat /proc/$pid/attr/apparmor/current | grep -E -q '\(enforce\)'; then
return 0
fi
return 1
}
function rm_tmpfiles() {
# Unmount any ns files
for file in $(ls ${NSDIR}); do
umount ${NSDIR}/$file
rm -f ${NSDIR}/$file
done
umount $NSDIR
rmdir $NSDIR
# Remove in case we exited before unlinking it
if [ -n "$MYNSENTER" ] && [ -e $MYNSENTER ]; then
rm -f $MYNSENTER
fi
}
# Usage: run_cmd <pid> <euid> <egid> <seccomp> <file> <apparmor> <starttime> "command <args>"
#
# Run a command as a given euid and egid, and in an environment matching that of
# the target <pid>
# If <seccomp> is 1 match the seccomp state of the target process
# <file> is the name of a file to open an fd to in the spawned process (fd 4)
# <apparmor> is the apparmor profile to match
# <starttime> is the expected start time of process <pid<> (used for verification)
# !!! NOTE WELL !!!
# Anything passed as the command to this function will be run via bash -c "$cmd"
# This means it will be evaluated ON THE HOST AS ROOT so be VERY careful when
# passing anything a container or unprivileged user controls in the command
function run_cmd() {
local PID="$1"
local MYEUID=$2
local MYEGID=$3
local SECCOMP=$4
local FILE=${5:-/dev/null}
local APPARMOR_PROFILE=$6
local STARTTIME=$7
shift 7
MYNSENTER=""
local NEED_NSENTER=0
local NSENTER=""
local NEED_CAPSH=0
local OPTIONS=""
local CMD=""
local APPARMOR
if [ "$EUID" != "0" ]; then
log "Function run_cmd() must be run as root"
return 1
fi
for exe in nsenter_aws nsenter; do
if command -v $exe > /dev/null 2>&1; then
NSENTER=$exe
break
fi
done
# Create a tmpdir for all the namespaces
NSDIR=$(mktemp -d -q --tmpdir log4j-hotpatch.XXXXXXXXXX)
if [ $? -ne 0 ]; then
log "unable to create tmp dir"
return 1
fi
# Bind mount then make unbindable; otherwise can't bind mount mount_namespace
mount --bind $NSDIR $NSDIR && \
mount --make-unbindable $NSDIR
if [ $? -ne 0 ]; then
log "unable to bind mount tmp dir $NSDIR"
rm_tmpfiles
return 1
fi
# Check which namespaces we need to enter
for ns in "mnt" "net" "ipc" "uts" "pid"; do
if [ "$(readlink /proc/$$/ns/${ns})" != "$(readlink /proc/${PID}/ns/${ns})" ]; then
if [ -z "$NSENTER" ]; then
log "nsenter required but not present on system"
rm_tmpfiles
return 1
fi
NEED_NSENTER=1
# Bind mount the namespace so we have an immutable copy
touch ${NSDIR}/${ns} && \
mount --bind /proc/${PID}/ns/${ns} ${NSDIR}/$ns
if [ $? -ne 0 ]; then
log "unable to bind mount ns $ns"
rm_tmpfiles
return 1
fi
# It happens the option is the same as the ns file except for mount
nsopt="--${ns}"
if [ "$ns" == "mnt" ]; then
nsopt="--mount"
fi
OPTIONS="${OPTIONS}${nsopt}=${NSDIR}/${ns} "
fi
done
# Check for seccomp filters
if [ "$SECCOMP" == "1" ]; then
seccomp="$(grep "^Seccomp:" /proc/${PID}/status | awk '{ print $2 }')"
if [ "$seccomp" == "1" ]; then
# Strict mode, we can't match this so just bail out
log "Target process running in Strict seccomp mode -> we can't match that"
rm_tmpfiles
return 1
elif [ "$seccomp" == "2" ]; then
# Filters applied, try to match them
if [ -z "$NSENTER" ]; then
log "Targeting process with seccomp filters -> require nsenter"
rm_tmpfiles
return 1
elif ! "$NSENTER" --help 2>/dev/null | grep -q "^ -s"; then
# We need nsenter to support the "-s" option
log "Targeting process with seccomp filters -> require \"${NSENTER} -s\" support"
rm_tmpfiles
return 1
else
NEED_NSENTER=1
OPTIONS="${OPTIONS}-s "
fi
fi
fi
# Check if the capabilities match
if [ "$MYEUID" == "0" ]; then
# For root
# - We only care about ambient, inherited and bounding capabilities
# - The effective & permitted sets are recalculated on exec
if [ "$(grep -E '^CapInh:|^CapBnd:|^CapAmb:' /proc/$$/status)" != \
"$(grep -E '^CapInh:|^CapBnd:|^CapAmb:' /proc/${PID}/status)" ]; then
if [ $NEED_NSENTER -eq 1 ]; then
# We need NSENTER with the "-P" option
if ! "$NSENTER" --help 2>/dev/null | grep -q "^ -P"; then
log "Targeting root process in container with capabilities mismatch -> require \"${NSENTER} -P\""
rm_tmpfiles
return 1
fi
# NEED_NSENTER == 1
OPTIONS="${OPTIONS}-P "
else
# Since we're not using nsenter we can match the caps with capsh
# Our capsh may not have the capability of manipulating the ambient set
# so we have to be careful here so that we don't grant capabilities from
# our ambient set which the process couldn't otherwise get.
# Match the bounding and inherited sets of the target process.
# If our ambient set exceeds the ambient set of the target process further
# reduce our inherited set to that of the ambient set of the target process
# which will clear the corresponding caps from our ambient set.
# Note: The ambient set must be a subset of the inherited set
NEED_CAPSH=1
OPTIONS="$(gen_capsh_cmd $PID 1) "
fi
fi
else
# For non-root:
# - We only care about inherited and bounding capabilities
# - The effective, permitted and ambient sets are cleared on uid change
if [ "$(grep -E '^CapInh:|^CapBnd:' /proc/$$/status)" != \
"$(grep -E '^CapInh:|^CapBnd:' /proc/${PID}/status)" ]; then
if [ $NEED_NSENTER -eq 1 ]; then
# We need NSENTER with the "-P" option
if ! "$NSENTER" --help 2>/dev/null | grep -q "^ -P"; then
log "Targeting process in container with capabilities mismatch -> require \"${NSENTER} -P\""
rm_tmpfiles
return 1
fi
NEED_NSENTER=1
OPTIONS="${OPTIONS}-P "
else
# Since we're not using nsenter we can match the caps with capsh
NEED_CAPSH=1
OPTIONS="$(gen_capsh_cmd $PID 0) "
fi
fi
fi
# If we don't need nsenter we can get away with just using setpriv
# Set no_new_privs so that the process can't gain capabilities through exec
if ! setpriv --help >/dev/null 2>&1; then
log "setpriv required but not present on system"
rm_tmpfiles
return 1
fi
# On systems that enable AppArmor, Docker applies a default profile
# allowing the containerized process ptrace access to other processes
# with the same profile. So we assume the target process's profile in
# order to allow the process to access our file descriptors via
# /proc/<pid/fd/. In the case where a process is running with a custom
# profile that doesn't permit ptrace, patching will fail.
if [ -n "$APPARMOR_PROFILE" ]; then
if command -v aa-exec > /dev/null 2>&1; then
APPARMOR="aa-exec -p $APPARMOR_PROFILE"
fi
fi
if [ $NEED_NSENTER -eq 1 ]; then
# Everywhere which sets NEED_NSENTER already checks for it's presence
# We need to be a bit careful here so that the nsenter binary can't be
# modified from within the mount namespace nsenter itself is joining.
# Do this by creating a tmp file which is made read/exec only and unlinked
MYNSENTER=$(mktemp)
if [ $? != 0 ]; then
log "unable to create tmp file"
rm_tmpfiles
return 1
fi
if ! install -m 500 $(command -v ${NSENTER}) ${MYNSENTER} >/dev/null 2>&1; then
log "unable to install ${NSENTER} to ${MYNSENTER}"
rm_tmpfiles
return 1
fi
CMD="setpriv --nnp $APPARMOR /proc/self/fd/5 -t $PID -S $MYEUID -G $MYEGID -Z ${OPTIONS}$@"
else
CMD="setpriv --nnp --reuid $MYEUID --regid $MYEGID --clear-groups $APPARMOR $@"
if [ $NEED_CAPSH -eq 1 ]; then
if ! capsh --help >/dev/null 2>&1; then
log "capsh required but not present on system"
rm_tmpfiles
return 1
fi
CMD="capsh ${OPTIONS}-- -c \"${CMD}\""
fi
fi
# Run as a new process to avoid needing to restore the cgroups.
# As a bit of fun, not all cgroups are preserved across sudo so DON'T
# call sudo AFTER run_in_cgroup. Use e.g. setpriv instead.
# Still call bash with sudo so as to close any open FDs
# In the new process fd's are assigned as follows
# 4 -> file provided as arg to function
# 5 -> nsenter binary as an fd
FUNC_A="$(declare -f run_in_cgroup)"
FUNC_B="$(declare -f get_java_starttime)"
sudo bash -c "
exec 4<${FILE}
exec 5<${MYNSENTER:-/dev/null}
if [ -n \"$MYNSENTER\" ] && ! unlink ${MYNSENTER}; then
exit 1
fi
$FUNC_A
$FUNC_B
run_in_cgroup $PID $STARTTIME $CMD
"
local RC=$?
rm_tmpfiles
return $RC
}
# Usage: is_dumpable <pid> <euid> <egid>
#
# Checks if a process <pid> is dumpable (MMF_DUMPABLE_MASK)
# Use the fact that if a process is _NOT_ dumpable then CAP_SYS_PTRACE is needed
# to read certain /proc/<pid> files
function is_dumpable() {
capsh --drop="cap_sys_ptrace" -- -c "sudo -u \"#$2\" -g \"#$3\" ls -l /proc/${1}/root" >/dev/null 2>&1
}
# Usage: can_patch <pid> <euid> <egid>
#
# Checks if a process can be patched.
# The below are reasons it might not be able to be:
# - The process is _NOT_ dumpable and we won't have CAP_SYS_PTRACE
function can_patch() {
local PID=$1
local MYEUID=$2
local MYEGID=$3
# 1. Process can't be patched if it's not dumpable and no cap_sys_ptrace
# Assume if the target process has cap_sys_ptrace (0x80000) we will to
CAPEFF=$(extract_proc_status $PID "^CapEff:")
PTRACE=$((0x${CAPEFF} & 0x80000))
if is_dumpable $PID $MYEUID $MYEGID || [ $PTRACE -ne 0 ]; then
return 0
fi
return 1
}
function apply_patch() {
local SECCOMP=1
local APPARMOR_PROFILE
log "Starting up now..."
# Remove these early such that an premature exit doesn't result in a false
# result.
rm -f /dev/shm/log4j-cve-2021-44228-hotpatch.good
rm -f /dev/shm/log4j-cve-2021-44228-hotpatch.bad
PATCHED_JVMS=0
TOTAL_JVMS=0
PIDS=$(pidof_sorted java)
RC=$?
if test "$RC" = "0"; then
log "Found JVMs with pids [$PIDS]"
for pid in $PIDS; do
log "Attempting to patch $pid"
wait_jvm_ready $pid 120
# We want to make sure that we're patching the java process that
# we think we are. That is that all of the information which we
# read about a process (exe, uid, gid, cgroup, ns etc.) relates to
# the same java process and is the one we ultimately end up
# patching. There is a window between where we read all the java
# pids, where we read the associated information and where we
# patch where the original java process could have exited and the
# pid been reused meaning some of the information we read could be
# stale. Thus we want to store a way to identify the process which
# we can check right before we patch it to ensure that it's the same
# process and we might not be executing a protentially malicious
# java exe in for example the wrong cgroup or namespace. Use the
# start time for this and check it right before we perform the
# actual patching.
STARTTIME=$(get_java_starttime $pid)
if [ $? -ne 0 ]; then
log "Process pid $pid no longer a java process, skipping."
continue
fi
# Wait 2 seconds since start time has 1 second granularity to ensure
# there is no window the java process could have been replaced by
# another java process while we read critical information. If it
# was replaced in that first 1 second where the start time would
# match that's fine since we'd patch the new java process on the
# next iteration anyway. If it was replaced by a java process which
# started later than 1 sec after then skip it, it will be patched on
# the next iteration. And if it was replaced by not a java process
# definitely skip it.
sleep 2
TIME=$(get_java_starttime $pid)
if [ $? -ne 0 ]; then
log "Process $pid no longer a java process, skipping."
continue
fi
if [ "$STARTTIME" != "$TIME" ]; then
log "Process $pid replaced, skipping."
continue
fi
# Check if the PID is associated with a container.
# Get the PID in the lowest namespace; if this isn't the same as the
# PID we see, then we need to use nsenter for operations below
container_pid=$(echo $(grep "^NSpid:" /proc/$pid/status) | awk '{ print $NF }')
if [ "$container_pid" != "$pid" ]; then
log "Running in a container with container pid: $container_pid"
else
container_pid=""
fi
TOTAL_JVMS=$(($TOTAL_JVMS + 1))
MYEUID=$(extract_euid $pid)
RC=$?
if test "$RC" -ne "0"; then
log "Failed to find effective UID for pid $pid, skipping."
continue
fi
MYEGID=$(extract_egid $pid)
RC=$?
if test "$RC" -ne "0"; then
log "Failed to find effective GID for pid $pid, skipping."
continue
fi
log "Found JVM running with effective UID of $MYEUID"
APPARMOR_PROFILE=""
if apparmor_constrained $pid; then
APPARMOR_PROFILE=$(cat /proc/$pid/attr/apparmor/current | awk '{print $1}')
fi
JVM="$(readlink /proc/$pid/exe)"
RC=$?
if test "$RC" -ne "0"; then
log "Failed to read exe name from pid $pid, skipping."
continue
fi
if grep -e "[^a-zA-Z0-9/_. -]" <<<$JVM; then
log "JVM binary \"$JVM\" for pid $pid contains special characters, skipping for safety."
continue
fi
log "Found JVM for pid $pid at $JVM"
FULL_VERSION_OUT=$(run_cmd "$pid" "$MYEUID" "$MYEGID" "$SECCOMP" "" \
"$APPARMOR_PROFILE" "$STARTTIME" "$JVM -version" 2>&1)
RC=$?
if [ $RC -ne 0 ]; then
# Try without seccomp first
SECCOMP=0
FULL_VERSION_OUT=$(run_cmd "$pid" "$MYEUID" "$MYEGID" "$SECCOMP" "" \
"$APPARMOR_PROFILE" "$STARTTIME" "$JVM -version" 2>&1)
RC=$?
fi
if [ $RC -ne 0 ]; then
log "Failed to execute JVM to determine version, skipping"
continue
fi
if [ $SECCOMP -ne 1 ]; then
log "Warning: Continuing without applying seccomp filters"
fi
FULL_VERSION=$(echo "${FULL_VERSION_OUT}" | head -1)
log "JVM version is $FULL_VERSION"
KIND=$(echo $FULL_VERSION | cut -f1 -d' ')
SEMVER=$(echo $FULL_VERSION | cut -f3 -d' ' | sed -e 's:"::g')
MAJOR=$(echo $SEMVER | cut -f1 -d'.')
MAJMIN=$(echo $SEMVER | cut -f1-2 -d'.')
if test "$KIND" != "openjdk" -a "$KIND" != "java"; then
log "Skipping unsupported JVM kind: $KIND"
continue
fi
log "Identified JVM[$KIND] of ($FULL_VERSION) with major version $MAJOR"
if ! can_patch $pid $MYEUID $MYEGID; then
log "Skipping unpatchable java process: $pid"
continue
fi
if test "$MAJOR" = "11" -o "$MAJOR" = "15"; then
log "Using Java 11 hotpatch"
if [ ! -z "$container_pid" ]; then
# The jar is provided to the java process via fd-4
run_cmd "$pid" "$MYEUID" "$MYEGID" "$SECCOMP" \
"/usr/share/log4j-cve-2021-44228-hotpatch/jdk11/Log4jHotPatch.jar" "$APPARMOR_PROFILE" "$STARTTIME" \
"$JVM $JVMOPTS -cp /proc/self/fd/4 Log4jHotPatch $container_pid"
RC=$?
else
run_cmd "$pid" "$MYEUID" "$MYEGID" "$SECCOMP" "" "$APPARMOR_PROFILE" "$STARTTIME" \
"$JVM $JVMOPTS -cp /usr/share/log4j-cve-2021-44228-hotpatch/jdk11/Log4jHotPatch.jar Log4jHotPatch $pid"
RC=$?
fi
log "Hotpatch application returned $RC"
if test "$RC" = 0; then
PATCHED_JVMS=$(($PATCHED_JVMS + 1))
fi
elif test "$MAJOR" = "17"; then
log "Using Java 17 hotpatch"
if [ ! -z "$container_pid" ]; then
# The jar is provided to the java process via fd-4
run_cmd "$pid" "$MYEUID" "$MYEGID" "$SECCOMP" \
"/usr/share/log4j-cve-2021-44228-hotpatch/jdk17/Log4jHotPatchFat.jar" "$APPARMOR_PROFILE" "$STARTTIME" \
"$JVM $JVMOPTS -cp /proc/self/fd/4 -DfatJar=/proc/self/fd/4 Log4jHotPatch17 $container_pid"
RC=$?
else
run_cmd "$pid" "$MYEUID" "$MYEGID" "$SECCOMP" "" "$APPARMOR_PROFILE" "$STARTTIME" \
"$JVM $JVMOPTS -cp /usr/share/log4j-cve-2021-44228-hotpatch/jdk17/Log4jHotPatchFat.jar \
-DfatJar=/usr/share/log4j-cve-2021-44228-hotpatch/jdk17/Log4jHotPatchFat.jar Log4jHotPatch17 $pid"
RC=$?
fi
log "Hotpatch application returned $RC"
if test "$RC" = 0; then
PATCHED_JVMS=$(($PATCHED_JVMS + 1))
fi
elif test "$MAJMIN" = "1.8"; then
log "Using Java 8 hotpatch"
BINDIR=$(dirname "$JVM")
log "Extracted BINDIR of $BINDIR"
BASEDIR=$(dirname "$BINDIR")
log "Extracted BASEDIR of $BASEDIR"
DIRNAME=$(basename "$BASEDIR")
log "Extracted DIRNAME of $DIRNAME"
# Sometimes java is invoked as $JAVAHOME/jre/bin/java verses $JAVAHOME/bin/java, try to correct for this.
if test "$DIRNAME" = "jre"; then
BASEDIR=$(dirname "$BASEDIR")
fi
if [ ! -z "$container_pid" ]; then
# Test for existence of the tools.jar file in the container via
# /proc/<pid>/root. Since this is done in our mount_namespace
# the container could have created a symlink to somewhere the jar
# doesn't exist to avoid being patched, but to do this you would
# need root or to be in control of the make-up of the container
# anyway in which case you can just delete/omit it to the same end.
if ! test -e "/proc/$pid/root/$BASEDIR/lib/tools.jar"; then
log "Could not find tools.jar in JVM lib, skipping"
continue
fi
else
if ! test -e "$BASEDIR/lib/tools.jar"; then
log "Could not find tools.jar in JVM lib, skipping"
continue
fi
fi
if [ ! -z "$container_pid" ]; then
# The jar is provided to the java process via fd-4
run_cmd "$pid" "$MYEUID" "$MYEGID" "$SECCOMP" \
"/usr/share/log4j-cve-2021-44228-hotpatch/jdk8/Log4jHotPatch.jar" "$APPARMOR_PROFILE" "$STARTTIME" \
"$JVM $JVMOPTS -cp \"$BASEDIR/lib/tools.jar:/proc/self/fd/4\" Log4jHotPatch $container_pid"
RC=$?
else
run_cmd "$pid" "$MYEUID" "$MYEGID" "$SECCOMP" "" "$APPARMOR_PROFILE" "$STARTTIME" \
"$JVM $JVMOPTS -cp \"$BASEDIR/lib/tools.jar:/usr/share/log4j-cve-2021-44228-hotpatch/jdk8/Log4jHotPatch.jar\" Log4jHotPatch $pid"
RC=$?
fi
log "Hotpatch application returned $RC"
if test "$RC" = 0; then
PATCHED_JVMS=$(($PATCHED_JVMS + 1))
fi
else
log "Unsupported Java major version $MAJOR, skipping"
fi
done
else
log "No JVMs found, exiting gracefully"
fi
# Leave a file to indicate that we're either patched or unpatched
if test "$PATCHED_JVMS" -lt "$TOTAL_JVMS"; then
touch /dev/shm/log4j-cve-2021-44228-hotpatch.bad
else
touch /dev/shm/log4j-cve-2021-44228-hotpatch.good
fi
}
function main() {
while test "$1"; do
OPT="$1"
shift
case "$OPT" in
-w)
WAIT_DELAY="$1"
shift
;;
-m)
MIN_WAIT="$1"
shift
;;
*)
echo "Unexpected option '$OPT'"
break
;;
esac
done
# This file allows disabling the hotpatch from being reapplied
# in case something goes wrong and we need an Andon Cord.
if test -e /dev/shm/log4j-cve-2021-44228-hotpatch.kill -o -e /etc/log4j-cve-2021-44228-hotpatch.kill; then
log "Found kill file, not applying patch."
else
apply_patch
fi
sleep "$MIN_WAIT"
START="$(now)"
NOW="$START"
END=$(($START + $WAIT_DELAY))
while test "$NOW" -lt "$END"; do
NEW_PIDS=$(pidof_sorted java)
if test "$PIDS" = "$NEW_PIDS"; then
sleep 1
else
break
fi
NOW="$(now)"
done
}
main "$@"