mirror of
https://github.com/github/codeql.git
synced 2026-05-30 02:51:24 +02:00
Compare commits
20 Commits
kaeluka/ap
...
mbg/csharp
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
2bce3879db | ||
|
|
a126e1dfae | ||
|
|
ebc4aaa3db | ||
|
|
373eb0a57e | ||
|
|
adde95d7a1 | ||
|
|
098812b073 | ||
|
|
ca334021ad | ||
|
|
69ab389d9f | ||
|
|
30f0b8ab2b | ||
|
|
a5e7ef424e | ||
|
|
84e58b77aa | ||
|
|
3092640115 | ||
|
|
143e1680bd | ||
|
|
d7c97d9d92 | ||
|
|
30925da7d9 | ||
|
|
947b094387 | ||
|
|
009d58034f | ||
|
|
2eb67549e6 | ||
|
|
afe318edbe | ||
|
|
a46a7fadb2 |
@@ -1,24 +0,0 @@
|
||||
#include <stdio.h>
|
||||
|
||||
char *copy;
|
||||
|
||||
void copyArgv(char **argv) {
|
||||
copy = argv[1];
|
||||
}
|
||||
|
||||
void printWrapper(char *str) {
|
||||
printf(str);
|
||||
}
|
||||
|
||||
int main(int argc, char **argv) {
|
||||
copyArgv(argv);
|
||||
|
||||
// This should be avoided
|
||||
printf(copy);
|
||||
|
||||
// This should be avoided too, because it has the same effect
|
||||
printWrapper(copy);
|
||||
|
||||
// This is fine
|
||||
printf("%s", copy);
|
||||
}
|
||||
@@ -1,36 +0,0 @@
|
||||
<!DOCTYPE qhelp PUBLIC
|
||||
"-//Semmle//qhelp//EN"
|
||||
"qhelp.dtd">
|
||||
<qhelp>
|
||||
<overview>
|
||||
<p>The program uses input from the user, propagated via a global variable, as a format string for <code>printf</code> style functions.
|
||||
This can lead to buffer overflows or data representation problems. An attacker can exploit this weakness to crash the program,
|
||||
disclose information or even execute arbitrary code.</p>
|
||||
|
||||
<p>This rule only identifies inputs from the user that are transferred through global variables before being used in <code>printf</code> style functions.
|
||||
Analyzing the flow of data through global variables is more prone to errors and so this rule may identify some examples of code where
|
||||
the input is not really from the user. For example, when a global variable is set in two places, one that comes from the user and one that does not.
|
||||
In this case we would mark all usages of the global variable as input from the user, but the input from the user may always came after the call to the
|
||||
<code>printf</code> style functions.</p>
|
||||
|
||||
<p>The results of this rule should be considered alongside the related rule "Uncontrolled format string" which tracks the flow of the
|
||||
values input by a user, excluding global variables, until the values are used as the format argument for a <code>printf</code> like function call.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
<p>Use constant expressions as the format strings. If you need to print a value from the user, use <code>printf("%s", value_from_user)</code>.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
<sample src="UncontrolledFormatStringThroughGlobalVar.c" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
<li>CERT C Coding
|
||||
Standard: <a href="https://www.securecoding.cert.org/confluence/display/c/FIO30-C.+Exclude+user+input+from+format+strings">FIO30-C. Exclude
|
||||
user input from format strings</a>.</li>
|
||||
|
||||
|
||||
</references>
|
||||
</qhelp>
|
||||
@@ -1,40 +0,0 @@
|
||||
/**
|
||||
* @name Uncontrolled format string (through global variable)
|
||||
* @description Using externally-controlled format strings in
|
||||
* printf-style functions can lead to buffer overflows
|
||||
* or data representation problems.
|
||||
* @kind path-problem
|
||||
* @problem.severity warning
|
||||
* @security-severity 9.3
|
||||
* @precision high
|
||||
* @id cpp/tainted-format-string-through-global
|
||||
* @tags reliability
|
||||
* security
|
||||
* external/cwe/cwe-134
|
||||
*/
|
||||
|
||||
import cpp
|
||||
import semmle.code.cpp.security.FunctionWithWrappers
|
||||
import semmle.code.cpp.security.Security
|
||||
import semmle.code.cpp.ir.dataflow.internal.DefaultTaintTrackingImpl
|
||||
import TaintedWithPath
|
||||
|
||||
class Configuration extends TaintTrackingConfiguration {
|
||||
override predicate isSink(Element tainted) {
|
||||
exists(PrintfLikeFunction printf | printf.outermostWrapperFunctionCall(tainted, _))
|
||||
}
|
||||
|
||||
override predicate taintThroughGlobals() { any() }
|
||||
}
|
||||
|
||||
from
|
||||
PrintfLikeFunction printf, Expr arg, PathNode sourceNode, PathNode sinkNode,
|
||||
string printfFunction, Expr userValue, string cause
|
||||
where
|
||||
printf.outermostWrapperFunctionCall(arg, printfFunction) and
|
||||
not taintedWithoutGlobals(arg) and
|
||||
taintedWithPath(userValue, arg, sourceNode, sinkNode) and
|
||||
isUserInput(userValue, cause)
|
||||
select arg, sourceNode, sinkNode,
|
||||
"The value of this argument may come from $@ and is being used as a formatting argument to " +
|
||||
printfFunction + ".", userValue, cause
|
||||
@@ -0,0 +1,4 @@
|
||||
---
|
||||
category: breaking
|
||||
---
|
||||
* The `cpp/tainted-format-string-through-global` query has been deleted. This does not lead to a loss of relevant alerts, as the query duplicated a subset of the alerts from `cpp/tainted-format-string`.
|
||||
@@ -1,69 +0,0 @@
|
||||
edges
|
||||
| globalVars.c:8:7:8:10 | copy | globalVars.c:27:9:27:12 | copy |
|
||||
| globalVars.c:8:7:8:10 | copy | globalVars.c:27:9:27:12 | copy |
|
||||
| globalVars.c:8:7:8:10 | copy | globalVars.c:30:15:30:18 | copy |
|
||||
| globalVars.c:8:7:8:10 | copy | globalVars.c:30:15:30:18 | copy |
|
||||
| globalVars.c:8:7:8:10 | copy | globalVars.c:30:15:30:18 | copy |
|
||||
| globalVars.c:8:7:8:10 | copy | globalVars.c:33:15:33:18 | copy |
|
||||
| globalVars.c:8:7:8:10 | copy | globalVars.c:35:11:35:14 | copy |
|
||||
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:38:9:38:13 | copy2 |
|
||||
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:38:9:38:13 | copy2 |
|
||||
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:41:15:41:19 | copy2 |
|
||||
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:41:15:41:19 | copy2 |
|
||||
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:41:15:41:19 | copy2 |
|
||||
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:44:15:44:19 | copy2 |
|
||||
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:50:9:50:13 | copy2 |
|
||||
| globalVars.c:9:7:9:11 | copy2 | globalVars.c:50:9:50:13 | copy2 |
|
||||
| globalVars.c:11:22:11:25 | argv | globalVars.c:8:7:8:10 | copy |
|
||||
| globalVars.c:11:22:11:25 | argv | globalVars.c:12:2:12:15 | ... = ... |
|
||||
| globalVars.c:12:2:12:15 | ... = ... | globalVars.c:8:7:8:10 | copy |
|
||||
| globalVars.c:15:21:15:23 | val | globalVars.c:9:7:9:11 | copy2 |
|
||||
| globalVars.c:15:21:15:23 | val | globalVars.c:16:2:16:12 | ... = ... |
|
||||
| globalVars.c:16:2:16:12 | ... = ... | globalVars.c:9:7:9:11 | copy2 |
|
||||
| globalVars.c:24:11:24:14 | argv | globalVars.c:11:22:11:25 | argv |
|
||||
| globalVars.c:24:11:24:14 | argv | globalVars.c:11:22:11:25 | argv |
|
||||
| globalVars.c:30:15:30:18 | copy | globalVars.c:30:15:30:18 | copy |
|
||||
| globalVars.c:30:15:30:18 | copy | globalVars.c:30:15:30:18 | copy |
|
||||
| globalVars.c:30:15:30:18 | copy | globalVars.c:30:15:30:18 | copy |
|
||||
| globalVars.c:30:15:30:18 | copy | globalVars.c:35:11:35:14 | copy |
|
||||
| globalVars.c:33:15:33:18 | copy | globalVars.c:35:11:35:14 | copy |
|
||||
| globalVars.c:35:11:35:14 | copy | globalVars.c:15:21:15:23 | val |
|
||||
| globalVars.c:35:11:35:14 | copy | globalVars.c:35:11:35:14 | copy |
|
||||
| globalVars.c:41:15:41:19 | copy2 | globalVars.c:41:15:41:19 | copy2 |
|
||||
| globalVars.c:41:15:41:19 | copy2 | globalVars.c:41:15:41:19 | copy2 |
|
||||
| globalVars.c:41:15:41:19 | copy2 | globalVars.c:41:15:41:19 | copy2 |
|
||||
| globalVars.c:41:15:41:19 | copy2 | globalVars.c:50:9:50:13 | copy2 |
|
||||
| globalVars.c:41:15:41:19 | copy2 | globalVars.c:50:9:50:13 | copy2 |
|
||||
| globalVars.c:44:15:44:19 | copy2 | globalVars.c:50:9:50:13 | copy2 |
|
||||
| globalVars.c:44:15:44:19 | copy2 | globalVars.c:50:9:50:13 | copy2 |
|
||||
subpaths
|
||||
nodes
|
||||
| globalVars.c:8:7:8:10 | copy | semmle.label | copy |
|
||||
| globalVars.c:9:7:9:11 | copy2 | semmle.label | copy2 |
|
||||
| globalVars.c:11:22:11:25 | argv | semmle.label | argv |
|
||||
| globalVars.c:12:2:12:15 | ... = ... | semmle.label | ... = ... |
|
||||
| globalVars.c:15:21:15:23 | val | semmle.label | val |
|
||||
| globalVars.c:16:2:16:12 | ... = ... | semmle.label | ... = ... |
|
||||
| globalVars.c:24:11:24:14 | argv | semmle.label | argv |
|
||||
| globalVars.c:24:11:24:14 | argv | semmle.label | argv |
|
||||
| globalVars.c:27:9:27:12 | copy | semmle.label | copy |
|
||||
| globalVars.c:27:9:27:12 | copy | semmle.label | copy |
|
||||
| globalVars.c:30:15:30:18 | copy | semmle.label | copy |
|
||||
| globalVars.c:30:15:30:18 | copy | semmle.label | copy |
|
||||
| globalVars.c:30:15:30:18 | copy | semmle.label | copy |
|
||||
| globalVars.c:33:15:33:18 | copy | semmle.label | copy |
|
||||
| globalVars.c:35:11:35:14 | copy | semmle.label | copy |
|
||||
| globalVars.c:38:9:38:13 | copy2 | semmle.label | copy2 |
|
||||
| globalVars.c:38:9:38:13 | copy2 | semmle.label | copy2 |
|
||||
| globalVars.c:41:15:41:19 | copy2 | semmle.label | copy2 |
|
||||
| globalVars.c:41:15:41:19 | copy2 | semmle.label | copy2 |
|
||||
| globalVars.c:41:15:41:19 | copy2 | semmle.label | copy2 |
|
||||
| globalVars.c:44:15:44:19 | copy2 | semmle.label | copy2 |
|
||||
| globalVars.c:50:9:50:13 | copy2 | semmle.label | copy2 |
|
||||
| globalVars.c:50:9:50:13 | copy2 | semmle.label | copy2 |
|
||||
#select
|
||||
| globalVars.c:27:9:27:12 | copy | globalVars.c:24:11:24:14 | argv | globalVars.c:27:9:27:12 | copy | The value of this argument may come from $@ and is being used as a formatting argument to printf(format). | globalVars.c:24:11:24:14 | argv | argv |
|
||||
| globalVars.c:30:15:30:18 | copy | globalVars.c:24:11:24:14 | argv | globalVars.c:30:15:30:18 | copy | The value of this argument may come from $@ and is being used as a formatting argument to printWrapper(str), which calls printf(format). | globalVars.c:24:11:24:14 | argv | argv |
|
||||
| globalVars.c:38:9:38:13 | copy2 | globalVars.c:24:11:24:14 | argv | globalVars.c:38:9:38:13 | copy2 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format). | globalVars.c:24:11:24:14 | argv | argv |
|
||||
| globalVars.c:41:15:41:19 | copy2 | globalVars.c:24:11:24:14 | argv | globalVars.c:41:15:41:19 | copy2 | The value of this argument may come from $@ and is being used as a formatting argument to printWrapper(str), which calls printf(format). | globalVars.c:24:11:24:14 | argv | argv |
|
||||
| globalVars.c:50:9:50:13 | copy2 | globalVars.c:24:11:24:14 | argv | globalVars.c:50:9:50:13 | copy2 | The value of this argument may come from $@ and is being used as a formatting argument to printf(format). | globalVars.c:24:11:24:14 | argv | argv |
|
||||
@@ -1 +0,0 @@
|
||||
Security/CWE/CWE-134/UncontrolledFormatStringThroughGlobalVar.ql
|
||||
@@ -559,6 +559,7 @@ namespace Semmle.Autobuild.CSharp.Tests
|
||||
{
|
||||
actions.RunProcess[@"C:\codeql\csharp/tools/linux64/Semmle.Extraction.CSharp.Standalone"] = 0;
|
||||
actions.FileExists["csharp.log"] = true;
|
||||
actions.FileExists["test.sln"] = false;
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = "";
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = "";
|
||||
actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
|
||||
@@ -573,6 +574,7 @@ namespace Semmle.Autobuild.CSharp.Tests
|
||||
{
|
||||
actions.RunProcess[@"C:\codeql\csharp/tools/linux64/Semmle.Extraction.CSharp.Standalone"] = 10;
|
||||
actions.FileExists["csharp.log"] = true;
|
||||
actions.FileExists["test.sln"] = false;
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = "";
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = "";
|
||||
actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
|
||||
@@ -587,6 +589,7 @@ namespace Semmle.Autobuild.CSharp.Tests
|
||||
{
|
||||
actions.RunProcess[@"C:\codeql\csharp/tools/linux64/Semmle.Extraction.CSharp.Standalone foo.sln"] = 0;
|
||||
actions.FileExists["csharp.log"] = true;
|
||||
actions.FileExists["foo.sln"] = false;
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = "";
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = "";
|
||||
actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
|
||||
@@ -629,6 +632,7 @@ namespace Semmle.Autobuild.CSharp.Tests
|
||||
{
|
||||
actions.RunProcess["./build.sh --skip-tests"] = 0;
|
||||
actions.FileExists["csharp.log"] = true;
|
||||
actions.FileExists["test.sln"] = false;
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = "";
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = "";
|
||||
actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
|
||||
@@ -729,6 +733,7 @@ namespace Semmle.Autobuild.CSharp.Tests
|
||||
actions.RunProcess[@"cmd.exe /C C:\codeql\tools\java\bin\java -jar C:\codeql\csharp\tools\extractor-asp.jar ."] = 0;
|
||||
actions.RunProcess[@"cmd.exe /C C:\codeql\tools\codeql index --xml --extensions config"] = 0;
|
||||
actions.FileExists["csharp.log"] = true;
|
||||
actions.FileExists[@"C:\Project\test.sln"] = false;
|
||||
SkipVsWhere();
|
||||
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = "";
|
||||
@@ -875,6 +880,7 @@ namespace Semmle.Autobuild.CSharp.Tests
|
||||
{
|
||||
actions.RunProcess[@"C:\codeql\csharp/tools/linux64/Semmle.Extraction.CSharp.Standalone foo.sln --skip-nuget"] = 0;
|
||||
actions.FileExists["csharp.log"] = true;
|
||||
actions.FileExists["foo.sln"] = false;
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = "";
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = "";
|
||||
actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.sln";
|
||||
@@ -946,6 +952,40 @@ namespace Semmle.Autobuild.CSharp.Tests
|
||||
TestAutobuilderScript(autobuilder, 0, 8);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDotnetVersionInstalledFromTargetFramework()
|
||||
{
|
||||
actions.RunProcess["dotnet --list-sdks"] = 0;
|
||||
actions.RunProcessOut["dotnet --list-sdks"] = "6.0.404 [C:\\Program Files\\dotnet\\sdks]\n7.0.102 [C:\\Program Files\\dotnet\\sdks]";
|
||||
actions.RunProcess[@"chmod u+x dotnet-install.sh"] = 0;
|
||||
actions.RunProcess[@"./dotnet-install.sh --channel 8.0 --version latest --install-dir C:\Project/.dotnet"] = 0;
|
||||
actions.RunProcess[@"rm dotnet-install.sh"] = 0;
|
||||
actions.RunProcess[@"C:\Project/.dotnet/dotnet --info"] = 0;
|
||||
actions.RunProcess[@"C:\Project/.dotnet/dotnet clean C:\Project/test.csproj"] = 0;
|
||||
actions.RunProcess[@"C:\Project/.dotnet/dotnet restore C:\Project/test.csproj"] = 0;
|
||||
actions.RunProcess[@"C:\Project/.dotnet/dotnet build --no-incremental C:\Project/test.csproj"] = 0;
|
||||
actions.FileExists["csharp.log"] = true;
|
||||
actions.FileExists["test.csproj"] = true;
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_TRAP_DIR"] = "";
|
||||
actions.GetEnvironmentVariable["CODEQL_EXTRACTOR_CSHARP_SOURCE_ARCHIVE_DIR"] = "";
|
||||
actions.GetEnvironmentVariable["PATH"] = "/bin:/usr/bin";
|
||||
actions.EnumerateFiles[@"C:\Project"] = "foo.cs\ntest.cs\ntest.csproj";
|
||||
actions.EnumerateDirectories[@"C:\Project"] = "";
|
||||
var xml = new XmlDocument();
|
||||
xml.LoadXml(@"<Project Sdk=""Microsoft.NET.Sdk"">
|
||||
<PropertyGroup>
|
||||
<OutputType>Exe</OutputType>
|
||||
<TargetFramework>net8.0</TargetFramework>
|
||||
</PropertyGroup>
|
||||
|
||||
</Project>");
|
||||
actions.LoadXml[@"C:\Project/test.csproj"] = xml;
|
||||
actions.DownloadFiles.Add(("https://dot.net/v1/dotnet-install.sh", "dotnet-install.sh"));
|
||||
|
||||
var autobuilder = CreateAutoBuilder(false);
|
||||
TestAutobuilderScript(autobuilder, 0, 8);
|
||||
}
|
||||
|
||||
[Fact]
|
||||
public void TestDotnetVersionAlreadyInstalled()
|
||||
{
|
||||
@@ -1016,7 +1056,7 @@ namespace Semmle.Autobuild.CSharp.Tests
|
||||
{
|
||||
TestDotnetVersionWindows(() =>
|
||||
{
|
||||
actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 0;
|
||||
actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Channel release -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 0;
|
||||
},
|
||||
6);
|
||||
}
|
||||
@@ -1026,8 +1066,8 @@ namespace Semmle.Autobuild.CSharp.Tests
|
||||
{
|
||||
TestDotnetVersionWindows(() =>
|
||||
{
|
||||
actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 1;
|
||||
actions.RunProcess[@"cmd.exe /C powershell -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 0;
|
||||
actions.RunProcess[@"cmd.exe /C pwsh -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Channel release -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 1;
|
||||
actions.RunProcess[@"cmd.exe /C powershell -NoProfile -ExecutionPolicy unrestricted -Command ""[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Channel release -Version 2.1.3 -InstallDir C:\Project\.dotnet"""] = 0;
|
||||
},
|
||||
7);
|
||||
}
|
||||
|
||||
@@ -119,20 +119,13 @@ namespace Semmle.Autobuild.CSharp
|
||||
=> WithDotNet(builder, (_1, env) => f(env));
|
||||
|
||||
/// <summary>
|
||||
/// Returns a script for downloading relevant versions of the
|
||||
/// .NET Core SDK. The SDK(s) will be installed at <code>installDir</code>
|
||||
/// (provided that the script succeeds).
|
||||
/// Tries to determine the version of .NET that's required for the project from a <code>global.json</code> file, if present.
|
||||
/// </summary>
|
||||
private static BuildScript DownloadDotNet(IAutobuilder<AutobuildOptionsShared> builder, string installDir)
|
||||
/// <returns>
|
||||
/// Returns the .NET version specified in <code>global.json<code> as a string or null if no version could be retrieved.
|
||||
/// </returns>
|
||||
private static string? SdkVersionFromGlobalJson(IAutobuilder<AutobuildOptionsShared> builder)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(builder.Options.DotNetVersion))
|
||||
// Specific version supplied in configuration: always use that
|
||||
return DownloadDotNetVersion(builder, installDir, builder.Options.DotNetVersion);
|
||||
|
||||
// Download versions mentioned in `global.json` files
|
||||
// See https://docs.microsoft.com/en-us/dotnet/core/tools/global-json
|
||||
var installScript = BuildScript.Success;
|
||||
var validGlobalJson = false;
|
||||
foreach (var path in builder.Paths.Select(p => p.Item1).Where(p => p.EndsWith("global.json", StringComparison.Ordinal)))
|
||||
{
|
||||
string version;
|
||||
@@ -147,11 +140,59 @@ namespace Semmle.Autobuild.CSharp
|
||||
continue;
|
||||
}
|
||||
|
||||
installScript &= DownloadDotNetVersion(builder, installDir, version);
|
||||
validGlobalJson = true;
|
||||
return version;
|
||||
}
|
||||
|
||||
return validGlobalJson ? installScript : BuildScript.Failure;
|
||||
return null;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
/// Returns a script for downloading relevant versions of the
|
||||
/// .NET Core SDK. The SDK(s) will be installed at <code>installDir</code>
|
||||
/// (provided that the script succeeds).
|
||||
/// </summary>
|
||||
private static BuildScript DownloadDotNet(IAutobuilder<AutobuildOptionsShared> builder, string installDir)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(builder.Options.DotNetVersion))
|
||||
// Specific version supplied in configuration: always use that
|
||||
return DownloadDotNetVersion(builder, installDir, builder.Options.DotNetVersion);
|
||||
|
||||
// Download versions mentioned in `global.json` files
|
||||
// See https://docs.microsoft.com/en-us/dotnet/core/tools/global-json
|
||||
var installScript = BuildScript.Success;
|
||||
var foundFrameworkVersion = false;
|
||||
var globalJsonSdkVersion = SdkVersionFromGlobalJson(builder);
|
||||
|
||||
if (!string.IsNullOrEmpty(globalJsonSdkVersion))
|
||||
{
|
||||
installScript &= DownloadDotNetVersion(builder, installDir, globalJsonSdkVersion);
|
||||
foundFrameworkVersion = true;
|
||||
}
|
||||
else
|
||||
{
|
||||
// If there is no `global.json` to retrieve the .NET version from, we find all the
|
||||
// TargetFramework values in the project files and install the latest versions
|
||||
// from the corresponding channels
|
||||
var targetFrameworkVersions = builder.ProjectsOrSolutionsToBuild
|
||||
.SelectMany(p => Enumerators.Singleton(p).Concat(p.IncludedProjects))
|
||||
.OfType<Project<CSharpAutobuildOptions>>()
|
||||
.Select(p => p.TargetFramework)
|
||||
.OfType<string>()
|
||||
.Where(v => v.StartsWith("net"));
|
||||
|
||||
foreach (var targetFrameworkVersion in targetFrameworkVersions)
|
||||
{
|
||||
// For simplicity, we only accept target frameworks of the form "netX.Y"
|
||||
Version? version = null;
|
||||
if (Version.TryParse(targetFrameworkVersion[3..], out version))
|
||||
{
|
||||
installScript &= DownloadDotNetVersion(builder, installDir, "latest", channel: version.ToString());
|
||||
foundFrameworkVersion = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return foundFrameworkVersion ? installScript : BuildScript.Failure;
|
||||
}
|
||||
|
||||
/// <summary>
|
||||
@@ -160,7 +201,7 @@ namespace Semmle.Autobuild.CSharp
|
||||
///
|
||||
/// See https://docs.microsoft.com/en-us/dotnet/core/tools/dotnet-install-script.
|
||||
/// </summary>
|
||||
private static BuildScript DownloadDotNetVersion(IAutobuilder<AutobuildOptionsShared> builder, string path, string version)
|
||||
private static BuildScript DownloadDotNetVersion(IAutobuilder<AutobuildOptionsShared> builder, string path, string version, string? channel = "release")
|
||||
{
|
||||
return BuildScript.Bind(GetInstalledSdksScript(builder.Actions), (sdks, sdksRet) =>
|
||||
{
|
||||
@@ -174,7 +215,7 @@ namespace Semmle.Autobuild.CSharp
|
||||
if (builder.Actions.IsWindows())
|
||||
{
|
||||
|
||||
var psCommand = $"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Version {version} -InstallDir {path}";
|
||||
var psCommand = $"[Net.ServicePointManager]::SecurityProtocol = [Net.SecurityProtocolType]::Tls12; &([scriptblock]::Create((Invoke-WebRequest -UseBasicParsing 'https://dot.net/v1/dotnet-install.ps1'))) -Channel {channel} -Version {version} -InstallDir {path}";
|
||||
|
||||
BuildScript GetInstall(string pwsh) =>
|
||||
new CommandBuilder(builder.Actions).
|
||||
@@ -203,7 +244,7 @@ namespace Semmle.Autobuild.CSharp
|
||||
var install = new CommandBuilder(builder.Actions).
|
||||
RunCommand("./dotnet-install.sh").
|
||||
Argument("--channel").
|
||||
Argument("release").
|
||||
Argument(channel).
|
||||
Argument("--version").
|
||||
Argument(version).
|
||||
Argument("--install-dir").
|
||||
|
||||
@@ -23,6 +23,8 @@ namespace Semmle.Autobuild.Shared
|
||||
|
||||
public Version ToolsVersion { get; private set; }
|
||||
|
||||
public string? TargetFramework { get; private set; }
|
||||
|
||||
private readonly Lazy<List<Project<TAutobuildOptions>>> includedProjectsLazy;
|
||||
public override IEnumerable<IProjectOrSolution> IncludedProjects => includedProjectsLazy.Value;
|
||||
|
||||
@@ -51,7 +53,8 @@ namespace Semmle.Autobuild.Shared
|
||||
{
|
||||
if (root.HasAttribute("Sdk"))
|
||||
{
|
||||
DotNetProject = true;
|
||||
this.DotNetProject = true;
|
||||
this.TargetFramework = root.SelectSingleNode("//Project/PropertyGroup/TargetFramework/text()")?.Value;
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -72,11 +75,12 @@ namespace Semmle.Autobuild.Shared
|
||||
|
||||
includedProjectsLazy = new Lazy<List<Project<TAutobuildOptions>>>(() =>
|
||||
{
|
||||
var mgr = new XmlNamespaceManager(projFile.NameTable);
|
||||
mgr.AddNamespace("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003");
|
||||
|
||||
var ret = new List<Project<TAutobuildOptions>>();
|
||||
// The documentation on `.proj` files is very limited, but it appears that both
|
||||
// `<ProjectFile Include="X"/>` and `<ProjectFiles Include="X"/>` is valid
|
||||
var mgr = new XmlNamespaceManager(projFile.NameTable);
|
||||
mgr.AddNamespace("msbuild", "http://schemas.microsoft.com/developer/msbuild/2003");
|
||||
var projectFileIncludes = root.SelectNodes("//msbuild:Project/msbuild:ItemGroup/msbuild:ProjectFile/@Include", mgr)
|
||||
?.OfType<XmlNode>() ?? Array.Empty<XmlNode>();
|
||||
var projectFilesIncludes = root.SelectNodes("//msbuild:Project/msbuild:ItemGroup/msbuild:ProjectFiles/@Include", mgr)
|
||||
|
||||
@@ -61,6 +61,12 @@ namespace Semmle.Autobuild.Shared
|
||||
{
|
||||
try
|
||||
{
|
||||
// SolutionFile.Parse won't throw a FileNotFoundException if it is given a Windows path on a non-Windows platform
|
||||
if (!builder.Actions.FileExists(FullPath))
|
||||
{
|
||||
throw new FileNotFoundException(FullPath);
|
||||
}
|
||||
|
||||
solution = SolutionFile.Parse(FullPath);
|
||||
}
|
||||
catch (Exception ex) when (ex is InvalidProjectFileException || ex is FileNotFoundException)
|
||||
|
||||
@@ -90,7 +90,7 @@ class PotentialSinkModelExpr extends Expr {
|
||||
string package, string type, boolean subtypes, string name, string signature, string input
|
||||
) {
|
||||
exists(Call call, Callable callable, int argIdx |
|
||||
call.getCallee().getSourceDeclaration() = callable and
|
||||
call.getCallee() = callable and
|
||||
(
|
||||
this = call.getArgument(argIdx)
|
||||
or
|
||||
|
||||
@@ -30,7 +30,9 @@ newtype TApplicationModeEndpoint =
|
||||
arg.asExpr() = argExpr and call = argExpr.getCall() and not argExpr.isVararg()
|
||||
)
|
||||
} or
|
||||
TInstanceArgument(Call call, DataFlow::Node arg) { arg = DataFlow::getInstanceArgument(call) } or
|
||||
TInstanceArgument(Call call, DataFlow::Node arg) {
|
||||
arg = DataFlow::getInstanceArgument(call) and not call instanceof ConstructorCall
|
||||
} or
|
||||
TImplicitVarargsArray(Call call, DataFlow::Node arg, int idx) {
|
||||
exists(Argument argExpr |
|
||||
arg.asExpr() = argExpr and
|
||||
@@ -98,7 +100,7 @@ class ExplicitArgument extends ApplicationModeEndpoint, TExplicitArgument {
|
||||
|
||||
ExplicitArgument() { this = TExplicitArgument(call, arg) }
|
||||
|
||||
override Callable getCallable() { result = call.getCallee().getSourceDeclaration() }
|
||||
override Callable getCallable() { result = call.getCallee() }
|
||||
|
||||
override Call getCall() { result = call }
|
||||
|
||||
@@ -121,7 +123,7 @@ class InstanceArgument extends ApplicationModeEndpoint, TInstanceArgument {
|
||||
|
||||
InstanceArgument() { this = TInstanceArgument(call, arg) }
|
||||
|
||||
override Callable getCallable() { result = call.getCallee().getSourceDeclaration() }
|
||||
override Callable getCallable() { result = call.getCallee() }
|
||||
|
||||
override Call getCall() { result = call }
|
||||
|
||||
@@ -152,7 +154,7 @@ class ImplicitVarargsArray extends ApplicationModeEndpoint, TImplicitVarargsArra
|
||||
|
||||
ImplicitVarargsArray() { this = TImplicitVarargsArray(call, vararg, idx) }
|
||||
|
||||
override Callable getCallable() { result = call.getCallee().getSourceDeclaration() }
|
||||
override Callable getCallable() { result = call.getCallee() }
|
||||
|
||||
override Call getCall() { result = call }
|
||||
|
||||
@@ -176,7 +178,7 @@ class MethodReturnValue extends ApplicationModeEndpoint, TMethodReturnValue {
|
||||
|
||||
MethodReturnValue() { this = TMethodReturnValue(call) }
|
||||
|
||||
override Callable getCallable() { result = call.getCallee().getSourceDeclaration() }
|
||||
override Callable getCallable() { result = call.getCallee() }
|
||||
|
||||
override Call getCall() { result = call }
|
||||
|
||||
@@ -206,7 +208,7 @@ class OverriddenParameter extends ApplicationModeEndpoint, TOverriddenParameter
|
||||
// candidate model will be about the overridden method, not the overriding
|
||||
// method. This is a more general model, that also applies to other
|
||||
// subclasses of the overridden class.
|
||||
result = overriddenMethod.getSourceDeclaration()
|
||||
result = overriddenMethod
|
||||
}
|
||||
|
||||
override Call getCall() { none() }
|
||||
@@ -333,9 +335,6 @@ private module ApplicationModeGetCallable implements AutomodelSharedGetCallable:
|
||||
|
||||
/**
|
||||
* Returns the API callable being modeled.
|
||||
*
|
||||
* We usually want to use `.getSourceDeclaration()` instead of just 'the' callable,
|
||||
* because the source declaration callable has erased generic type parameters.
|
||||
*/
|
||||
Callable getCallable(Endpoint e) { result = e.getCall().getCallee() }
|
||||
}
|
||||
|
||||
@@ -25,7 +25,7 @@ newtype JavaRelatedLocationType =
|
||||
|
||||
newtype TFrameworkModeEndpoint =
|
||||
TExplicitParameter(Parameter p) or
|
||||
TQualifier(Callable c) or
|
||||
TQualifier(Callable c) { not c instanceof Constructor } or
|
||||
TReturnValue(Callable c) or
|
||||
TOverridableParameter(Method m, Parameter p) {
|
||||
p.getCallable() = m and
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
| PluginImpl.java:5:27:5:37 | name | Related locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | PluginImpl.java:5:27:5:37 | name | CallContext | hudson/Plugin.java:5:5:5:31 | /** Configure method doc */ | MethodDoc | hudson/Plugin.java:3:1:3:17 | /** Plugin doc */ | ClassDoc | file://hudson:1:1:1:1 | hudson | package | file://Plugin:1:1:1:1 | Plugin | type | file://true:1:1:1:1 | true | subtypes | file://configure:1:1:1:1 | configure | name | file://(String,String):1:1:1:1 | (String,String) | signature | file://:1:1:1:1 | | input | file://Parameter[0]:1:1:1:1 | Parameter[0] | output | file://false:1:1:1:1 | false | isVarargsArray | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
|
||||
| PluginImpl.java:5:40:5:51 | value | Related locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | PluginImpl.java:5:40:5:51 | value | CallContext | hudson/Plugin.java:5:5:5:31 | /** Configure method doc */ | MethodDoc | hudson/Plugin.java:3:1:3:17 | /** Plugin doc */ | ClassDoc | file://hudson:1:1:1:1 | hudson | package | file://Plugin:1:1:1:1 | Plugin | type | file://true:1:1:1:1 | true | subtypes | file://configure:1:1:1:1 | configure | name | file://(String,String):1:1:1:1 | (String,String) | signature | file://:1:1:1:1 | | input | file://Parameter[1]:1:1:1:1 | Parameter[1] | output | file://false:1:1:1:1 | false | isVarargsArray | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
|
||||
| Test.java:19:3:19:11 | reference | Related locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:19:3:19:24 | set(...) | CallContext | Test.java:19:3:19:11 | reference | MethodDoc | Test.java:19:3:19:11 | reference | ClassDoc | file://java.util.concurrent.atomic:1:1:1:1 | java.util.concurrent.atomic | package | file://AtomicReference:1:1:1:1 | AtomicReference | type | file://false:1:1:1:1 | false | subtypes | file://set:1:1:1:1 | set | name | file://(Object):1:1:1:1 | (Object) | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://:1:1:1:1 | | output | file://false:1:1:1:1 | false | isVarargsArray | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
| Test.java:19:3:19:11 | reference | Related locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:19:3:19:24 | set(...) | CallContext | Test.java:19:3:19:11 | reference | MethodDoc | Test.java:19:3:19:11 | reference | ClassDoc | file://java.util.concurrent.atomic:1:1:1:1 | java.util.concurrent.atomic | package | file://AtomicReference:1:1:1:1 | AtomicReference | type | file://false:1:1:1:1 | false | subtypes | file://set:1:1:1:1 | set | name | file://(String):1:1:1:1 | (String) | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://:1:1:1:1 | | output | file://false:1:1:1:1 | false | isVarargsArray | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
| Test.java:24:3:24:10 | supplier | Related locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:24:3:24:16 | get(...) | CallContext | Test.java:24:3:24:10 | supplier | MethodDoc | Test.java:24:3:24:10 | supplier | ClassDoc | file://java.util.function:1:1:1:1 | java.util.function | package | file://Supplier:1:1:1:1 | Supplier | type | file://true:1:1:1:1 | true | subtypes | file://get:1:1:1:1 | get | name | file://():1:1:1:1 | () | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://:1:1:1:1 | | output | file://false:1:1:1:1 | false | isVarargsArray | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
| Test.java:24:3:24:16 | get(...) | Related locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:24:3:24:16 | get(...) | CallContext | Test.java:24:3:24:16 | get(...) | MethodDoc | Test.java:24:3:24:16 | get(...) | ClassDoc | file://java.util.function:1:1:1:1 | java.util.function | package | file://Supplier:1:1:1:1 | Supplier | type | file://true:1:1:1:1 | true | subtypes | file://get:1:1:1:1 | get | name | file://():1:1:1:1 | () | signature | file://:1:1:1:1 | | input | file://ReturnValue:1:1:1:1 | ReturnValue | output | file://false:1:1:1:1 | false | isVarargsArray | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
|
||||
| Test.java:28:3:32:3 | copy(...) | Related locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:28:3:32:3 | copy(...) | CallContext | Test.java:28:3:32:3 | copy(...) | MethodDoc | Test.java:28:3:32:3 | copy(...) | ClassDoc | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Files:1:1:1:1 | Files | type | file://false:1:1:1:1 | false | subtypes | file://copy:1:1:1:1 | copy | name | file://(Path,Path,CopyOption[]):1:1:1:1 | (Path,Path,CopyOption[]) | signature | file://:1:1:1:1 | | input | file://ReturnValue:1:1:1:1 | ReturnValue | output | file://false:1:1:1:1 | false | isVarargsArray | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
|
||||
@@ -11,4 +11,3 @@
|
||||
| Test.java:56:4:56:4 | o | Related locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:54:3:59:3 | walk(...) | CallContext | Test.java:54:3:59:3 | walk(...) | MethodDoc | Test.java:54:3:59:3 | walk(...) | ClassDoc | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Files:1:1:1:1 | Files | type | file://false:1:1:1:1 | false | subtypes | file://walk:1:1:1:1 | walk | name | file://(Path,FileVisitOption[]):1:1:1:1 | (Path,FileVisitOption[]) | signature | file://Argument[1]:1:1:1:1 | Argument[1] | input | file://:1:1:1:1 | | output | file://true:1:1:1:1 | true | isVarargsArray | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
| Test.java:63:3:63:3 | c | Related locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:63:3:63:20 | getInputStream(...) | CallContext | Test.java:63:3:63:3 | c | MethodDoc | Test.java:63:3:63:3 | c | ClassDoc | file://java.net:1:1:1:1 | java.net | package | file://URLConnection:1:1:1:1 | URLConnection | type | file://true:1:1:1:1 | true | subtypes | file://getInputStream:1:1:1:1 | getInputStream | name | file://():1:1:1:1 | () | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://:1:1:1:1 | | output | file://false:1:1:1:1 | false | isVarargsArray | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
| Test.java:68:30:68:47 | writer | Related locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:68:30:68:47 | writer | CallContext | Test.java:68:30:68:47 | writer | MethodDoc | Test.java:68:30:68:47 | writer | ClassDoc | file://java.lang:1:1:1:1 | java.lang | package | file://Throwable:1:1:1:1 | Throwable | type | file://true:1:1:1:1 | true | subtypes | file://printStackTrace:1:1:1:1 | printStackTrace | name | file://(PrintWriter):1:1:1:1 | (PrintWriter) | signature | file://:1:1:1:1 | | input | file://Parameter[0]:1:1:1:1 | Parameter[0] | output | file://false:1:1:1:1 | false | isVarargsArray | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
|
||||
| Test.java:76:3:76:6 | list | Related locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:76:3:76:16 | add(...) | CallContext | Test.java:76:3:76:6 | list | MethodDoc | Test.java:76:3:76:6 | list | ClassDoc | file://java.util:1:1:1:1 | java.util | package | file://ArrayList:1:1:1:1 | ArrayList | type | file://true:1:1:1:1 | true | subtypes | file://add:1:1:1:1 | add | name | file://(Object):1:1:1:1 | (Object) | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://:1:1:1:1 | | output | file://false:1:1:1:1 | false | isVarargsArray | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
|
||||
@@ -1,4 +1,3 @@
|
||||
| Test.java:43:14:43:21 | openPath | taint step\nrelated locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:43:4:43:22 | get(...) | CallContext | Test.java:43:14:43:21 | openPath | MethodDoc | Test.java:43:14:43:21 | openPath | ClassDoc | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Paths:1:1:1:1 | Paths | type | file://false:1:1:1:1 | false | subtypes | file://get:1:1:1:1 | get | name | file://(String,String[]):1:1:1:1 | (String,String[]) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | file://:1:1:1:1 | | output | file://false:1:1:1:1 | false | isVarargsArray | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
| Test.java:48:10:50:3 | compareTo(...) | known sanitizer\nrelated locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:48:10:50:3 | compareTo(...) | CallContext | Test.java:48:10:50:3 | compareTo(...) | MethodDoc | Test.java:48:10:50:3 | compareTo(...) | ClassDoc | file://java.io:1:1:1:1 | java.io | package | file://File:1:1:1:1 | File | type | file://true:1:1:1:1 | true | subtypes | file://compareTo:1:1:1:1 | compareTo | name | file://(File):1:1:1:1 | (File) | signature | file://:1:1:1:1 | | input | file://ReturnValue:1:1:1:1 | ReturnValue | output | file://false:1:1:1:1 | false | isVarargsArray | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
|
||||
| Test.java:49:4:49:5 | f2 | known non-sink\nrelated locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:48:10:50:3 | compareTo(...) | CallContext | Test.java:49:4:49:5 | f2 | MethodDoc | Test.java:49:4:49:5 | f2 | ClassDoc | file://java.io:1:1:1:1 | java.io | package | file://File:1:1:1:1 | File | type | file://true:1:1:1:1 | true | subtypes | file://compareTo:1:1:1:1 | compareTo | name | file://(File):1:1:1:1 | (File) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | file://:1:1:1:1 | | output | file://false:1:1:1:1 | false | isVarargsArray | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
| Test.java:67:7:67:18 | this <constr(this)> | exception\nrelated locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:67:7:67:18 | super(...) | CallContext | Test.java:67:7:67:18 | super(...) | MethodDoc | Test.java:67:7:67:18 | super(...) | ClassDoc | file://java.lang:1:1:1:1 | java.lang | package | file://Exception:1:1:1:1 | Exception | type | file://true:1:1:1:1 | true | subtypes | file://Exception:1:1:1:1 | Exception | name | file://():1:1:1:1 | () | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://:1:1:1:1 | | output | file://false:1:1:1:1 | false | isVarargsArray | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
| Test.java:76:3:76:16 | add(...) | known sanitizer\nrelated locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:76:3:76:16 | add(...) | CallContext | Test.java:76:3:76:16 | add(...) | MethodDoc | Test.java:76:3:76:16 | add(...) | ClassDoc | file://java.util:1:1:1:1 | java.util | package | file://ArrayList:1:1:1:1 | ArrayList | type | file://true:1:1:1:1 | true | subtypes | file://add:1:1:1:1 | add | name | file://(Object):1:1:1:1 | (Object) | signature | file://:1:1:1:1 | | input | file://ReturnValue:1:1:1:1 | ReturnValue | output | file://false:1:1:1:1 | false | isVarargsArray | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
|
||||
| Test.java:55:4:55:4 | p | taint step\nrelated locations: $@, $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@. | Test.java:54:3:59:3 | walk(...) | CallContext | Test.java:55:4:55:4 | p | MethodDoc | Test.java:55:4:55:4 | p | ClassDoc | file://java.nio.file:1:1:1:1 | java.nio.file | package | file://Files:1:1:1:1 | Files | type | file://false:1:1:1:1 | false | subtypes | file://walk:1:1:1:1 | walk | name | file://(Path,FileVisitOption[]):1:1:1:1 | (Path,FileVisitOption[]) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | file://:1:1:1:1 | | output | file://false:1:1:1:1 | false | isVarargsArray | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
|
||||
@@ -6,12 +6,12 @@ import java.nio.file.CopyOption;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.util.ArrayList;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.function.Supplier;
|
||||
import java.io.File;
|
||||
import java.nio.file.FileVisitOption;
|
||||
import java.net.URLConnection;
|
||||
import java.util.concurrent.FutureTask;
|
||||
|
||||
class Test {
|
||||
public static void main(String[] args) throws Exception {
|
||||
@@ -68,12 +68,15 @@ class OverrideTest extends Exception {
|
||||
public void printStackTrace(PrintWriter writer) { // writer is a source candidate because it overrides an existing method
|
||||
return;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
class GenericTypesTest<T> {
|
||||
public ArrayList<String> mkList(String elem) {
|
||||
ArrayList<String> list = new ArrayList<String>();
|
||||
list.add(elem); // list is a sink candidate (elem is not, because there's a taint step originating)
|
||||
return list;
|
||||
class TaskUtils {
|
||||
public FutureTask getTask() {
|
||||
FutureTask ft = new FutureTask(() -> {
|
||||
// ^-- no sink candidate for the `this` qualifier of a constructor
|
||||
return 42;
|
||||
});
|
||||
return ft;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -10,6 +10,8 @@
|
||||
| com/github/codeql/test/PublicClass.java:13:18:13:31 | nonPublicStuff | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:13:18:13:31 | nonPublicStuff | MethodDoc | com/github/codeql/test/PublicClass.java:13:18:13:31 | nonPublicStuff | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://nonPublicStuff:1:1:1:1 | nonPublicStuff | name | file://(String):1:1:1:1 | (String) | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://:1:1:1:1 | | output | file://this:1:1:1:1 | this | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
| com/github/codeql/test/PublicClass.java:13:33:13:42 | arg | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:13:33:13:42 | arg | MethodDoc | com/github/codeql/test/PublicClass.java:13:33:13:42 | arg | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://nonPublicStuff:1:1:1:1 | nonPublicStuff | name | file://(String):1:1:1:1 | (String) | signature | file://:1:1:1:1 | | input | file://Parameter[0]:1:1:1:1 | Parameter[0] | output | file://arg:1:1:1:1 | arg | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
|
||||
| com/github/codeql/test/PublicClass.java:13:33:13:42 | arg | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:13:33:13:42 | arg | MethodDoc | com/github/codeql/test/PublicClass.java:13:33:13:42 | arg | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://nonPublicStuff:1:1:1:1 | nonPublicStuff | name | file://(String):1:1:1:1 | (String) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | file://:1:1:1:1 | | output | file://arg:1:1:1:1 | arg | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
| com/github/codeql/test/PublicClass.java:22:10:22:20 | PublicClass | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:22:10:22:20 | PublicClass | MethodDoc | com/github/codeql/test/PublicClass.java:22:10:22:20 | PublicClass | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://PublicClass:1:1:1:1 | PublicClass | name | file://(Object):1:1:1:1 | (Object) | signature | file://:1:1:1:1 | | input | file://ReturnValue:1:1:1:1 | ReturnValue | output | file://:1:1:1:1 | | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
|
||||
| com/github/codeql/test/PublicClass.java:22:22:22:33 | input | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicClass.java:22:22:22:33 | input | MethodDoc | com/github/codeql/test/PublicClass.java:22:22:22:33 | input | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicClass:1:1:1:1 | PublicClass | type | file://true:1:1:1:1 | true | subtypes | file://PublicClass:1:1:1:1 | PublicClass | name | file://(Object):1:1:1:1 | (Object) | signature | file://Argument[0]:1:1:1:1 | Argument[0] | input | file://:1:1:1:1 | | output | file://input:1:1:1:1 | input | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
| com/github/codeql/test/PublicInterface.java:4:17:4:21 | stuff | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicInterface.java:4:17:4:21 | stuff | MethodDoc | com/github/codeql/test/PublicInterface.java:4:17:4:21 | stuff | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicInterface:1:1:1:1 | PublicInterface | type | file://true:1:1:1:1 | true | subtypes | file://stuff:1:1:1:1 | stuff | name | file://(String):1:1:1:1 | (String) | signature | file://:1:1:1:1 | | input | file://Parameter[this]:1:1:1:1 | Parameter[this] | output | file://this:1:1:1:1 | this | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
|
||||
| com/github/codeql/test/PublicInterface.java:4:17:4:21 | stuff | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicInterface.java:4:17:4:21 | stuff | MethodDoc | com/github/codeql/test/PublicInterface.java:4:17:4:21 | stuff | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicInterface:1:1:1:1 | PublicInterface | type | file://true:1:1:1:1 | true | subtypes | file://stuff:1:1:1:1 | stuff | name | file://(String):1:1:1:1 | (String) | signature | file://:1:1:1:1 | | input | file://ReturnValue:1:1:1:1 | ReturnValue | output | file://:1:1:1:1 | | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sourceModel:1:1:1:1 | sourceModel | extensibleType |
|
||||
| com/github/codeql/test/PublicInterface.java:4:17:4:21 | stuff | Related locations: $@, $@.\nmetadata: $@, $@, $@, $@, $@, $@, $@, $@, $@, $@. | com/github/codeql/test/PublicInterface.java:4:17:4:21 | stuff | MethodDoc | com/github/codeql/test/PublicInterface.java:4:17:4:21 | stuff | ClassDoc | file://com.github.codeql.test:1:1:1:1 | com.github.codeql.test | package | file://PublicInterface:1:1:1:1 | PublicInterface | type | file://true:1:1:1:1 | true | subtypes | file://stuff:1:1:1:1 | stuff | name | file://(String):1:1:1:1 | (String) | signature | file://Argument[this]:1:1:1:1 | Argument[this] | input | file://:1:1:1:1 | | output | file://this:1:1:1:1 | this | parameterName | file://:1:1:1:1 | | alreadyAiModeled | file://sinkModel:1:1:1:1 | sinkModel | extensibleType |
|
||||
|
||||
@@ -18,4 +18,8 @@ public class PublicClass {
|
||||
void packagePrivateStuff(String arg) {
|
||||
System.out.println(arg);
|
||||
}
|
||||
|
||||
public PublicClass(Object input) {
|
||||
// the `this` qualifier is not a candidate
|
||||
}
|
||||
}
|
||||
|
||||
@@ -2,23 +2,11 @@ public void sendUserFile(Socket sock, String user) {
|
||||
BufferedReader filenameReader = new BufferedReader(
|
||||
new InputStreamReader(sock.getInputStream(), "UTF-8"));
|
||||
String filename = filenameReader.readLine();
|
||||
// BAD: read from a file using a path controlled by the user
|
||||
BufferedReader fileReader = new BufferedReader(
|
||||
new FileReader("/home/" + user + "/" + filename));
|
||||
// BAD: read from a file without checking its path
|
||||
BufferedReader fileReader = new BufferedReader(new FileReader(filename));
|
||||
String fileLine = fileReader.readLine();
|
||||
while(fileLine != null) {
|
||||
sock.getOutputStream().write(fileLine.getBytes());
|
||||
fileLine = fileReader.readLine();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendUserFileFixed(Socket sock, String user) {
|
||||
// ...
|
||||
|
||||
// GOOD: remove all dots and directory delimiters from the filename before using
|
||||
String filename = filenameReader.readLine().replaceAll("\\.", "").replaceAll("/", "");
|
||||
BufferedReader fileReader = new BufferedReader(
|
||||
new FileReader("/home/" + user + "/" + filename));
|
||||
|
||||
// ...
|
||||
}
|
||||
|
||||
@@ -8,32 +8,44 @@ can result in sensitive information being revealed or deleted, or an attacker be
|
||||
behavior by modifying unexpected files.</p>
|
||||
|
||||
<p>Paths that are naively constructed from data controlled by a user may contain unexpected special characters,
|
||||
such as "..". Such a path may potentially point to any directory on the file system.</p>
|
||||
such as "..". Such a path may potentially point anywhere on the file system.</p>
|
||||
|
||||
</overview>
|
||||
<recommendation>
|
||||
|
||||
<p>Validate user input before using it to construct a file path. Ideally, follow these rules:</p>
|
||||
<p>Validate user input before using it to construct a file path.</p>
|
||||
|
||||
<ul>
|
||||
<li>Do not allow more than a single "." character.</li>
|
||||
<li>Do not allow directory separators such as "/" or "\" (depending on the file system).</li>
|
||||
<li>Do not rely on simply replacing problematic sequences such as "../". For example, after applying this filter to
|
||||
".../...//" the resulting string would still be "../".</li>
|
||||
<li>Ideally use a whitelist of known good patterns.</li>
|
||||
</ul>
|
||||
<p>The choice of validation depends on whether you want to allow the user to specify complex paths with
|
||||
multiple components that may span multiple folders, or only simple filenames without a path component.</p>
|
||||
|
||||
<p>In the former case, a common strategy is to make sure that the constructed file path is contained within
|
||||
a safe root folder, for example by checking that the path starts with the root folder. Additionally,
|
||||
you need to ensure that the path does not contain any ".." components, since otherwise
|
||||
even a path that starts with the root folder could be used to access files outside the root folder.</p>
|
||||
|
||||
<p>In the latter case, if you want to ensure that the user input is interpreted as a simple filename without
|
||||
a path component, you can remove all path separators ("/" or "\") and all ".." sequences from the input
|
||||
before using it to construct a file path. Note that it is <i>not</i> sufficient to only remove "../" sequences:
|
||||
for example, applying this filter to ".../...//" would still result in the string "../".</p>
|
||||
|
||||
<p>Finally, the simplest (but most restrictive) option is to use an allow list of safe patterns and make sure that
|
||||
the user input matches one of these patterns.</p>
|
||||
|
||||
</recommendation>
|
||||
<example>
|
||||
|
||||
<p>In this example, a file name is read from a <code>java.net.Socket</code> and then used to access a file in the
|
||||
user's home directory and send it back over the socket. However, a malicious user could enter a file name which contains special
|
||||
characters. For example, the string "../../etc/passwd" will result in the code reading the file located at
|
||||
"/home/[user]/../../etc/passwd", which is the system's password file. This file would then be sent back to the user,
|
||||
giving them access to all the system's passwords.</p>
|
||||
<p>In this example, a file name is read from a <code>java.net.Socket</code> and then used to access a file
|
||||
and send it back over the socket. However, a malicious user could enter a file name anywhere on the file system,
|
||||
such as "/etc/passwd".</p>
|
||||
|
||||
<sample src="TaintedPath.java" />
|
||||
|
||||
<p>Simply checking that the path is under a trusted location (such as a known public folder) is not enough,
|
||||
however, since the path could contain relative components such as "..". To fix this, check that it does
|
||||
not contain ".." and starts with the public folder.</p>
|
||||
|
||||
<sample src="TaintedPathGood.java" />
|
||||
|
||||
</example>
|
||||
<references>
|
||||
|
||||
|
||||
14
java/ql/src/Security/CWE/CWE-022/TaintedPathGood.java
Normal file
14
java/ql/src/Security/CWE/CWE-022/TaintedPathGood.java
Normal file
@@ -0,0 +1,14 @@
|
||||
public void sendUserFileGood(Socket sock, String user) {
|
||||
BufferedReader filenameReader = new BufferedReader(
|
||||
new InputStreamReader(sock.getInputStream(), "UTF-8"));
|
||||
String filename = filenameReader.readLine();
|
||||
// GOOD: ensure that the file is in a designated folder in the user's home directory
|
||||
if (!filename.contains("..") && filename.startsWith("/home/" + user + "/public/")) {
|
||||
BufferedReader fileReader = new BufferedReader(new FileReader(filename));
|
||||
String fileLine = fileReader.readLine();
|
||||
while(fileLine != null) {
|
||||
sock.getOutputStream().write(fileLine.getBytes());
|
||||
fileLine = fileReader.readLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,9 @@
|
||||
edges
|
||||
| TaintedPath.java:11:38:11:110 | new BufferedReader(...) : BufferedReader | TaintedPath.java:12:24:12:37 | filenameReader : BufferedReader |
|
||||
| TaintedPath.java:11:57:11:109 | new InputStreamReader(...) : InputStreamReader | TaintedPath.java:11:38:11:110 | new BufferedReader(...) : BufferedReader |
|
||||
| TaintedPath.java:11:79:11:99 | getInputStream(...) : InputStream | TaintedPath.java:11:57:11:109 | new InputStreamReader(...) : InputStreamReader |
|
||||
| TaintedPath.java:12:24:12:37 | filenameReader : BufferedReader | TaintedPath.java:12:24:12:48 | readLine(...) : String |
|
||||
| TaintedPath.java:12:24:12:48 | readLine(...) : String | TaintedPath.java:14:68:14:75 | filename |
|
||||
| Test.java:19:18:19:38 | getHostName(...) : String | Test.java:24:20:24:23 | temp |
|
||||
| Test.java:19:18:19:38 | getHostName(...) : String | Test.java:27:21:27:24 | temp |
|
||||
| Test.java:19:18:19:38 | getHostName(...) : String | Test.java:30:44:30:47 | temp |
|
||||
@@ -184,6 +189,12 @@ edges
|
||||
| mad/Test.java:221:26:221:33 | source(...) : String | mad/Test.java:221:19:221:33 | (...)... |
|
||||
| mad/Test.java:226:29:226:36 | source(...) : String | mad/Test.java:226:20:226:36 | (...)... |
|
||||
nodes
|
||||
| TaintedPath.java:11:38:11:110 | new BufferedReader(...) : BufferedReader | semmle.label | new BufferedReader(...) : BufferedReader |
|
||||
| TaintedPath.java:11:57:11:109 | new InputStreamReader(...) : InputStreamReader | semmle.label | new InputStreamReader(...) : InputStreamReader |
|
||||
| TaintedPath.java:11:79:11:99 | getInputStream(...) : InputStream | semmle.label | getInputStream(...) : InputStream |
|
||||
| TaintedPath.java:12:24:12:37 | filenameReader : BufferedReader | semmle.label | filenameReader : BufferedReader |
|
||||
| TaintedPath.java:12:24:12:48 | readLine(...) : String | semmle.label | readLine(...) : String |
|
||||
| TaintedPath.java:14:68:14:75 | filename | semmle.label | filename |
|
||||
| Test.java:19:18:19:38 | getHostName(...) : String | semmle.label | getHostName(...) : String |
|
||||
| Test.java:24:20:24:23 | temp | semmle.label | temp |
|
||||
| Test.java:27:21:27:24 | temp | semmle.label | temp |
|
||||
@@ -375,6 +386,7 @@ nodes
|
||||
| mad/Test.java:226:29:226:36 | source(...) : String | semmle.label | source(...) : String |
|
||||
subpaths
|
||||
#select
|
||||
| TaintedPath.java:14:53:14:76 | new FileReader(...) | TaintedPath.java:11:79:11:99 | getInputStream(...) : InputStream | TaintedPath.java:14:68:14:75 | filename | This path depends on a $@. | TaintedPath.java:11:79:11:99 | getInputStream(...) | user-provided value |
|
||||
| Test.java:24:11:24:24 | new File(...) | Test.java:19:18:19:38 | getHostName(...) : String | Test.java:24:20:24:23 | temp | This path depends on a $@. | Test.java:19:18:19:38 | getHostName(...) | user-provided value |
|
||||
| Test.java:27:11:27:25 | get(...) | Test.java:19:18:19:38 | getHostName(...) : String | Test.java:27:21:27:24 | temp | This path depends on a $@. | Test.java:19:18:19:38 | getHostName(...) | user-provided value |
|
||||
| Test.java:30:11:30:48 | getPath(...) | Test.java:19:18:19:38 | getHostName(...) : String | Test.java:30:44:30:47 | temp | This path depends on a $@. | Test.java:19:18:19:38 | getHostName(...) | user-provided value |
|
||||
|
||||
@@ -0,0 +1,35 @@
|
||||
import java.io.BufferedReader;
|
||||
import java.io.FileReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.Socket;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.Paths;
|
||||
import java.io.IOException;
|
||||
|
||||
public class TaintedPath {
|
||||
public void sendUserFile(Socket sock, String user) throws IOException {
|
||||
BufferedReader filenameReader = new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
|
||||
String filename = filenameReader.readLine();
|
||||
// BAD: read from a file without checking its path
|
||||
BufferedReader fileReader = new BufferedReader(new FileReader(filename));
|
||||
String fileLine = fileReader.readLine();
|
||||
while(fileLine != null) {
|
||||
sock.getOutputStream().write(fileLine.getBytes());
|
||||
fileLine = fileReader.readLine();
|
||||
}
|
||||
}
|
||||
|
||||
public void sendUserFileGood(Socket sock, String user) throws IOException {
|
||||
BufferedReader filenameReader = new BufferedReader(new InputStreamReader(sock.getInputStream(), "UTF-8"));
|
||||
String filename = filenameReader.readLine();
|
||||
// GOOD: ensure that the file is in a designated folder in the user's home directory
|
||||
if (!filename.contains("..") && filename.startsWith("/home/" + user + "/public/")) {
|
||||
BufferedReader fileReader = new BufferedReader(new FileReader(filename));
|
||||
String fileLine = fileReader.readLine();
|
||||
while(fileLine != null) {
|
||||
sock.getOutputStream().write(fileLine.getBytes());
|
||||
fileLine = fileReader.readLine();
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -101,4 +101,12 @@ class Test {
|
||||
new File(new URI(null, null, null, 0, t, null, null));
|
||||
}
|
||||
|
||||
void doGet6(String root, InetAddress address)
|
||||
throws IOException{
|
||||
String temp = address.getHostName();
|
||||
// GOOD: Use `contains` and `startsWith` to check if the path is safe
|
||||
if (!temp.contains("..") && temp.startsWith(root + "/")) {
|
||||
File file = new File(temp);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user