Skip to content

Commit dccb128

Browse files
authored
fix(image): lookup origin layer for custom resources in merged layers (aquasecurity#10788)
1 parent 9032dcb commit dccb128

2 files changed

Lines changed: 115 additions & 4 deletions

File tree

pkg/fanal/applier/docker.go

Lines changed: 23 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,18 @@ func lookupOriginLayerForLib(filePath string, lib ftypes.Package, layers []ftype
9595
return "", ""
9696
}
9797

98+
func lookupOriginLayerForCustomResource(customResource ftypes.CustomResource, layers []ftypes.BlobInfo) (string, string) {
99+
for _, layer := range layers {
100+
for _, layerCR := range layer.CustomResources {
101+
if customResource.FilePath != layerCR.FilePath || customResource.Type != layerCR.Type {
102+
continue
103+
}
104+
return layer.Digest, layer.DiffID
105+
}
106+
}
107+
return "", ""
108+
}
109+
98110
// ApplyLayers returns the merged layer
99111
// nolint: gocyclo
100112
func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail {
@@ -163,10 +175,6 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail {
163175
// Apply custom resources
164176
for _, customResource := range layer.CustomResources {
165177
key := fmt.Sprintf("%s/custom:%s", customResource.FilePath, customResource.Type)
166-
customResource.Layer = ftypes.Layer{
167-
Digest: layer.Digest,
168-
DiffID: layer.DiffID,
169-
}
170178
nestedMap.SetByString(key, sep, customResource)
171179
}
172180
}
@@ -291,6 +299,17 @@ func ApplyLayers(layers []ftypes.BlobInfo) ftypes.ArtifactDetail {
291299
}
292300
}
293301

302+
for i, customResource := range mergedLayer.CustomResources {
303+
// Skip lookup if the layer is already set.
304+
if lo.IsEmpty(customResource.Layer) {
305+
originLayerDigest, originLayerDiffID := lookupOriginLayerForCustomResource(customResource, layers)
306+
mergedLayer.CustomResources[i].Layer = ftypes.Layer{
307+
Digest: originLayerDigest,
308+
DiffID: originLayerDiffID,
309+
}
310+
}
311+
}
312+
294313
// Aggregate python/ruby/node.js packages and JAR files
295314
aggregate(&mergedLayer)
296315

pkg/fanal/applier/docker_test.go

Lines changed: 92 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1528,6 +1528,92 @@ func TestApplyLayers(t *testing.T) {
15281528
},
15291529
},
15301530
},
1531+
{
1532+
name: "custom resource origin layer lookup",
1533+
inputLayers: []types.BlobInfo{
1534+
{
1535+
SchemaVersion: 1,
1536+
Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
1537+
DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
1538+
CustomResources: []types.CustomResource{
1539+
{
1540+
Type: "spring4shell",
1541+
FilePath: "app.jar",
1542+
Data: "v1",
1543+
},
1544+
},
1545+
},
1546+
{
1547+
// Different Type, same FilePath — must not match spring4shell/app.jar lookup
1548+
SchemaVersion: 1,
1549+
Digest: "sha256:7f8e9d0c1b2a394857463524130211009f8e7d6c5b4a39281716151413121110",
1550+
DiffID: "sha256:1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f80",
1551+
CustomResources: []types.CustomResource{
1552+
{
1553+
Type: "log4shell",
1554+
FilePath: "app.jar",
1555+
Data: "other-type",
1556+
},
1557+
},
1558+
},
1559+
{
1560+
// Different FilePath, same Type — must not match spring4shell/app.jar lookup
1561+
SchemaVersion: 1,
1562+
Digest: "sha256:8e9f0d1c2b3a4958675746352413221100af9e8d7c6b5a49392827262524232221",
1563+
DiffID: "sha256:2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809",
1564+
CustomResources: []types.CustomResource{
1565+
{
1566+
Type: "spring4shell",
1567+
FilePath: "other.jar",
1568+
Data: "other-path",
1569+
},
1570+
},
1571+
},
1572+
{
1573+
SchemaVersion: 1,
1574+
Digest: "sha256:dffd9992ca398466a663c87c92cfea2a2db0ae0cf33fcb99da60eec52addbfc5",
1575+
DiffID: "sha256:aad63a9339440e7c3e1fff2b988991b9bfb81280042fa7f39a5e327023056819",
1576+
CustomResources: []types.CustomResource{
1577+
{
1578+
Type: "spring4shell",
1579+
FilePath: "app.jar",
1580+
Data: "v2",
1581+
},
1582+
},
1583+
},
1584+
},
1585+
want: types.ArtifactDetail{
1586+
CustomResources: []types.CustomResource{
1587+
{
1588+
Type: "log4shell",
1589+
FilePath: "app.jar",
1590+
Layer: types.Layer{
1591+
Digest: "sha256:7f8e9d0c1b2a394857463524130211009f8e7d6c5b4a39281716151413121110",
1592+
DiffID: "sha256:1a2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f80",
1593+
},
1594+
Data: "other-type",
1595+
},
1596+
{
1597+
Type: "spring4shell",
1598+
FilePath: "app.jar",
1599+
Layer: types.Layer{
1600+
Digest: "sha256:932da51564135c98a49a34a193d6cd363d8fa4184d957fde16c9d8527b3f3b02",
1601+
DiffID: "sha256:a187dde48cd289ac374ad8539930628314bc581a481cdb41409c9289419ddb72",
1602+
},
1603+
Data: "v2",
1604+
},
1605+
{
1606+
Type: "spring4shell",
1607+
FilePath: "other.jar",
1608+
Layer: types.Layer{
1609+
Digest: "sha256:8e9f0d1c2b3a4958675746352413221100af9e8d7c6b5a49392827262524232221",
1610+
DiffID: "sha256:2b3c4d5e6f708192a3b4c5d6e7f8091a2b3c4d5e6f708192a3b4c5d6e7f809",
1611+
},
1612+
Data: "other-path",
1613+
},
1614+
},
1615+
},
1616+
},
15311617
{
15321618
// Duplicate packages with different PURL namespaces, prefer OS-matching PURL
15331619
name: "prefer OS-matching PURL during deduplication",
@@ -1590,6 +1676,12 @@ func TestApplyLayers(t *testing.T) {
15901676
for _, app := range got.Applications {
15911677
sort.Sort(app.Packages)
15921678
}
1679+
sort.Slice(got.CustomResources, func(i, j int) bool {
1680+
if got.CustomResources[i].FilePath == got.CustomResources[j].FilePath {
1681+
return got.CustomResources[i].Type < got.CustomResources[j].Type
1682+
}
1683+
return got.CustomResources[i].FilePath < got.CustomResources[j].FilePath
1684+
})
15931685
assert.Equal(t, tt.want, got, tt.name)
15941686
})
15951687
}

0 commit comments

Comments
 (0)