From 01f08a498e3a6efffbff6d95b6cf7d2d68788ba8 Mon Sep 17 00:00:00 2001 From: Zarir Hamza Date: Fri, 15 May 2026 12:33:43 -0400 Subject: [PATCH] fix: compile ffi from source per-runtime so AppSec loads on Ruby 3.2 MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit dd-trace-rb 2.30's AppSec component (which v3.28.0 bundled into all Datadog-Ruby{3-2,3-3,3-4,4-0} layers) `require`s libddwaf → ffi → ffi_c at boot when DD_APPSEC_ENABLED=true. The `ffi-1.17.4-x86_64-linux-gnu` precompiled gem on rubygems ships ABI-specific subdirs (`lib/3.3/ffi_c.so`, `lib/3.4/ffi_c.so`, etc.) and ffi 1.17 does NOT include a Ruby 3.2 subdir. Result: every cold start on Datadog-Ruby3-2 crashes with Init: cannot load such file -- ffi_c …stranding any Ruby 3.2 Lambda customer who turns on AppSec. Force ffi to be (re)installed from source in the per-runtime builder right after the datadog gem install. The build container is the matching `ruby:X.Y` Docker image, so the resulting `ffi_c.so` is compiled against the same Ruby ABI as the target Lambda runtime by construction, regardless of which ABIs the rubygems precompiled bundle chooses to ship. Adds `make libffi-dev pkg-config` to apt-get so `gem install --platform ruby` has the headers + linker it needs. Confirmed via single-lambda sandbox repro 2026-05-15: - Datadog-Ruby3-2:28 + DD_APPSEC_ENABLED=true → Init - Datadog-Ruby3-4:28 + DD_APPSEC_ENABLED=true → HTTP 200, "ok" After this PR ships and the next prod layer publishes, the Ruby 3.2 case should also return HTTP 200. xfailed in DataDog/serverless-e2e-tests#223 in the meantime. Also defensive against future ffi releases dropping additional Ruby ABIs from their precompiled bundle. --- Dockerfile | 27 ++++++++++++++++++++++++++- 1 file changed, 26 insertions(+), 1 deletion(-) diff --git a/Dockerfile b/Dockerfile index 432cc5e..7eb42cd 100644 --- a/Dockerfile +++ b/Dockerfile @@ -7,7 +7,9 @@ RUN echo "git_ref: $git_ref" COPY . /var/task/datadog-lambda-rb WORKDIR /var/task/datadog-lambda-rb RUN apt-get update -RUN apt-get install -y gcc zip binutils +# `libffi-dev` + `make` are needed to compile the `ffi` gem from source +# below — see the rebuild step after the datadog gem install. +RUN apt-get install -y gcc zip binutils make libffi-dev pkg-config # Install this gem RUN gem build datadog-lambda @@ -26,6 +28,29 @@ RUN set -eux; \ gem install ./datadog-*.gem --install-dir "/opt/ruby/gems/$runtime"; \ fi +# Force `ffi` to be (re)built from source in this per-runtime builder so +# the resulting `ffi_c.so` matches the target Lambda runtime's Ruby ABI +# exactly. The precompiled `ffi-x.y.z-x86_64-linux-gnu` gem shipped via +# rubygems ships ABI-specific subdirs (`lib/3.3/ffi_c.so`, +# `lib/3.4/ffi_c.so`, …) and `ffi 1.17` does NOT include a Ruby 3.2 +# subdir. Without this step, dd-trace-rb 2.30's AppSec component (which +# `require`s libddwaf → ffi → ffi_c) crashes the function at boot on +# Ruby 3.2 when `DD_APPSEC_ENABLED=true`: +# +# Init: cannot load such file -- ffi_c +# +# Confirmed via a single-lambda sandbox repro on 2026-05-15 +# (datadog-lambda-rb v3.28.0, Datadog-Ruby3-2:28 + DD_APPSEC_ENABLED=true). +# Same combo on Ruby 3.4 / 4.0 works because the precompiled package does +# bundle binaries for their ABIs, but compiling from source here is a +# permanent fix that's defensive against future `ffi` releases dropping +# additional Ruby ABIs from the precompiled bundle. +RUN set -eux; \ + gem uninstall ffi --all --ignore-dependencies --executables --force \ + --install-dir "/opt/ruby/gems/$runtime" || true; \ + gem install ffi --platform=ruby \ + --install-dir "/opt/ruby/gems/$runtime" + WORKDIR /opt # Remove native extension debase-ruby_core_source (25MB) runtimes below Ruby 2.6 RUN rm -rf ./ruby/gems/$runtime/gems/debase-ruby_core_source*/