@@ -1106,7 +1106,7 @@ func TestDownloadActionOutputs(t *testing.T) {
11061106 treeADigest := fake .Put (treeABlob )
11071107 ar := & repb.ActionResult {
11081108 OutputFiles : []* repb.OutputFile {
1109- & repb.OutputFile {Path : "../foo" , Digest : fooDigest .ToProto ()}},
1109+ & repb.OutputFile {Path : "foo/ ../foo" , Digest : fooDigest .ToProto ()}},
11101110 OutputFileSymlinks : []* repb.OutputSymlink {
11111111 & repb.OutputSymlink {Path : "x/bar" , Target : "../dir/a/bar" }},
11121112 OutputDirectorySymlinks : []* repb.OutputSymlink {
@@ -1172,7 +1172,7 @@ func TestDownloadActionOutputs(t *testing.T) {
11721172 contents : []byte ("bar" ),
11731173 },
11741174 {
1175- path : "foo" ,
1175+ path : "wd/ foo" ,
11761176 contents : []byte ("foo" ),
11771177 fileDigest : & fooDigest ,
11781178 },
@@ -2050,3 +2050,149 @@ func TestBatchDownloadBlobsBrokenCompression(t *testing.T) {
20502050 })
20512051 }
20522052}
2053+
2054+ type escapedPathTestEnv struct {
2055+ ctx context.Context
2056+ c * client.Client
2057+ cache filemetadata.Cache
2058+ outDir string
2059+ escapedDigest digest.Digest
2060+ safeDigest digest.Digest
2061+ escapedPath string
2062+ safePath string
2063+ }
2064+
2065+ func createEscapedPathTestEnv (t * testing.T ) escapedPathTestEnv {
2066+ t .Helper ()
2067+ ctx := context .Background ()
2068+ e , cleanup := fakes .NewTestEnv (t )
2069+ t .Cleanup (cleanup )
2070+ fake := e .Server .CAS
2071+ c := e .Client .GrpcClient
2072+ cache := filemetadata .NewSingleFlightCache ()
2073+
2074+ escapedContent := []byte ("escaped vulnerability test" )
2075+ escapedDigest := fake .Put (escapedContent )
2076+ escapedPath := "../escaped_file.txt"
2077+
2078+ safeContent := []byte ("safe content" )
2079+ safeDigest := fake .Put (safeContent )
2080+ safePath := "safe_file.txt"
2081+
2082+ outDir := filepath .Join (t .TempDir (), "real_out_dir" )
2083+ if err := os .MkdirAll (outDir , 0755 ); err != nil {
2084+ t .Fatalf ("Failed to create outDir: %v" , err )
2085+ }
2086+
2087+ // Verify that the hardcoded escapedPath indeed attempts to escape.
2088+ targetPath := filepath .Clean (filepath .Join (outDir , escapedPath ))
2089+ if strings .HasPrefix (targetPath , outDir ) {
2090+ t .Fatalf ("Failed to construct an escaped path: %s is still within %s" , targetPath , outDir )
2091+ }
2092+
2093+ return escapedPathTestEnv {
2094+ ctx : ctx ,
2095+ c : c ,
2096+ cache : cache ,
2097+ outDir : outDir ,
2098+ escapedDigest : escapedDigest ,
2099+ safeDigest : safeDigest ,
2100+ escapedPath : escapedPath ,
2101+ safePath : safePath ,
2102+ }
2103+ }
2104+
2105+ func TestEscapeDownloadOutputs (t * testing.T ) {
2106+ t .Parallel ()
2107+ env := createEscapedPathTestEnv (t )
2108+
2109+ outputs := map [string ]* client.TreeOutput {
2110+ env .escapedPath : {
2111+ Digest : env .escapedDigest ,
2112+ Path : env .escapedPath ,
2113+ },
2114+ }
2115+
2116+ if _ , err := env .c .DownloadOutputs (env .ctx , outputs , env .outDir , env .cache ); err == nil {
2117+ t .Errorf ("DownloadOutputs didn't fail when output path %v try to escape from outDir %v" , env .escapedPath , env .outDir )
2118+ }
2119+ }
2120+
2121+ func TestEscapeDownloadFiles_Single (t * testing.T ) {
2122+ t .Parallel ()
2123+ env := createEscapedPathTestEnv (t )
2124+
2125+ outputs := map [digest.Digest ]* client.TreeOutput {
2126+ env .escapedDigest : {
2127+ Digest : env .escapedDigest ,
2128+ Path : env .escapedPath ,
2129+ },
2130+ }
2131+
2132+ if _ , err := env .c .DownloadFiles (env .ctx , env .outDir , outputs ); err == nil {
2133+ t .Errorf ("DownloadFiles didn't fail when output path try to escape from outDir %v" , env .outDir )
2134+ }
2135+ }
2136+
2137+ func TestEscapeDownloadFiles_Batch (t * testing.T ) {
2138+ t .Parallel ()
2139+ env := createEscapedPathTestEnv (t )
2140+
2141+ outputs := map [digest.Digest ]* client.TreeOutput {
2142+ env .escapedDigest : {
2143+ Digest : env .escapedDigest ,
2144+ Path : env .escapedPath ,
2145+ },
2146+ env .safeDigest : {
2147+ Digest : env .safeDigest ,
2148+ Path : env .safePath ,
2149+ },
2150+ }
2151+ client .UseBatchOps (true ).Apply (env .c )
2152+ client .MaxBatchDigests (2 ).Apply (env .c )
2153+ env .c .RunBackgroundTasks (env .ctx )
2154+
2155+ if _ , err := env .c .DownloadFiles (env .ctx , env .outDir , outputs ); err == nil {
2156+ t .Errorf ("DownloadFiles didn't fail when output path try to escape from outDir %v" , env .outDir )
2157+ }
2158+ }
2159+
2160+ func TestEscapeDownloadNonUnified_Single (t * testing.T ) {
2161+ t .Parallel ()
2162+ env := createEscapedPathTestEnv (t )
2163+
2164+ outputs := map [digest.Digest ]* client.TreeOutput {
2165+ env .escapedDigest : {
2166+ Digest : env .escapedDigest ,
2167+ Path : env .escapedPath ,
2168+ },
2169+ }
2170+ client .UnifiedDownloads (false ).Apply (env .c )
2171+ if _ , err := env .c .DownloadFiles (env .ctx , env .outDir , outputs ); err == nil {
2172+ t .Errorf ("DownloadFiles didn't fail when output path try to escape from outDir %v" , env .outDir )
2173+ }
2174+ }
2175+
2176+ func TestEscapeDownloadNonUnified_Batch (t * testing.T ) {
2177+ t .Parallel ()
2178+ env := createEscapedPathTestEnv (t )
2179+
2180+ outputs := map [digest.Digest ]* client.TreeOutput {
2181+ env .escapedDigest : {
2182+ Digest : env .escapedDigest ,
2183+ Path : env .escapedPath ,
2184+ },
2185+ env .safeDigest : {
2186+ Digest : env .safeDigest ,
2187+ Path : env .safePath ,
2188+ },
2189+ }
2190+ client .UnifiedDownloads (false ).Apply (env .c )
2191+ client .UseBatchOps (true ).Apply (env .c )
2192+ client .MaxBatchDigests (2 ).Apply (env .c )
2193+ env .c .RunBackgroundTasks (env .ctx )
2194+
2195+ if _ , err := env .c .DownloadFiles (env .ctx , env .outDir , outputs ); err == nil {
2196+ t .Errorf ("DownloadFiles didn't fail when output path try to escape from outDir %v" , env .outDir )
2197+ }
2198+ }
0 commit comments