個人的なメモ

Tomohiro Suzuki @hiro128_777 のブログです。Xamarin に関する事を中心に書いています。 Microsoft MVP for Development Technologies 2017- 本ブログと所属組織の公式見解は関係ございません。

.NET 6 の新機能 - ランタイムのセキュリティ脆弱性の緩和策

ランタイムのセキュリティ脆弱性の緩和策(.NET 6 RC 1)

2021年の初頭に、.NET ランタイムのセキュリティ対策のロードマップが発表されています。

これらのセキュリティ対策は単なるセキュリティパッチではなく、ランタイムの構造に手を入れる根本的な対策を含んでおり、それらは .NET 6 以降にのみ実装されることとなっています。.NET Framework のセキュリティパッチでは実装されません。
github.com
なお、.NET 6 RC 1 では、以下の2つの重要なセキュリティ対策がプレビューとなりました。なお、.NET 7ではこれらはデフォルトで有効になる予定です。
 

Hardware-enforced Stack Protection(ハードウェア強制型スタック保護)

ハードウェア強制型スタック保護は、Intel の第11世代 Tiger Lake プロセッサに および、AMD Zen 3 プロセッサに Control-flowEnforcement Technology (CET)として搭載されているセキュリティ機能です。

x64 CPU のハードウェアに依存した機能ですので当然ながら、x64 プラットフォームでのみ有効にできる機能となります。

CET はシャドウスタック(コールスタックの「影(シャドウ)」となる第2の別のスタック)を使ってすべてのリターンアドレスを記録しておく技術です。x64 のアセンブリ言語で、CALL 命令ごとにリターンアドレスがコールスタックとシャドースタックの両方にプッシュされ、RET 命令ではコールスタックとシャドースタックの両方からリターンアドレスをロードします。

