[PR #15504] server/images: prevent skipVerify map collision with duplicate digests #46426

Open
opened 2026-04-25 01:51:49 -05:00 by GiteaMirror · 0 comments
Owner

📋 Pull Request Information

Original PR: https://github.com/ollama/ollama/pull/15504
Author: @vigneshakaviki
Created: 4/11/2026
Status: 🔄 Open

Base: mainHead: fix/skip-verify-duplicate-digest


📝 Commits (1)

  • 34c7924 server/images: prevent skipVerify map collision with duplicate digests

📊 Changes

2 files changed (+157 additions, -1 deletions)

View changed files

📝 server/images.go (+10 -1)
📝 server/images_test.go (+147 -0)

📄 Description

Summary

Fixes #15485 — blob hash verification is skipped when a manifest's config and layer share the same digest, enabling SSRF response exfiltration from rogue OCI registries.

Root Cause

In PullModel, the skipVerify map tracks whether each blob needs hash verification after download. The map is keyed by digest. When config and layer share the same digest:

  1. Layer downloads first → skipVerify[digest] = false (fresh download, needs verification)
  2. Config processes second → blob already on disk → skipVerify[digest] = true (overwrites!)
  3. Verification loop sees true → skips verifyBlob entirely

A rogue registry can exploit this by serving duplicate digests with a 307 redirect to internal endpoints. The SSRF response is written as a blob, verification is bypassed, and the attacker can exfiltrate via /api/copy + /api/push.

Fix

Use logical AND when updating the skipVerify map: existing && cacheHit. Once any download of a digest was not a cache hit, verification is always performed regardless of subsequent cache hits for the same digest.

-		skipVerify[layer.Digest] = cacheHit
+		if existing, ok := skipVerify[layer.Digest]; !ok {
+			skipVerify[layer.Digest] = cacheHit
+		} else {
+			skipVerify[layer.Digest] = existing && cacheHit
+		}

Test Plan

  • TestVerifyBlobWithDuplicateDigest — verifies that a tampered blob with duplicate config/layer digest is caught by verifyBlob
  • TestSkipVerifyMapNeverOverwritesFalse — table-driven test covering all combinations of cache-hit sequences, confirming false is never overwritten by true
  • All existing server/ tests pass with no regressions

🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.

## 📋 Pull Request Information **Original PR:** https://github.com/ollama/ollama/pull/15504 **Author:** [@vigneshakaviki](https://github.com/vigneshakaviki) **Created:** 4/11/2026 **Status:** 🔄 Open **Base:** `main` ← **Head:** `fix/skip-verify-duplicate-digest` --- ### 📝 Commits (1) - [`34c7924`](https://github.com/ollama/ollama/commit/34c7924784bfdf878d8dc2455931262c4bfc063e) server/images: prevent skipVerify map collision with duplicate digests ### 📊 Changes **2 files changed** (+157 additions, -1 deletions) <details> <summary>View changed files</summary> 📝 `server/images.go` (+10 -1) 📝 `server/images_test.go` (+147 -0) </details> ### 📄 Description ## Summary Fixes #15485 — blob hash verification is skipped when a manifest's config and layer share the same digest, enabling SSRF response exfiltration from rogue OCI registries. ## Root Cause In `PullModel`, the `skipVerify` map tracks whether each blob needs hash verification after download. The map is keyed by digest. When config and layer share the same digest: 1. Layer downloads first → `skipVerify[digest] = false` (fresh download, needs verification) 2. Config processes second → blob already on disk → `skipVerify[digest] = true` (overwrites!) 3. Verification loop sees `true` → skips `verifyBlob` entirely A rogue registry can exploit this by serving duplicate digests with a 307 redirect to internal endpoints. The SSRF response is written as a blob, verification is bypassed, and the attacker can exfiltrate via `/api/copy` + `/api/push`. ## Fix Use logical AND when updating the `skipVerify` map: `existing && cacheHit`. Once any download of a digest was not a cache hit, verification is always performed regardless of subsequent cache hits for the same digest. ```diff - skipVerify[layer.Digest] = cacheHit + if existing, ok := skipVerify[layer.Digest]; !ok { + skipVerify[layer.Digest] = cacheHit + } else { + skipVerify[layer.Digest] = existing && cacheHit + } ``` ## Test Plan - [x] `TestVerifyBlobWithDuplicateDigest` — verifies that a tampered blob with duplicate config/layer digest is caught by `verifyBlob` - [x] `TestSkipVerifyMapNeverOverwritesFalse` — table-driven test covering all combinations of cache-hit sequences, confirming `false` is never overwritten by `true` - [x] All existing `server/` tests pass with no regressions --- <sub>🔄 This issue represents a GitHub Pull Request. It cannot be merged through Gitea due to API limitations.</sub>
GiteaMirror added the pull-request label 2026-04-25 01:51:49 -05:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: github-starred/ollama#46426