Skip to content

Commit 1bba584

Browse files
authored
artifact: protect against unbounded artifact decompression (1.4.x) (#16126) (#16157)
* artifact: protect against unbounded artifact decompression This PR enables mitigations provided by go-getter against payloads which decompress into an unbounded size or file count. There are two new client config options under the artifact block: artifact.decompression_size_limit (e.g. "10GB") - the maximum amount of data that will be decompressed before triggering an error and cancelling the operation artifact.decompression_file_count_limit (e.g. 1024) - the maximum number of files that will be decompressed before triggering ana error and cancelling the operation. * fixup CR comments * deps: update to go-getter 1.7.0
1 parent c4d5b26 commit 1bba584

File tree

11 files changed

+416
-141
lines changed

11 files changed

+416
-141
lines changed

.changelog/16126.txt

+3
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
```release-note:security
2+
artifact: Provide mitigations against unbounded artifact decompression
3+
```

client/alloc_watcher_e2e_test.go

+1
Original file line numberDiff line numberDiff line change
@@ -55,6 +55,7 @@ func TestPrevAlloc_StreamAllocDir_TLS(t *testing.T) {
5555
CertFile: clientCertFn,
5656
KeyFile: clientKeyFn,
5757
}
58+
5859
c.Client.Enabled = true
5960
c.Client.Servers = []string{server.GetConfig().RPCAddr.String()}
6061
}

client/allocrunner/taskrunner/getter/getter.go

+5-1
Original file line numberDiff line numberDiff line change
@@ -83,6 +83,7 @@ func (g *Getter) GetArtifact(taskEnv interfaces.EnvReplacer, artifact *structs.T
8383
}
8484

8585
headers := getHeaders(taskEnv, artifact.GetterHeaders)
86+
8687
if err := g.getClient(ggURL, headers, mode, dest).Get(); err != nil {
8788
return newGetError(ggURL, err, true)
8889
}
@@ -99,8 +100,11 @@ func (g *Getter) getClient(src string, headers http.Header, mode gg.ClientMode,
99100
Umask: 060000000,
100101
Getters: g.createGetters(headers),
101102

102-
// This will prevent copying or writing files through symlinks
103+
// This will prevent copying or writing files through symlinks.
103104
DisableSymlinks: true,
105+
106+
// This will protect against decompression bombs.
107+
Decompressors: gg.LimitedDecompressors(g.config.DecompressionLimitFileCount, g.config.DecompressionLimitSize),
104108
}
105109
}
106110

client/allocrunner/taskrunner/getter/getter_test.go

+23-7
Original file line numberDiff line numberDiff line change
@@ -86,20 +86,36 @@ func (u upperReplacer) ClientPath(p string, join bool) (string, bool) {
8686
}
8787

