Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

New rule S6640: Allowing unsafe code is security-sensitive #7474

Merged
merged 5 commits into from
Jun 21, 2023
Merged
Show file tree
Hide file tree
Changes from 3 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
{
"issues": [
{
"id": "S6640",
"message": "Make sure that using "unsafe" is safe here.",
"location": {
"uri": "sources\akka.net\src\core\Akka.Remote\Serialization\LruBoundedCache.cs",
"region": {
"startLine": 61,
"startColumn": 13,
"endLine": 61,
"endColumn": 19
}
}
}
martin-strecker-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
]
}
59 changes: 59 additions & 0 deletions analyzers/rspec/cs/S6640.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,59 @@
<p>Using <code>unsafe</code> code blocks can lead to unintended security or stability risks.</p>
<p><code>unsafe</code> code blocks allow developers to use features such as pointers, fixed buffers, function calls through pointers and manual memory
management. Such features may be necessary for interoperability with native libraries, as these often require pointers. It may also increase
performance in some critical areas, as certain bounds checks are not executed in an unsafe context.</p>
<p><code>unsafe</code> code blocks aren’t necessarily dangerous, however, the contents of such blocks are not verified by the Common Language Runtime.
Therefore, it is up to the programmer to ensure that no bugs are introduced through manual memory management or casting. If not done correctly, then
those bugs can lead to memory corruption vulnerabilities such as stack overflows. <code>unsafe</code> code blocks should be used with caution because
of these security and stability risks.</p>
<h2>Ask Yourself Whether</h2>
<ul>
<li> There are any pointers or fixed buffers declared within the <code>unsafe</code> code block. </li>
</ul>
<p>There is a risk if you answered yes to the question.</p>
<h2>Recommended Secure Coding Practices</h2>
<p>Unless absolutely necessary, do not use <code>unsafe</code> code blocks. If <code>unsafe</code> is used to increase performance, then the <a
href="https://learn.microsoft.com/en-us/dotnet/standard/memory-and-spans/">Span and Memory APIs</a> may serve a similar purpose in a safe context.</p>
<p>If it is not possible to remove the code block, then it should be kept as short as possible. Doing so reduces risk, as there is less code that can
potentially introduce new bugs. Within the <code>unsafe</code> code block, make sure that:</p>
<ul>
<li> All type casts are correct. </li>
<li> Memory is correctly allocated and then released. </li>
<li> Array accesses can never go out of bounds. </li>
</ul>
<h2>Sensitive Code Example</h2>
<pre data-diff-id="1" data-diff-type="noncompliant">
public unsafe int subarraySum(int[] array, int start, int end) // Sensitive
{
var sum = 0;

// Skip array bound checks for extra performance
fixed (int* firstNumber = array)
{
for (int i = start; i &lt; end; i++)
sum += *(firstNumber + i);
}

return sum;
}
</pre>
<h2>Compliant Solution</h2>
<pre data-diff-id="1" data-diff-type="compliant">
public int subarraySum(int[] array, int start, int end)
{
var sum = 0;

Span&lt;int&gt; span = array.AsSpan();
for (int i = start; i &lt; end; i++)
sum += span[i];

return sum;
}
</pre>
<h2>See</h2>
<ul>
<li> <a href="https://cwe.mitre.org/data/definitions/787.html">MITRE, CWE-787</a> - Out-of-bounds Write </li>
<li> <a href="https://learn.microsoft.com/en-us/dotnet/csharp/language-reference/unsafe-code">Microsoft Learn</a> - Unsafe code, pointer types, and
function pointers </li>
</ul>

15 changes: 15 additions & 0 deletions analyzers/rspec/cs/S6640.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
{
"title": "Using unsafe code blocks is security-sensitive",
"type": "SECURITY_HOTSPOT",
"status": "ready",
"remediation": {
"func": "Constant\/Issue",
"constantCost": "60min"
},
"tags": [],
"defaultSeverity": "Major",
"ruleSpecification": "RSPEC-6640",
"sqKey": "S6640",
"scope": "All",
"quickfix": "unknown"

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should be infeasible.

}
3 changes: 2 additions & 1 deletion analyzers/rspec/cs/Sonar_way_profile.json
Original file line number Diff line number Diff line change
Expand Up @@ -284,6 +284,7 @@
"S6612",
"S6613",
"S6617",
"S6618"
"S6618",
"S6640"
]
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact 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.
*/

