diff --git a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs index 7eb0d539812c..a8ce96539169 100644 --- a/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs +++ b/csharp/autobuilder/Semmle.Autobuild.CSharp.Tests/BuildScripts.cs @@ -158,6 +158,10 @@ IEnumerable IBuildActions.EnumerateDirectories(string dir) bool IBuildActions.IsMacOs() => IsMacOs; + public bool IsLinux { get; set; } + + bool IBuildActions.IsLinux() => IsLinux; + public bool IsRunningOnAppleSilicon { get; set; } bool IBuildActions.IsRunningOnAppleSilicon() => IsRunningOnAppleSilicon; diff --git a/csharp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs b/csharp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs index afa4ea4b41c3..fd5e4073d6d9 100644 --- a/csharp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs +++ b/csharp/autobuilder/Semmle.Autobuild.Cpp.Tests/BuildScripts.cs @@ -146,6 +146,10 @@ IEnumerable IBuildActions.EnumerateDirectories(string dir) bool IBuildActions.IsMacOs() => IsMacOs; + public bool IsLinux { get; set; } + + bool IBuildActions.IsLinux() => IsLinux; + public bool IsRunningOnAppleSilicon { get; set; } bool IBuildActions.IsRunningOnAppleSilicon() => IsRunningOnAppleSilicon; diff --git a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs index 29cec8a0e339..1d01412ee051 100644 --- a/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs +++ b/csharp/extractor/Semmle.Extraction.CSharp.DependencyFetching/NugetPackageRestorer.cs @@ -814,6 +814,43 @@ private IEnumerable GetFeeds(Func> getNugetFeeds) private (HashSet explicitFeeds, HashSet allFeeds) GetAllFeeds() { var nugetConfigs = fileProvider.NugetConfigs; + + // On systems with case-sensitive file systems (for simplicity, we assume that is Linux), the + // filenames of NuGet configuration files must be named correctly. For compatibility with projects + // that are typically built on Windows or macOS where this doesn't matter, we accept all variants + // of `nuget.config` ourselves. However, `dotnet` does not. If we detect that incorrectly-named + // files are present, we emit a diagnostic to warn the user. + if (SystemBuildActions.Instance.IsLinux()) + { + string[] acceptedNugetConfigNames = ["nuget.config", "NuGet.config", "NuGet.Config"]; + var invalidNugetConfigs = nugetConfigs + .Where(path => !acceptedNugetConfigNames.Contains(Path.GetFileName(path))); + + if (invalidNugetConfigs.Count() > 0) + { + this.logger.LogWarning(string.Format( + "Found incorrectly named NuGet configuration files: {0}", + string.Join(", ", invalidNugetConfigs) + )); + this.diagnosticsWriter.AddEntry(new DiagnosticMessage( + Language.CSharp, + "buildless/case-sensitive-nuget-config", + "Found NuGet configuration files which are not correctly named", + visibility: new DiagnosticMessage.TspVisibility(statusPage: true, cliSummaryTable: true, telemetry: true), + markdownMessage: string.Format( + "On platforms with case-sensitive file systems, NuGet only accepts files with one of the following names: {0}.\n\n" + + "CodeQL found the following files while performing an analysis on a platform with a case-sensitive file system:\n\n" + + "{1}\n\n" + + "To avoid unexpected results, rename these files to match the casing of one of the accepted filenames.", + string.Join(", ", acceptedNugetConfigNames), + string.Join("\n", invalidNugetConfigs.Select(path => string.Format("- `{0}`", path))) + ), + severity: DiagnosticMessage.TspSeverity.Warning + )); + } + } + + // Find feeds that are explicitly configured in the NuGet configuration files that we found. var explicitFeeds = nugetConfigs .SelectMany(config => GetFeeds(() => dotnet.GetNugetFeeds(config))) .ToHashSet(); @@ -849,6 +886,14 @@ private IEnumerable GetFeeds(Func> getNugetFeeds) .Where(folder => folder != null) .SelectMany(folder => GetFeeds(() => dotnet.GetNugetFeedsFromFolder(folder!))) .ToHashSet(); + + // If we have discovered any explicit feeds, then we also expect these to be in the set of all feeds. + // Normally, it is a safe assumption to make that `GetNugetFeedsFromFolder` will include the feeds configured + // in a NuGet configuration file in the given directory. There is one exception: on a system with case-sensitive + // file systems, we may discover a configuration file such as `Nuget.Config` which is not recognised by `dotnet nuget`. + // In that case, our call to `GetNugetFeeds` will retrieve the feeds from that file (because it is accepted when + // provided explicitly as `--configfile` argument), but the call to `GetNugetFeedsFromFolder` will not. + allFeeds.UnionWith(explicitFeeds); } else { diff --git a/csharp/extractor/Semmle.Util/BuildActions.cs b/csharp/extractor/Semmle.Util/BuildActions.cs index 38210402945b..09696564efc5 100644 --- a/csharp/extractor/Semmle.Util/BuildActions.cs +++ b/csharp/extractor/Semmle.Util/BuildActions.cs @@ -119,6 +119,12 @@ public interface IBuildActions /// True if we are running on macOS. bool IsMacOs(); + /// + /// Gets a value indicating whether we are running on Linux. + /// + /// True if we are running on Linux. + bool IsLinux(); + /// /// Gets a value indicating whether we are running on Apple Silicon. /// @@ -246,6 +252,8 @@ int IBuildActions.RunProcess(string cmd, string args, string? workingDirectory, bool IBuildActions.IsMacOs() => RuntimeInformation.IsOSPlatform(OSPlatform.OSX); + bool IBuildActions.IsLinux() => RuntimeInformation.IsOSPlatform(OSPlatform.Linux); + bool IBuildActions.IsRunningOnAppleSilicon() { var thisBuildActions = (IBuildActions)this; diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/Program.cs b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/Program.cs new file mode 100644 index 000000000000..39a9e95bb6e3 --- /dev/null +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/Program.cs @@ -0,0 +1,6 @@ +class Program +{ + static void Main(string[] args) + { + } +} \ No newline at end of file diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/diagnostics.expected b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/diagnostics.expected new file mode 100644 index 000000000000..063a2659f861 --- /dev/null +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/diagnostics.expected @@ -0,0 +1,42 @@ +{ + "markdownMessage": "C# analysis with build-mode 'none' completed.", + "severity": "unknown", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/buildless/complete", + "name": "C# analysis with build-mode 'none' completed" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": false, + "telemetry": true + } +} +{ + "markdownMessage": "C# was extracted with build-mode set to 'none'. This means that all C# source in the working directory will be scanned, with build tools, such as NuGet and dotnet CLIs, only contributing information about external dependencies.", + "severity": "note", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/buildless/mode-active", + "name": "C# was extracted with build-mode set to 'none'" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} +{ + "markdownMessage": "On platforms with case-sensitive file systems, NuGet only accepts files with one of the following names: nuget.config, NuGet.config, NuGet.Config.\n\nCodeQL found the following files while performing an analysis on a platform with a case-sensitive file system:\n\n- `/sub-project/Nuget.Config`\n\nTo avoid unexpected results, rename these files to match the casing of one of the accepted filenames.", + "severity": "warning", + "source": { + "extractorName": "csharp", + "id": "csharp/autobuilder/buildless/case-sensitive-nuget-config", + "name": "Found NuGet configuration files which are not correctly named" + }, + "visibility": { + "cliSummaryTable": true, + "statusPage": true, + "telemetry": true + } +} diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/global.json b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/global.json new file mode 100644 index 000000000000..481e95ec7be1 --- /dev/null +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/global.json @@ -0,0 +1,5 @@ +{ + "sdk": { + "version": "10.0.100" + } +} diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/sub-project/Nuget.Config b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/sub-project/Nuget.Config new file mode 100644 index 000000000000..aa5beec8aa09 --- /dev/null +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/sub-project/Nuget.Config @@ -0,0 +1,5 @@ + + + + + diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.csproj b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.csproj new file mode 100644 index 000000000000..a15a29bf12c2 --- /dev/null +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.csproj @@ -0,0 +1,8 @@ + + + + Exe + net10.0 + + + diff --git a/csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.py b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.py new file mode 100644 index 000000000000..a5d5f3fe03a4 --- /dev/null +++ b/csharp/ql/integration-tests/linux/diag_nuget_config_casing/test.py @@ -0,0 +1,5 @@ +import runs_on + +@runs_on.linux +def test(codeql, csharp): + codeql.database.create(build_mode="none")