2つのアドレスが一致しない場合、プロセッサは制御保護例外(#CP—Control-Protection Exception)を発行します。これがカーネルにトラップされ、セキュリティを保証するためにプロセスを終了させます。

シャドウスタックはリターンアドレスのみを格納するため、追加のメモリオーバーヘッドを最小限に抑えることができます。
 
公式情報:
CET
Understanding Hardware-enforced Stack Protection
.NET 6 compatibility with Intel CET shadow stacks (early preview on Windows) 
 
 

W^X(write xor execute:書き込みと実行の排他)

W^X は、プロセスやカーネルアドレス空間内のすべてのページが、書き込み可能か実行可能かのいずれかであり、両方はできないというメモリ保護ポリシーです。この保護機能がないと、プログラムはデータ用のメモリ領域にCPU命令を書き込み(データ「W」として)、その命令を実行(実行可能「X」、または読み取り-実行「RX」として)することができてしまいます。

W^X は、最も単純な攻撃経路を遮断します。この機能がない場合、その他のより高度な対策はバイパスされる可能性があるため、その他の対策の意味がない結果となってしまいます。W^X を導入したことで前提条件がそろったため、今後 CET のような他の補完的な対策を追加される予定となっています。

Apple は「macOS」の将来のバージョンで W^X を必須としました。W^X は、.NET 6 を搭載したすべての OS で利用可能ですが、デフォルトで有効なのは、Apple Silicon 上で動く macOS のみです。.NET 7 ではすべての OS で有効になります

公式情報:W^X
 
 

C# 10 の新機能 - const および文字列補間

C# 10 の新機能の情報の目次は以下をご覧ください。
hiro128.hatenablog.jp 
 

 

const および文字列補間

プレースホルダーに使用する値が定数である場合は、const で定義された値にも文字列補間に使用できるようになりました。単純にこれは便利です。

さらに、下記コード1のように補間された文字列のすべての部分が文字列リテラルであることを認識できる場合、コンパイラは、コード2のように単一の文字列リテラルとして記述されていた場合と同じように最適化して IL に出力します。つまり、以下の2つのコードはどちらの場合も、コード2のように記述されたものとして IL に出力されます。

コード1

const string Greeting = "Hello";
const string GreetingName = $"{Greeting} .NET 6"; // const で定義する値にも文字列補間が使用できる
string result = $"{GreetingName}!"; 

 
コード 2

string result = "Hello, .NET 6!";

 

導入の意図

この機能はコードの記述の自由度を高めることとコンパイラの最適化による速度向上のための機能です。
速度向上については開発者は特に意識することなく、コンパイラで最適化が働きパフォーマンスアップします。
 
公式情報:
const and interpolated strings
String Interpolation in C# 10 and .NET 6
 
 

C# 10 の新機能 - 暗黙的な using ディレクティブ

C# 10 の新機能の情報の目次は以下をご覧ください。
hiro128.hatenablog.jp 
 

暗黙的な using ディレクティブ

コンパイラによって、プロジェクトの種類ごとにあらかじめ決められたよく使われる名前空間のセットが自動的に追加されます。
例えば、コンソール アプリケーションの場合、以下の名前空間が自動的に追加されます。

using System;
using System.IO;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Threading;
using System.Threading.Tasks;

 
.NET 5 以前のプロジェクトを .NET 6 以降に再ターゲットするときに、暗黙的な global using ディレクティブは追加されないので注意してください(場合によっては、コンパイルできなくなります)
また、.NET 6 以降の ImplicitUsings が有効になっているプロジェクトで、この機能を無効にすると、例えば、暗黙的に宣言されていた、System 名前空間が宣言されなくなってしまい、アプリがコンパイルされなくなりますので、プロジェクト単位での有効・無効の変更時はご注意ください。
 
手動で、ImplicitUsings プロパティ を true または enable に設定することで、プロジェクト単位でこの機能を有効にすることができます。
.NET 6 以降をターゲットとする C# プロジェクトのテンプレートでは、ImplicitUsings は既定で enable に設定されています。

<PropertyGroup>
  <ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>

 

導入の意図

明示的に指定することなくその種類のアプリで一般的によく使われる名前空間が自動的に追加されるので多くの using 行が不要になり、ソースコードからロジック以外の要素を排除できます。
こちらも「ソースコードを簡潔にするための機能」です。
 
公式情報:
Implicit usings
.NET SDK プロジェクトの MSBuild リファレンス - ImplicitUsings プロパティ

C# 10 の新機能 - ファイルスコープの名前空間宣言

C# 10 の新機能の情報の目次は以下をご覧ください。
hiro128.hatenablog.jp 
 

ファイルスコープの名前空間宣言

1つのファイルにつき1つだけ使用でき、従来の中括弧({})を含めた3行の構文ではなく、中括弧を省いた1行の構文で名前空間宣言が記述できます。
なお、従来の3行の構文は名前空間入れ子にすることができますが、新しい1行の構文は入れ子にすることができません。
ファイル内で定義されたすべての型の前に宣言する必要があります。

名前空間宣言は、最上位レベルのステートメントと互換性がありませんのでご注意ください。最上位レベルのステートメントは、最上位レベルの名前空間内に存在します。
 
「最上位レベルのステートメント」は C# 9 で追加された機能です。詳細は以下をご参考ください。
最上位レベルのステートメント - Main メソッドを使用しないプログラム | Microsoft Docs
 

導入の意図

通常使われる名前空間を1つだけ含むファイルの場合に、中括弧やインデントをなくすことができます。
一言で言えば「ソースコードを簡潔にするための機能」です。
 

新しい中括弧を省いた1行の構文
namespace MyNameSpace; // セミコロンが必要

class A // インデントを最小にできる
{
}

 

従来の中括弧を含めた3行の構文
namespace MyNameSpace
{
    class A // 無駄にインデントが多くなって読みにくい
    {
        
    }
}

公式情報:
File-scoped namespace declaration
File Scoped Namespaces
 
 

C# 10 の新機能 - global using

C# 10 の新機能の情報の目次は以下をご覧ください。
hiro128.hatenablog.jp 
 

global using

任意のソース ファイルに global using ディレクティブを追加するか、プロジェクトファイル(*.csproj)に Using Itemを追加することですべてのソースファイルで使用したい名前空間を、各ソースファイルで宣言されているかのように指定することができます。
なお、static 修飾子やエイリアスと一緒に使うことができます。
 

導入の意図

共通の using 宣言を1箇所に集約できるので多くの using 行が不要になり、ソースコードからロジック以外の要素を排除できます。
一言で言えば「ソースコードを簡潔にするための機能」です。
 

任意のソースファイルに global using ディレクティブを追加
global using System.Linq; 
global using static System.Console; // static 併用
global using E = System.Environment; // エイリアス併用

 

プロジェクトファイル(*.csproj)に Using Itemを追加
<ItemGroup>
  <Using Include="System.Linq" />
  <Using Include="System.Console" Static="True" /> 
  <Using Include="System.Environment" Alias="E" />
</ItemGroup>

 
公式情報:
Global usings
global 修飾子
 
 

C# 10 の新機能 - 拡張プロパティ パターン

C# 10 の新機能の情報の目次は以下をご覧ください。
hiro128.hatenablog.jp 
 

拡張プロパティ パターン

ネストされたプロパティまたはプロパティ パターン内のフィールドを参照できるようになりました。
以下のような C# 9.0 のコードは、C# 10.0 では「.(ドット)」を使って簡潔に直感的に理解できるように記述できます。

C# 9.0

public record Point(int X, int Y);
public record Segment(Point Start, Point End);

static bool IsAnyEndOnXAxis(Segment segment) => segment is { Start: { Y: 0 } } or { End: { Y: 0 } };

 
C# 10.0

public record Point(int X, int Y);
public record Segment(Point Start, Point End);

static bool IsAnyEndOnXAxis(Segment segment) => segment is { Start.Y: 0 } or { End.Y: 0 };

導入の意図

ネストして冗長な中括弧が増えて読みにくくなるのを防ぎ、簡潔で直感的に理解できるようなコードが記述できます。
こちらも「ソースコードを簡潔にするための機能」です。

公式情報:
Extended property patterns
プロパティ パターン
 

C# 10 の新機能 - ラムダ式の自然型

C# 10 の新機能の情報の目次は以下をご覧ください。
hiro128.hatenablog.jp 
 

 

ラムダ式の自然型

ラムダ式に対して Func<...>Action<...> などのデリゲート型を強制的に宣言するのではなく、コンパイラがパラメーターと式の型からデリゲート型を推論し、Func または Action のデリゲートの割り当て、または、デリゲート型の合成が行われる機能です。

var f = (string s) => int.Parse(s);  // Func<string, int>

上記の場合、コンパイラにより、parseFunc<string, int> と推論することができます。上記のように当てはまる Func または Action が存在する場合、コンパイラには Func または Action のデリゲートが使われます。 それ以外の場合デリゲート型が合成されます。 たとえば、ラムダ式のパラメーターが ref の場合や、パラメータの数が多い場合などはコンパイラによって型が合成されます。

なお、ラムダ式が自然型の場合、System.Object、System.Delegate などの曖昧な型に割り当てることができます。
(後述しますが、この、ラムダの自然型は、System.Object、System.Delegate に割り当て可能というところがポイントになります。)

object f = (string s) => int.Parse(s);   // Func<string, int>
Delegate f = (string s) => int.Parse(s); // Func<string, int>

 

導入の意図

明示的なデリゲート型なしでラムダとメソッドグループを使用できるようになるため、本来明示的なデリゲート型を定義して割り当てる必要があるメソッドのパラメータに対して明示的なキャストなしでパラメーターと式の型を合わせた任意のラムダを割り当て可能になります。
こちらも「ソースコードを簡潔にするための機能」です。

わかりやすい例として、この機能は ASP.NET の Minimal API (最小限のコードで表現する REST API)のシナリオで便利です。
.NET 6 では以下のオーバーロードが MapGet 拡張メソッドに追加されています。(Delegate handler パラメータに注目してください。)

public static Microsoft.AspNetCore.Builder.RouteHandlerBuilder MapGet (this Microsoft.AspNetCore.Routing.IEndpointRouteBuilder endpoints, string pattern, Delegate handler);

MapGet(IEndpointRouteBuilder, String, Delegate)

上記のように、MapGet メソッドで Microsoft.AspNetCore.Http.RequestDelegate ではなく、Delegate を利用できるようになりました。
さらに、ラムダ式が自然型の場合、System.Delegate に割り当て可能になったため、下記のコードのように、明示的なデリゲート型へのキャストなしでラムダを記述できるようになり、余計な括弧や型が不必要になって直感的に読みやすいコードになりました。

using System;
using System.Linq;
using Microsoft.AspNetCore.Builder;

var app = WebApplication.Create(args);
app.MapGet("/", () => "Hello Minimal API !"); // ラムダの自然型によりラムダを直接割り当てられる
app.MapGet("/", (Func<string>)(() => "Hello Minimal API !")); // ラムダの自然型を使用しない場合、明示的な Func<string> へのキャストが必要
await app.RunAsync();

 
公式情報:
Natural types for lambdas
ラムダ式の自然型
 

Minimal API

Minimal API については以下を参照ください。
hiro128.hatenablog.jp