namespace SonarAnalyzer.Extensions
{
public static class SyntaxTokenListExtensions
{
public static SyntaxToken? Find(this SyntaxTokenList tokenList, SyntaxKind kind) =>
tokenList.IndexOf(kind) is var index && index != -1

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternative (I'm not sure whether it is better, so the change is optional):

Suggested change
tokenList.IndexOf(kind) is var index && index != -1
tokenList.IndexOf(kind) is >= 0 and var index

? tokenList[index] : null;
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,58 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact 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.
*/

namespace SonarAnalyzer.Rules.CSharp;

[DiagnosticAnalyzer(LanguageNames.CSharp)]
public sealed class UnsafeCodeBlocks : HotspotDiagnosticAnalyzer
{
private const string DiagnosticId = "S6640";
private const string MessageFormat = """Make sure that using "unsafe" is safe here.""";

private static readonly DiagnosticDescriptor Rule = DescriptorFactory.Create(DiagnosticId, MessageFormat);

public UnsafeCodeBlocks() : this(AnalyzerConfiguration.Hotspot) { }

public UnsafeCodeBlocks(IAnalyzerConfiguration configuration) : base(configuration) { }

public override ImmutableArray<DiagnosticDescriptor> SupportedDiagnostics => ImmutableArray.Create(Rule);

protected override void Initialize(SonarAnalysisContext context)
{
context.RegisterNodeAction(c => Report(c, ((UnsafeStatementSyntax)c.Node).UnsafeKeyword), SyntaxKind.UnsafeStatement);
context.RegisterNodeAction(c =>
{
if (c.Node is BaseMethodDeclarationSyntax { Modifiers: var modifiers }
&& modifiers.Find(SyntaxKind.UnsafeKeyword) is { } unsafeModifier)
{
Report(c, unsafeModifier);
}
},
SyntaxKind.MethodDeclaration, SyntaxKind.ConstructorDeclaration, SyntaxKind.DestructorDeclaration, SyntaxKind.OperatorDeclaration);
}

private void Report(SonarSyntaxNodeReportingContext context, SyntaxToken token)
{
if (IsEnabled(context.Options))
{
context.ReportIssue(Diagnostic.Create(Rule, token.GetLocation()));
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6564,7 +6564,7 @@ internal static class RuleTypeMappingCS
// ["S6637"],
// ["S6638"],
// ["S6639"],
// ["S6640"],
["S6640"] = "SECURITY_HOTSPOT",
// ["S6641"],
// ["S6642"],
// ["S6643"],
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
/*
* SonarAnalyzer for .NET
* Copyright (C) 2015-2023 SonarSource SA
* mailto: contact 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 SonarAnalyzer.Rules.CSharp;

namespace SonarAnalyzer.UnitTest.Rules;

[TestClass]
public class UnsafeCodeBlocksTest
{
private readonly VerifierBuilder builder = new VerifierBuilder().WithBasePath("Hotspots").AddAnalyzer(() => new UnsafeCodeBlocks(AnalyzerConfiguration.AlwaysEnabled));

[TestMethod]
public void UnsafeCodeBlocks() =>
builder.AddPaths("UnsafeCodeBlocks.cs").Verify();
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,79 @@
using System;

public class Sample
{
unsafe void MethodScope(byte* pointer) { } // Noncompliant {{Make sure that using "unsafe" is safe here.}}
// ^^^^^^

void BlockScope()
{
unsafe // Noncompliant
// ^^^^^^
{
}
}

void SafeMethod() { } // Compliant

void LocalFunction()
{
unsafe void Local(byte* pointer) { } // FN
void SafeLocal(byte noPointer) { } // Compliant
}

unsafe class UnsafeClass { } // FN

unsafe struct UnsafeStruct // FN
martin-strecker-sonarsource marked this conversation as resolved.
Show resolved Hide resolved
{
unsafe fixed byte unsafeFixedBuffer[16]; // FN
}

unsafe interface IUnsafeInterface { } // FN

unsafe Sample(byte* pointer) { } // Noncompliant

static unsafe Sample() { } // Noncompliant

unsafe ~Sample() { } // Noncompliant

unsafe byte* unsafeField; // FN

unsafe byte* UnsafeProperty { get; } // FN

unsafe event EventHandler UnsafeEvent; // FN

unsafe delegate void UnsafeDelegate(byte* pointer); // FN

unsafe int this[int i] => 5; // FN

public unsafe static Sample operator +(Sample other) => other; // Noncompliant

// from RSPEC
public unsafe int SubarraySum(int[] array, int start, int end) // Noncompliant
{
var sum = 0;

// Skip array bound checks for extra performance
fixed (int* firstNumber = array)
{
for (int i = start; i < end; i++)
sum += *(firstNumber + i);
}

return sum;
}

// from C# docs
unsafe static void SquarePtrParam(int* p) // Noncompliant
{
*p *= *p;
}

unsafe static void Main() // Noncompliant
{
int i = 5;
// Unsafe method: uses address-of operator (&).
SquarePtrParam(&i);
Console.WriteLine(i);
}
}