Skip to content
This repository has been archived by the owner on Oct 2, 2024. It is now read-only.

Return error if blob download is incomplete by validating hash digest. #1833

Merged
merged 8 commits into from
Feb 21, 2024
14 changes: 11 additions & 3 deletions lib/registry.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import getpass
import hashlib
import io
import os
import re
Expand Down Expand Up @@ -395,7 +396,7 @@ def blob_to_file(self, digest, path, msg):
# /v2/library/hello-world/blobs/<layer-hash>
url = self._url_of("blobs", "sha256:" + digest)
sw = ch.Progress_Writer(path, msg)
self.request("GET", url, out=sw)
self.request("GET", url, out=sw, hd=digest)
sw.close()

def blob_upload(self, digest, data, note=""):
Expand Down Expand Up @@ -518,17 +519,19 @@ def manifest_upload(self, manifest):
self.request("PUT", url, {201}, data=manifest,
headers={ "Content-Type": TYPES_MANIFEST["docker2"] })

def request(self, method, url, statuses={200}, out=None, **kwargs):
def request(self, method, url, statuses={200}, out=None, hd=None, **kwargs):
"""Request url using method and return the response object. If statuses
is given, it is set of acceptable response status codes, defaulting
to {200}; any other response is a fatal error. If out is given,
response content will be streamed to this Progress_Writer object and
must be non-zero length.
must be non-zero length. If hd is given, validate integrity of
downloaded data using expected hash digest.

Use current session if there is one, or start a new one if not. If
authentication fails (or isn’t initialized), then authenticate harder
and re-try the request."""
# Set up.
assert (out or hd is None), "digest only checked if streaming"
self.session_init_maybe()
ch.VERBOSE("auth: %s" % self.auth)
if (out is not None):
Expand All @@ -547,6 +550,7 @@ def request(self, method, url, statuses={200}, out=None, **kwargs):
else:
ch.FATAL("unhandled authentication failure")
# Stream response if needed.
m = hashlib.sha256()
if (out is not None and res.status_code == 200):
try:
length = int(res.headers["Content-Length"])
Expand All @@ -557,6 +561,10 @@ def request(self, method, url, statuses={200}, out=None, **kwargs):
out.start(length)
for chunk in res.iter_content(ch.HTTP_CHUNK_SIZE):
out.write(chunk)
m.update(chunk) # store downloaded hash digest
# Validate integrity of downloaded data
if (hd is not None and hd != m.hexdigest()):
ch.FATAL("registry streamed response content is invalid")
# Done.
return res

Expand Down
Loading