Skip to content

Commit 539c909

Browse files
authored
Merge pull request #637 from jkloetzke/detect-artifact-corruption
Detect artifact corruption
2 parents 679f222 + e01ffe6 commit 539c909

File tree

6 files changed

+90
-3
lines changed

6 files changed

+90
-3
lines changed

pym/bob/audit.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -220,6 +220,9 @@ def getId(self):
220220
def getBuildId(self):
221221
return bytes.fromhex(self.__data['build-id'])
222222

223+
def getResultHash(self):
224+
return bytes.fromhex(self.__data['result-hash'])
225+
223226
def getReferences(self):
224227
deps = self.__data["dependencies"]
225228
ret = set()

pym/bob/builder.py

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1492,7 +1492,7 @@ async def _downloadPackage(self, packageStep, depth, packageBuildId):
14921492
dissectPackageInputState(BobState().getInputHashes(prettyPackagePath))
14931493
workspaceChanged = False
14941494
wasDownloaded = False
1495-
audit = os.path.join(prettyPackagePath, "..", "audit.json.gz")
1495+
audit = os.path.normpath(os.path.join(prettyPackagePath, "..", "audit.json.gz"))
14961496
packageDigest = packageStep.getVariantId()
14971497

14981498
# prune directory if we previously downloaded/built something different
@@ -1509,6 +1509,7 @@ async def _downloadPackage(self, packageStep, depth, packageBuildId):
15091509
stepMessage(packageStep, "PRUNE", "{} ({})".format(prettyPackagePath,
15101510
reason), WARNING)
15111511
emptyDirectory(prettyPackagePath)
1512+
removePath(audit)
15121513
BobState().resetWorkspaceState(prettyPackagePath, packageDigest)
15131514
oldInputBuildId = None
15141515
oldInputFingerprint = None
@@ -1522,9 +1523,19 @@ async def _downloadPackage(self, packageStep, depth, packageBuildId):
15221523
packageBuildId, audit, prettyPackagePath, executor=self.__executor)
15231524
if wasDownloaded:
15241525
self.__statistic.packagesDownloaded += 1
1526+
# Reject downloads without audit trail. They cannot be verified
1527+
# and break the audit trail of downstream packages.
1528+
if not os.path.exists(audit):
1529+
raise BuildError("Downloaded artifact misses its audit trail!")
1530+
1531+
# Verify integrity of downloaded package to protect against
1532+
# data corruption.
1533+
packageHash = hashWorkspace(packageStep)
1534+
if Audit.fromFile(audit).getArtifact().getResultHash() != packageHash:
1535+
raise BuildError("Corrupt downloaded artifact! Extracted content hash does not match audit trail.")
1536+
15251537
BobState().setInputHashes(prettyPackagePath,
15261538
packageInputDownloaded(packageBuildId))
1527-
packageHash = hashWorkspace(packageStep)
15281539
workspaceChanged = True
15291540
elif layerDownloadMode == 'forced':
15301541
raise BuildError("Downloading artifact of layer %s failed" % layer)

pym/bob/develop/version.py

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,7 +62,7 @@ def getVersion():
6262

6363
if not version:
6464
# Last fallback. See PEP 440 and adjust accordingly.
65-
version = "1.0.dev999+unknown"
65+
version = "1.1.dev999+unknown"
6666

6767
return version
6868

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
bobMinimumVersion: "1"
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
root: True
2+
packageScript: |
3+
echo foo >result.txt
Lines changed: 69 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,69 @@
1+
#!/bin/bash -e
2+
. ../../test-lib.sh 2>/dev/null || { echo "Must run in script directory!" ; exit 1 ; }
3+
cleanup
4+
5+
# Test that corruptions of binary artifacts are detected...
6+
7+
# setup local archive
8+
trap 'rm -rf "${archiveDir}"' EXIT
9+
archiveDir=$(mktemp -d)
10+
11+
upload="$archiveDir/uploads"
12+
cat >"${upload}.yaml" <<EOF
13+
archive:
14+
-
15+
name: "local"
16+
backend: file
17+
path: "$(mangle_path "$archiveDir/artifacts")"
18+
EOF
19+
scratch="$archiveDir/scratch"
20+
mkdir "$scratch"
21+
22+
# fill archive
23+
run_bob build -c "$upload" --download=no --upload root
24+
ARTIFACTS=( $(/usr/bin/find "$archiveDir/artifacts" -type f) )
25+
test "${#ARTIFACTS[@]}" -eq 1
26+
27+
# save artifact for later modification
28+
A="${ARTIFACTS[0]}"
29+
SAVE="$archiveDir/save.tgz"
30+
cp "$A" "$SAVE"
31+
32+
# Verify download of re-packed artifact still works.
33+
rm "$A"
34+
pushd "$scratch"
35+
tar xf "$SAVE"
36+
tar --pax-option bob-archive-vsn=1 -zcf "$A" meta content
37+
popd
38+
run_bob dev -c "$upload" --download=forced root
39+
40+
# Modify content. When downloading the artifact again, the corruption must be
41+
# detected.
42+
rm "$A"
43+
pushd "$scratch"
44+
tar xf "$SAVE"
45+
echo bar > content/result.txt
46+
tar --pax-option bob-archive-vsn=1 -zcf "$A" meta content
47+
popd
48+
49+
rm -rf dev
50+
expect_fail run_bob dev -c "$upload" --download=forced root
51+
52+
# Remove audit trail. Such artifacts must be rejected.
53+
rm "$A"
54+
pushd "$scratch"
55+
tar xf "$SAVE"
56+
tar --pax-option bob-archive-vsn=1 -zcf "$A" content
57+
popd
58+
59+
rm -rf dev
60+
expect_fail run_bob dev -c "$upload" --download=forced root
61+
62+
# Add garbage in the middle. Must fail gracefully.
63+
len=$(stat -c %s "$SAVE")
64+
head -c $((len - 64)) "$SAVE" > "$A"
65+
echo -n asdf >> "$A"
66+
tail -c 60 "$SAVE" >> "$A"
67+
68+
rm -rf dev
69+
expect_fail run_bob dev -c "$upload" --download=forced root

0 commit comments

Comments
 (0)