-
Notifications
You must be signed in to change notification settings - Fork 144
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Add support for JRE provisioning: Jre tar.gz unpack (#2036)
1 parent
72a7718
commit 3c6df25
Showing
14 changed files
with
370 additions
and
33 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
127 changes: 127 additions & 0 deletions
127
Tests/SonarScanner.MSBuild.PreProcessor.Test/JreCaching/TarGzUnpackTests.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,127 @@ | ||
/* | ||
* SonarScanner for .NET | ||
* Copyright (C) 2016-2024 SonarSource SA | ||
* mailto: info AT sonarsource DOT com | ||
* | ||
* This program is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU Lesser General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 3 of the License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with this program; if not, write to the Free Software Foundation, | ||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
*/ | ||
|
||
using System; | ||
using System.IO; | ||
using System.Text; | ||
using FluentAssertions; | ||
using ICSharpCode.SharpZipLib.Core; | ||
using Microsoft.VisualStudio.TestTools.UnitTesting; | ||
using NSubstitute; | ||
using SonarScanner.MSBuild.Common; | ||
using SonarScanner.MSBuild.PreProcessor.JreCaching; | ||
using TestUtilities; | ||
|
||
namespace SonarScanner.MSBuild.PreProcessor.Test.JreCaching; | ||
|
||
[TestClass] | ||
public class TarGzUnpackTests | ||
{ | ||
private readonly IFileWrapper fileWrapper = Substitute.For<IFileWrapper>(); | ||
private readonly IDirectoryWrapper directoryWrapper = Substitute.For<IDirectoryWrapper>(); | ||
private readonly IOperatingSystemProvider osProvider = Substitute.For<IOperatingSystemProvider>(); | ||
|
||
[TestMethod] | ||
public void TarGzUnpacking_Success() | ||
{ | ||
// A tarball with the following content: | ||
// Main | ||
// ├── Sub | ||
// └── Sub2 | ||
// └── Sample.txt | ||
const string sampleTarGzFile = """ | ||
H4sICL04jWYEAE1haW4udGFyAO3SUQrDIAyA4RzFE2wao55iTz2BBccK3Ribw | ||
nb7iVDKnkqh+mK+l4S8/rn46XGGumTmnMuz+JvLrsiSRk1ImO/WkgRhoIH0jv | ||
4lBHSq9B/SWPMHdvVHk+9OO+T+LSz9seID7OpPpT8Zy/1bWPsP/v6cwyl+Ihx | ||
ss78ya3+T70qR1iAkNNB5/1v4ijH4FKdrmoExxlgvfmqGu7oADgAA | ||
"""; | ||
var baseDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); | ||
using var archive = new MemoryStream(Convert.FromBase64String(sampleTarGzFile)); | ||
using var unzipped = new MemoryStream(); | ||
fileWrapper.Create($"""{baseDirectory}\Main\Sub2\Sample.txt""").Returns(unzipped); | ||
|
||
CreateUnpacker().Unpack(archive, baseDirectory); | ||
|
||
directoryWrapper.Received(1).CreateDirectory($"""{baseDirectory}\Main\"""); | ||
directoryWrapper.Received(1).CreateDirectory($"""{baseDirectory}\Main\Sub\"""); | ||
directoryWrapper.Received(1).CreateDirectory($"""{baseDirectory}\Main\Sub2\"""); | ||
Encoding.UTF8.GetString(unzipped.ToArray()).NormalizeLineEndings().Should().Be("hey beautiful"); | ||
} | ||
|
||
[TestMethod] | ||
public void TarGzUnpacking_RootedPath_Success() | ||
{ | ||
// A tarball with a single file with a rooted path: "\ sample.txt" | ||
const string zipWithRootedPath = """ | ||
H4sIAAAAAAAAA+3OMQ7CMBBE0T3KngCtsY0PwDVoUlghkiEoNhLHB | ||
5QmFdBEEdJ/zRQzxZy0dpdbybv2aLISe0kpvdOlaMucuSAuHIKPto | ||
/eizmXfBS1tQ4t3WvrJlXpp9x/2n3r/9Q5lzLqcaxtuG79BQAAAAA | ||
AAAAAAAAAAADwuyfh1ptHACgAAA== | ||
"""; | ||
var baseDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); | ||
using var unzipped = new MemoryStream(); | ||
fileWrapper.Create($"""{baseDirectory}\ sample.txt""").Returns(unzipped); | ||
using var archive = new MemoryStream(Convert.FromBase64String(zipWithRootedPath)); | ||
|
||
CreateUnpacker().Unpack(archive, baseDirectory); | ||
|
||
directoryWrapper.Received(1).CreateDirectory(baseDirectory); | ||
Encoding.UTF8.GetString(unzipped.ToArray()).NormalizeLineEndings().Should().Be("hello Costin"); | ||
} | ||
|
||
[TestMethod] | ||
public void TarGzUnpacking_Fails_InvalidZipFile() | ||
{ | ||
var baseDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); | ||
using var archive = new MemoryStream([1, 2, 3]); // Invalid archive content | ||
var sut = CreateUnpacker(); | ||
|
||
var action = () => sut.Unpack(archive, baseDirectory); | ||
|
||
action.Should().Throw<Exception>().WithMessage("Error GZIP header, first magic byte doesn't match"); | ||
directoryWrapper.Received(0).CreateDirectory(Arg.Any<string>()); | ||
fileWrapper.Received(0).Create(Arg.Any<string>()); | ||
} | ||
|
||
[TestMethod] | ||
public void TarGzUnpacking_ZipSlip_IsDetected() | ||
{ | ||
// slip.tar.gz from https://github.com/kevva/decompress/issues/71 | ||
// google "Zip Slip Vulnerability" for details | ||
const string zipSlip = """ | ||
H4sICJDill0C/215LXNsaXAudGFyAO3TvQrCMBSG4cxeRa4gTdKk | ||
XRUULHQo2MlNUET8K7aC9OrFFsTFn0ELlffhwDmcZEngU4EKhunx | ||
sE43h634Dd161rWL3X1u9sZYa4VMRQfOZbU4Sfn1R/aEUgH1YVX7 | ||
Iih3m6JYLVV1qcQ/6OLnbnmIoibjJvb6sbesESb0znsfGh8Kba1z | ||
XkjdZf6Pdb1bvbj37ryn+Z8nmcyno1zO0iTLJuOBAAAAAAAAAAAA | ||
AAAAQJ9cAZCup/MAKAAA | ||
"""; | ||
var baseDirectory = Path.Combine(Path.GetTempPath(), Path.GetRandomFileName()); | ||
using var zipStream = new MemoryStream(Convert.FromBase64String(zipSlip)); | ||
var sut = CreateUnpacker(); | ||
|
||
var action = () => sut.Unpack(zipStream, baseDirectory); | ||
|
||
action.Should().Throw<InvalidNameException>().WithMessage("Parent traversal in paths is not allowed"); | ||
} | ||
|
||
private TarGzUnpacker CreateUnpacker() => | ||
new(directoryWrapper, fileWrapper, osProvider); | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
130 changes: 130 additions & 0 deletions
130
src/SonarScanner.MSBuild.PreProcessor/JreCaching/TarGzUnpacker.cs
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,130 @@ | ||
/* | ||
* SonarScanner for .NET | ||
* Copyright (C) 2016-2024 SonarSource SA | ||
* mailto: info AT sonarsource DOT com | ||
* | ||
* This program is free software; you can redistribute it and/or | ||
* modify it under the terms of the GNU Lesser General Public | ||
* License as published by the Free Software Foundation; either | ||
* version 3 of the License, or (at your option) any later version. | ||
* | ||
* This program is distributed in the hope that it will be useful, | ||
* but WITHOUT ANY WARRANTY; without even the implied warranty of | ||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU | ||
* Lesser General Public License for more details. | ||
* | ||
* You should have received a copy of the GNU Lesser General Public License | ||
* along with this program; if not, write to the Free Software Foundation, | ||
* Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. | ||
*/ | ||
|
||
using System; | ||
using System.Diagnostics; | ||
using System.Diagnostics.CodeAnalysis; | ||
using System.IO; | ||
using ICSharpCode.SharpZipLib.Core; | ||
using ICSharpCode.SharpZipLib.GZip; | ||
using ICSharpCode.SharpZipLib.Tar; | ||
using SonarScanner.MSBuild.Common; | ||
|
||
namespace SonarScanner.MSBuild.PreProcessor.JreCaching; | ||
|
||
public class TarGzUnpacker(IDirectoryWrapper directoryWrapper, IFileWrapper fileWrapper, IOperatingSystemProvider operatingSystemProvider) : IUnpacker | ||
{ | ||
// ref https://github.com/icsharpcode/SharpZipLib/blob/ff2d7c30bdb2474d507f001bc555405e9f02a0bb/src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs#L608 | ||
public void Unpack(Stream archive, string destinationDirectory) | ||
{ | ||
using var gzip = new GZipInputStream(archive); | ||
using var tarIn = new TarInputStream(gzip, null); | ||
|
||
var destinationFullPath = Path.GetFullPath(destinationDirectory).TrimEnd('/', '\\'); | ||
while (tarIn.GetNextEntry() is { } entry) | ||
{ | ||
if (entry.TarHeader.TypeFlag is not (TarHeader.LF_LINK or TarHeader.LF_SYMLINK)) | ||
{ | ||
ExtractEntry(tarIn, destinationFullPath, entry); | ||
} | ||
} | ||
} | ||
|
||
// ref https://github.com/icsharpcode/SharpZipLib/blob/ff2d7c30bdb2474d507f001bc555405e9f02a0bb/src/ICSharpCode.SharpZipLib/Tar/TarArchive.cs#L644 | ||
private void ExtractEntry(TarInputStream tar, string destinationFullPath, TarEntry entry) | ||
{ | ||
var name = entry.Name; | ||
if (Path.IsPathRooted(name)) | ||
{ | ||
// NOTE: | ||
// for UNC names... \\machine\share\zoom\beet.txt gives \zoom\beet.txt | ||
name = name.Substring(Path.GetPathRoot(name).Length); | ||
} | ||
|
||
name = name.Replace('/', Path.DirectorySeparatorChar); | ||
var destinationFile = Path.Combine(destinationFullPath, name); | ||
var destinationFileDirectory = Path.GetDirectoryName(Path.GetFullPath(destinationFile)) ?? string.Empty; | ||
var isRootDir = entry.IsDirectory && entry.Name == string.Empty; | ||
|
||
if (!isRootDir && !destinationFileDirectory.StartsWith(destinationFullPath, StringComparison.InvariantCultureIgnoreCase)) | ||
{ | ||
throw new InvalidNameException("Parent traversal in paths is not allowed"); | ||
} | ||
|
||
if (entry.IsDirectory) | ||
{ | ||
directoryWrapper.CreateDirectory(destinationFile); | ||
} | ||
else | ||
{ | ||
directoryWrapper.CreateDirectory(destinationFileDirectory); | ||
using var outputStream = fileWrapper.Create(destinationFile); | ||
tar.CopyEntryContents(outputStream); | ||
outputStream.Close(); | ||
try | ||
{ | ||
SetPermissions(operatingSystemProvider, entry, destinationFile); | ||
} | ||
catch (Exception ex) // TODO: Test this when SetPermissions is extracted | ||
{ | ||
// TODO: Add some verbose logging and inject ILogger | ||
} | ||
} | ||
|
||
// TODO: Move this into an IFilePermissionProvider | ||
[ExcludeFromCodeCoverage] | ||
static void SetPermissions(IOperatingSystemProvider operatingSystemProvider, TarEntry source, string destination) | ||
{ | ||
if (operatingSystemProvider.IsUnix()) | ||
{ | ||
if (operatingSystemProvider.OperatingSystem() is PlatformOS.Alpine) | ||
{ | ||
// https://github.com/Jackett/Jackett/blob/master/src/Jackett.Server/Services/FilePermissionService.cs#L27 | ||
var process = new Process | ||
{ | ||
StartInfo = new ProcessStartInfo | ||
{ | ||
RedirectStandardError = true, | ||
UseShellExecute = false, | ||
CreateNoWindow = true, | ||
WindowStyle = ProcessWindowStyle.Hidden, | ||
FileName = "chmod", | ||
Arguments = $"""{Convert.ToString(source.TarHeader.Mode, 8)} "{destination}" """, | ||
} | ||
}; | ||
process.Start(); | ||
var stdError = process.StandardError.ReadToEnd(); | ||
process.WaitForExit(); | ||
if (process.ExitCode != 0) | ||
{ | ||
throw new InvalidOperationException(stdError); | ||
} | ||
} | ||
else | ||
{ | ||
_ = new Mono.Unix.UnixFileInfo(destination) | ||
{ | ||
FileAccessPermissions = (Mono.Unix.FileAccessPermissions)source.TarHeader.Mode // set the same permissions as inside the archive | ||
}; | ||
} | ||
} | ||
} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters