Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 6 additions & 14 deletions Dockerfile
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,7 @@ RUN ./build_linux.sh && \
cd /usr/src/plugins/bin
WORKDIR /

FROM registry.ci.openshift.org/ocp/builder:rhel-8-golang-1.24-openshift-4.21 AS rhel8
ADD . /usr/src/plugins
WORKDIR /usr/src/plugins
ENV CGO_ENABLED=0
RUN ./build_linux.sh && \
cd /usr/src/plugins/bin
WORKDIR /

FROM registry.ci.openshift.org/ocp/builder:rhel-8-golang-1.24-openshift-4.21 AS windows
FROM registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.24-openshift-4.21 AS windows
ADD . /usr/src/plugins
WORKDIR /usr/src/plugins
ENV CGO_ENABLED=0
Expand All @@ -25,13 +17,13 @@ WORKDIR /

FROM registry.ci.openshift.org/ocp/4.21:base-rhel9
RUN mkdir -p /usr/src/plugins/bin && \
mkdir -p /usr/src/plugins/rhel8/bin && \
mkdir -p /usr/src/plugins/rhel9/bin && \
mkdir -p /usr/src/plugins/rhel10/bin && \
mkdir -p /usr/src/plugins/windows/bin
COPY --from=rhel8 /usr/src/plugins/bin/* /usr/src/plugins/rhel8/bin/
# pod container image is RHEL8 based, so use rhel8
COPY --from=rhel8 /usr/src/plugins/bin/* /usr/src/plugins/bin/
COPY --from=rhel9 /usr/src/plugins/bin/* /usr/src/plugins/rhel9/bin/
COPY --from=rhel9 /usr/src/plugins/bin/* /usr/src/plugins/bin/
RUN ln /usr/src/plugins/bin/* /usr/src/plugins/rhel9/bin/
# For now assume rhel9 binaries are compatible with rhel10
RUN ln /usr/src/plugins/bin/* /usr/src/plugins/rhel10/bin/
Comment on lines +24 to +26
Copy link
Copy Markdown

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🏁 Script executed:

#!/bin/bash
set -euo pipefail

engine="podman"
command -v podman >/dev/null 2>&1 || engine="docker"

$engine build -t cni-hardlink-check -f Dockerfile .

$engine run --rm cni-hardlink-check sh -ec '
for f in /usr/src/plugins/bin/*; do
  b=$(basename "$f")
  i_bin=$(stat -c "%d:%i" "$f")
  i_r9=$(stat -c "%d:%i" "/usr/src/plugins/rhel9/bin/$b")
  i_r10=$(stat -c "%d:%i" "/usr/src/plugins/rhel10/bin/$b")
  if [ "$i_bin" != "$i_r9" ] || [ "$i_bin" != "$i_r10" ]; then
    echo "hardlink mismatch: $b -> bin=$i_bin rhel9=$i_r9 rhel10=$i_r10"
    exit 1
  fi
done
echo "all hardlinks match"
'

Repository: openshift/containernetworking-plugins

Length of output: 128


🏁 Script executed:

# First, let's see the full Dockerfile context around the flagged lines
fd -t f -name "Dockerfile" | head -5

Repository: openshift/containernetworking-plugins

Length of output: 312


🌐 Web query:

Docker overlayfs hardlink inode preservation across layers RUN

💡 Result:

No, Docker's overlayfs (specifically overlay2, the current default) does not preserve the same inode numbers for hardlinks created in a RUN instruction across different image layers. Hardlinks created within the same RUN layer (single layer) share the same inode and are preserved correctly during layer commit, as shown in examples where ls -li reports the same inode and link count >1 intra-layer. However, when creating hardlinks in subsequent RUN instructions to files from previous layers, the inode is not preserved across layers: the new hardlink gets a new inode in the current layer's diff directory, resulting in separate storage (duplication of file data in the layer tarball), increased layer size, and different inode numbers when inspected in the final image. This is due to how layers are committed to tar archives—tar only preserves hardlinks if both links are in the same layer's changes; cross-layer references cannot be maintained as inodes are layer-local. Official Docker docs confirm layers contain "hard links to data shared with lower layers" at the storage driver level for mounting, but this does not extend to preserving identical inodes for user-created hardlinks across committed layers. Recent issues (2024) confirm this behavior persists with BuildKit.

Citations:


🌐 Web query:

overlayfs copy-on-write hardlink behavior separate layers

💡 Result:

In OverlayFS, hard links do not span across separate layers (upper and lower). The merged view presents files from either the upper or lower layer, but since upper and lower are distinct filesystem trees (often on the same underlying fs but treated separately), hard links cannot connect inodes across layers. When a write operation triggers copy-up of a file from the lower layer to the upper layer, OverlayFS creates a new independent file in the upper layer. If the original lower file had multiple hard links (nlink > 1), this copy-up "breaks" those links by default: the upper copy is a separate inode, so changes to it do not propagate to other lower hard link names, and vice versa. The kernel documentation explicitly states: "If this feature [inode index] is disabled and a file with multiple hard links is copied up, then this will 'break' the link. Changes will not be propagated to other names referring to the same inode." Creating a new hard link in the overlay (to a lower file) also triggers copy-up of the target file to the upper layer before linking. The "index=on" mount option (inodes index feature) preserves hard link semantics during copy-up by creating an index entry (a hard link in a hidden index directory to the upper inode, keyed by the lower file handle). This ensures consistent lookup of the same upper inode for all lower hard links, maintaining the union nlink count. Without index=on, hard links are broken on copy-up. Sources confirm hard links do not work across layers natively, and copy-up creates separate inodes unless indexing is used.

Citations:


🏁 Script executed:

cat -n Dockerfile | head -40

Repository: openshift/containernetworking-plugins

Length of output: 1619


Combine both hardlink operations in a single RUN layer to preserve inode identity and avoid storage duplication.

Lines 24 and 26 create hardlinks in separate Docker layers. Due to how overlayfs manages layer composition, each subsequent RUN instruction creates a new inode for hardlinked files, resulting in duplicate file data stored in each layer's tarball. Consolidating both ln commands into one RUN layer ensures all hardlinks reference the same inode across bin, rhel9/bin, and rhel10/bin.

Proposed fix
-RUN ln /usr/src/plugins/bin/* /usr/src/plugins/rhel9/bin/
-# For now assume rhel9 binaries are compatible with rhel10
-RUN ln /usr/src/plugins/bin/* /usr/src/plugins/rhel10/bin/
+# For now assume rhel9 binaries are compatible with rhel10
+RUN ln /usr/src/plugins/bin/* /usr/src/plugins/rhel9/bin/ && \
+    ln /usr/src/plugins/bin/* /usr/src/plugins/rhel10/bin/
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
RUN ln /usr/src/plugins/bin/* /usr/src/plugins/rhel9/bin/
# For now assume rhel9 binaries are compatible with rhel10
RUN ln /usr/src/plugins/bin/* /usr/src/plugins/rhel10/bin/
# For now assume rhel9 binaries are compatible with rhel10
RUN ln /usr/src/plugins/bin/* /usr/src/plugins/rhel9/bin/ && \
ln /usr/src/plugins/bin/* /usr/src/plugins/rhel10/bin/
🤖 Prompt for AI Agents
Verify each finding against the current code and only fix it if needed.

In `@Dockerfile` around lines 24 - 26, The two separate RUN layers creating
hardlinks should be combined into a single RUN so the hardlinks share the same
inode; update the Dockerfile so the ln operations that link
/usr/src/plugins/bin/* into /usr/src/plugins/rhel9/bin/ and
/usr/src/plugins/rhel10/bin/ are executed in one RUN instruction (i.e., run both
ln commands together in the same shell invocation) to avoid duplicate file data
across layers and preserve inode identity.

COPY --from=windows /usr/src/plugins/bin/* /usr/src/plugins/windows/bin/

LABEL io.k8s.display-name="Container Networking Plugins" \
Expand Down
2 changes: 1 addition & 1 deletion build_linux.sh
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ for d in $PLUGINS; do
plugin="$(basename "$d")"
if [ "${plugin}" != "windows" ]; then
echo " $plugin"
${GO:-go} build -o "${PWD}/bin/$plugin" "$@" ./"$d"
${GO:-go} build -o "${PWD}/bin/$plugin" -ldflags "-s -w" "$@" ./"$d"
fi
fi
done