8888
func TestGetter_getClient(t *testing.T) {
89+
const fileCountLimit = 555
90+
const fileSizeLimit = int64(666)
8991
getter := NewGetter(hclog.NewNullLogger(), &clientconfig.ArtifactConfig{
90-
HTTPReadTimeout: time.Minute,
91-
HTTPMaxBytes: 100_000,
92-
GCSTimeout: 1 * time.Minute,
93-
GitTimeout: 2 * time.Minute,
94-
HgTimeout: 3 * time.Minute,
95-
S3Timeout: 4 * time.Minute,
92+
HTTPReadTimeout: time.Minute,
93+
HTTPMaxBytes: 100_000,
94+
GCSTimeout: 1 * time.Minute,
95+
GitTimeout: 2 * time.Minute,
96+
HgTimeout: 3 * time.Minute,
97+
S3Timeout: 4 * time.Minute,
98+
DecompressionLimitFileCount: fileCountLimit,
99+
DecompressionLimitSize: fileSizeLimit,
96100
})
101+
97102
client := getter.getClient("src", nil, gg.ClientModeAny, "dst")
98103

99104
t.Run("check symlink config", func(t *testing.T) {
100105
require.True(t, client.DisableSymlinks)
101106
})
102107

108+
t.Run("check file size limits", func(t *testing.T) {
109+
require.Equal(t, fileSizeLimit, client.Decompressors["zip"].(*gg.ZipDecompressor).FileSizeLimit)
110+
require.Equal(t, fileCountLimit, client.Decompressors["zip"].(*gg.ZipDecompressor).FilesLimit)
111+
112+
require.Equal(t, fileSizeLimit, client.Decompressors["tar.gz"].(*gg.TarGzipDecompressor).FileSizeLimit)
113+
require.Equal(t, fileCountLimit, client.Decompressors["tar.gz"].(*gg.TarGzipDecompressor).FilesLimit)
114+
115+
require.Equal(t, fileSizeLimit, client.Decompressors["xz"].(*gg.XzDecompressor).FileSizeLimit)
116+
// xz does not support files count limit
117+
})
118+
103119
t.Run("check http config", func(t *testing.T) {
104120
require.True(t, client.Getters["http"].(*gg.HttpGetter).XTerraformGetDisabled)
105121
require.Equal(t, time.Minute, client.Getters["http"].(*gg.HttpGetter).ReadTimeout)
@@ -151,7 +167,7 @@ func TestGetArtifact_getHeaders(t *testing.T) {
151167
}
152168

153169
func TestGetArtifact_Headers(t *testing.T) {
154-
file := "output.txt"
170+
const file = "output.txt"
155171

156172
// Create the test server with a handler that will validate headers are set.
157173
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,4 @@
11
//go:build !release
2-
// +build !release
32

43
package getter
54

@@ -8,12 +7,17 @@ import (
87

98
"github.com/hashicorp/go-hclog"
109
clientconfig "github.com/hashicorp/nomad/client/config"
10+
"github.com/hashicorp/nomad/helper/pointer"
1111
"github.com/hashicorp/nomad/nomad/structs/config"
12-
"github.com/stretchr/testify/require"
12+
"github.com/shoenig/test/must"
1313
)
1414

15+
// TestDefaultGetter creates a Getter suitable for unit test cases.
1516
func TestDefaultGetter(t *testing.T) *Getter {
16-
getterConf, err := clientconfig.ArtifactConfigFromAgent(config.DefaultArtifactConfig())
17-
require.NoError(t, err)
17+
defaultConfig := config.DefaultArtifactConfig()
18+
defaultConfig.DecompressionSizeLimit = pointer.Of("1MB")
19+
defaultConfig.DecompressionFileCountLimit = pointer.Of(10)
20+
getterConf, err := clientconfig.ArtifactConfigFromAgent(defaultConfig)
21+
must.NoError(t, err)
1822
return NewGetter(hclog.NewNullLogger(), getterConf)
1923
}

client/allocrunner/taskrunner/task_runner_hooks.go

+1
Original file line numberDiff line numberDiff line change
@@ -58,6 +58,7 @@ func (tr *TaskRunner) initHooks() {
5858
// Create the task directory hook. This is run first to ensure the
5959
// directory path exists for other hooks.
6060
alloc := tr.Alloc()
61+
6162
tr.runnerHooks = []interfaces.TaskHook{
6263
newValidateHook(tr.clientConfig, hookLogger),
6364
newTaskDirHook(tr, hookLogger),

client/config/artifact.go

+12
Original file line numberDiff line numberDiff line change
@@ -18,6 +18,9 @@ type ArtifactConfig struct {
1818
GitTimeout time.Duration
1919
HgTimeout time.Duration
2020
S3Timeout time.Duration
21+
22+
DecompressionLimitSize int64
23+
DecompressionLimitFileCount int
2124
}
2225

2326
// ArtifactConfigFromAgent creates a new internal readonly copy of the client
@@ -61,6 +64,15 @@ func ArtifactConfigFromAgent(c *config.ArtifactConfig) (*ArtifactConfig, error)
6164
}
6265
newConfig.S3Timeout = t
6366

67+
s, err = humanize.ParseBytes(*c.DecompressionSizeLimit)
68+
if err != nil {
69+
return nil, fmt.Errorf("error parsing DecompressionLimitSize: %w", err)
70+
}
71+
newConfig.DecompressionLimitSize = int64(s)
72+
73+
// no parsing its just an int
74+
newConfig.DecompressionLimitFileCount = *c.DecompressionFileCountLimit
75+
6476
return newConfig, nil
6577
}
6678

go.mod

+25-25
Original file line numberDiff line numberDiff line change
@@ -55,8 +55,8 @@ require (
5555
// versions.
5656
github.com/hashicorp/go-discover v0.0.0-20220621183603-a413e131e836
5757
github.com/hashicorp/go-envparse v0.0.0-20180119215841-310ca1881b22
58-
github.com/hashicorp/go-getter v1.6.1
59-
github.com/hashicorp/go-hclog v1.2.2
58+
github.com/hashicorp/go-getter v1.7.0
59+
github.com/hashicorp/go-hclog v1.3.1
6060
github.com/hashicorp/go-immutable-radix v1.3.1
6161
github.com/hashicorp/go-memdb v1.3.4
6262
github.com/hashicorp/go-msgpack v1.1.5
@@ -68,7 +68,7 @@ require (
6868
github.com/hashicorp/go-sockaddr v1.0.2
6969
github.com/hashicorp/go-syslog v1.0.0
7070
github.com/hashicorp/go-uuid v1.0.2
71-
github.com/hashicorp/go-version v1.4.0
71+
github.com/hashicorp/go-version v1.6.0
7272
github.com/hashicorp/golang-lru v0.5.4
7373
github.com/hashicorp/hcl v1.0.1-vault-3
7474
github.com/hashicorp/hcl/v2 v2.9.2-0.20210407182552-eb14f8319bdc
@@ -119,20 +119,25 @@ require (
119119
go.uber.org/goleak v1.1.12
120120
golang.org/x/crypto v0.0.0-20220622213112-05595931fe9d
121121
golang.org/x/exp v0.0.0-20220921164117-439092de6870
122-
golang.org/x/net v0.1.0
123-
golang.org/x/sync v0.0.0-20220722155255-886fb9371eb4
124122
golang.org/x/sys v0.2.0
125123
golang.org/x/time v0.0.0-20220224211638-0e9765cccd65
126-
google.golang.org/grpc v1.45.0
124+
google.golang.org/grpc v1.50.1
127125
google.golang.org/protobuf v1.28.1
128126
gopkg.in/tomb.v1 v1.0.0-20141024135613-dd632973f1e7
129127
gopkg.in/tomb.v2 v2.0.0-20140626144623-14b3d72120e8
130128
oss.indeed.com/go/libtime v1.6.0
131129
)
132130

133131
require (
134-
cloud.google.com/go v0.97.0 // indirect
135-
cloud.google.com/go/storage v1.18.2 // indirect
132+
golang.org/x/net v0.1.0
133+
golang.org/x/sync v0.0.0-20220929204114-8fcdb60fdcc0
134+
)
135+
136+
require (
137+
cloud.google.com/go v0.104.0 // indirect
138+
cloud.google.com/go/compute v1.10.0 // indirect
139+
cloud.google.com/go/iam v0.5.0 // indirect
140+
cloud.google.com/go/storage v1.27.0 // indirect
136141
github.com/Azure/azure-sdk-for-go v56.3.0+incompatible // indirect
137142
github.com/Azure/go-ansiterm v0.0.0-20210617225240-d185dfc1b5a1 // indirect
138143
github.com/Azure/go-autorest v14.2.0+incompatible // indirect
@@ -165,30 +170,24 @@ require (
165170
github.com/bmatcuk/doublestar v1.1.5 // indirect
166171
github.com/boltdb/bolt v1.3.1 // indirect
167172
github.com/cenkalti/backoff/v3 v3.2.2 // indirect
168-
github.com/census-instrumentation/opencensus-proto v0.3.0 // indirect
169173
github.com/cespare/xxhash/v2 v2.1.2 // indirect
170174
github.com/checkpoint-restore/go-criu/v5 v5.3.0 // indirect
171175
github.com/cheggaaa/pb/v3 v3.0.5 // indirect
172176
github.com/cilium/ebpf v0.8.1 // indirect
173177
github.com/circonus-labs/circonus-gometrics v2.3.1+incompatible // indirect
174178
github.com/circonus-labs/circonusllhist v0.1.3 // indirect
175-
github.com/cncf/udpa/go v0.0.0-20210930031921-04548b0d99d4 // indirect
176-
github.com/cncf/xds/go v0.0.0-20211011173535-cb28da3451f1 // indirect
177-
github.com/containerd/cgroups v1.0.2 // indirect
178-
github.com/containerd/console v1.0.3 // indirect
179-
github.com/containerd/containerd v1.5.9 // indirect
179+
github.com/containerd/cgroups v1.0.1 // indirect
180+
github.com/containerd/console v1.0.2 // indirect
181+
github.com/containerd/containerd v1.5.1 // indirect
180182
github.com/coreos/go-systemd/v22 v22.3.2 // indirect
181-
github.com/cyphar/filepath-securejoin v0.2.3 // indirect
183+
github.com/cyphar/filepath-securejoin v0.2.2 // indirect
182184
github.com/davecgh/go-spew v1.1.1 // indirect
183185
github.com/denverdino/aliyungo v0.0.0-20190125010748-a747050bb1ba // indirect
184186
github.com/digitalocean/godo v1.10.0 // indirect
185187
github.com/dimchansky/utfbom v1.1.0 // indirect
186-
github.com/docker/docker-credential-helpers v0.6.4 // indirect
188+
github.com/docker/docker-credential-helpers v0.7.0 // indirect
187189
github.com/docker/go-connections v0.4.0 // indirect
188190
github.com/docker/go-metrics v0.0.1 // indirect
189-
github.com/docker/libtrust v0.0.0-20160708172513-aabc10ec26b7 // indirect
190-
github.com/envoyproxy/go-control-plane v0.10.0 // indirect
191-
github.com/envoyproxy/protoc-gen-validate v0.6.2 // indirect
192191
github.com/felixge/httpsnoop v1.0.1 // indirect
193192
github.com/go-ole/go-ole v1.2.6 // indirect
194193
github.com/godbus/dbus/v5 v5.1.0 // indirect
@@ -199,7 +198,8 @@ require (
199198
github.com/google/btree v1.0.0 // indirect
200199
github.com/google/go-querystring v0.0.0-20170111101155-53e6ce116135 // indirect
201200
github.com/google/uuid v1.3.0 // indirect
202-
github.com/googleapis/gax-go/v2 v2.1.1 // indirect
201+
github.com/googleapis/enterprise-certificate-proxy v0.2.0 // indirect
202+
github.com/googleapis/gax-go/v2 v2.6.0 // indirect
203203
github.com/gookit/color v1.3.1 // indirect
204204
github.com/gophercloud/gophercloud v0.1.0 // indirect
205205
github.com/gorilla/mux v1.8.0 // indirect
@@ -220,7 +220,7 @@ require (
220220
github.com/jefferai/isbadcipher v0.0.0-20190226160619-51d2077c035f // indirect
221221
github.com/jmespath/go-jmespath v0.4.0 // indirect
222222
github.com/joyent/triton-go v0.0.0-20190112182421-51ffac552869 // indirect
223-
github.com/klauspost/compress v1.13.6 // indirect
223+
github.com/klauspost/compress v1.15.11 // indirect
224224
github.com/linode/linodego v0.7.1 // indirect
225225
github.com/lufia/plan9stats v0.0.0-20211012122336-39d0f177ccd0 // indirect
226226
github.com/mattn/go-isatty v0.0.16 // indirect
@@ -268,14 +268,14 @@ require (
268268
go.opencensus.io v0.23.0 // indirect
269269
go.uber.org/atomic v1.9.0 // indirect
270270
golang.org/x/mod v0.6.0-dev.0.20220419223038-86c51ed26bb4 // indirect
271-
golang.org/x/oauth2 v0.0.0-20220223155221-ee480838109b // indirect
271+
golang.org/x/oauth2 v0.1.0 // indirect
272272
golang.org/x/term v0.1.0 // indirect
273273
golang.org/x/text v0.4.0 // indirect
274274
golang.org/x/tools v0.1.12 // indirect
275-
golang.org/x/xerrors v0.0.0-20200804184101-5ec99f83aff1 // indirect
276-
google.golang.org/api v0.60.0 // indirect
275+
golang.org/x/xerrors v0.0.0-20220907171357-04be3eba64a2 // indirect
276+
google.golang.org/api v0.100.0 // indirect
277277
google.golang.org/appengine v1.6.7 // indirect
278-
google.golang.org/genproto v0.0.0-20220314164441-57ef72a4c106 // indirect
278+
google.golang.org/genproto v0.0.0-20221025140454-527a21cfbd71 // indirect
279279
gopkg.in/check.v1 v1.0.0-20200227125254-8fa46927fb4f // indirect
280280
gopkg.in/fsnotify.v1 v1.4.7 // indirect
281281
gopkg.in/resty.v1 v1.12.0 // indirect

0 commit comments

Comments
 (0)