Runtime: Like Tiering, Levels of Verification And Safety

Created on 19 Jul 2020  Â·  15Comments  Â·  Source: dotnet/runtime

Background and impetus

Consider -Xverify:none from java...

Consider this code which is valid on Mono 5 but not the clr

.assembly Hello {}
.assembly extern mscorlib {}



.class interface nested public auto ansi abstract Interface
{
}

.class interface nested public auto ansi abstract Test
{
}

.method public hidebysig static 
    !!T M<(Interface) T> () cil managed 
{
    .maxstack 1
    .locals init (
        [0] !!T
    )

    IL_0000: ldloca.s 0
    IL_0002: initobj !!T
    IL_0008: ldloc.0
    IL_0009: ret
}


.method static void Main()
{
    .entrypoint
    .maxstack 1
    call !!0 M<class Interface>()
    pop
    call !!0 M<class Test>()
    pop
    ldstr "Hello, world!"
    call void [mscorlib]System.Console::WriteLine(string)
    ret
}
would be almost equal to this C#

using System;
public class C {
    public interface Interface{}
    public interface Test{}
    public static T M<T>() where T : Interface { return default; }
                                              //CS0311
    public static void Main(){ M<Interface>();M<Test>(); System.Console.WriteLine("Hello, world!");}
}

With just some small modification similar code runs on .net core:

.assembly Hello {}
.assembly extern mscorlib {}



.class interface nested public auto ansi abstract Interface
{
}

.class interface nested public auto ansi abstract Test
{
}

.method public hidebysig static 
    !!T M<class T> () cil managed 
{
    .maxstack 1
    .locals init (
        [0] !!T
    )

    IL_0000: ldloca.s 0
    IL_0002: initobj !!T
    IL_0008: ldloc.0
    IL_0009: ret
}


.method static void Main()
{
    .entrypoint
    .maxstack 1
    call !!0 M<class Interface>()
    pop
    call !!0 M<class Test>()
    pop
    ldstr "Hello, world!"
    call void [mscorlib]System.Console::WriteLine(string)
    ret
}

I propose we offer levels of verification such that we can even have the options to completely turn off anything such as but not limited to:
gc (-nogc)
exception handling (-noexcept)
memory access (-norca)
array access (-nooob)
string modification (-nostringintern)
generic constraint verification (--noconstraints)
default thread stack size upon creation (-tss)
default stack size of threads in the thread pool and number (--tpss --tpcount)
initial tier for jit, max tier (disabled by -1 or 0) (-itier, -mtier)

Finally maybe build profiles, e.g. FullTrust, Shared, Unsafe, Development etc which encapsulate the best practices for each profile an then finally a big switch like (-noverify) to turn off everything.

I believe these features will allow developers who are writing very high performance code (or developers who do not have a lot of power) to not have to think as much about unnecessary things [to them atp] when doing something like encryption or compression or writing a game or media server.

We already offer Unsafe and friends and I feel like this compliments some of the options existing such as checked and \unsafe.

Immediate benefits

Cheap way to make code faster code after six sigma testing
Find bugs faster with hand rails turned off
Layered design approach to turning things on and off rather than just unsafe, we kind of do this now by supporting unchecked

Alternative Designs

Perhaps a lot of environment variables to control the same things however they could change during the execution of the process, hidden arguments to the program itself are other options... (something like -O in gcc for the build is also possible)

See also

Design Discussion area-ILVerification

Most helpful comment

to not have to think as much when doing something like encryption or compression

I want anyone doing any of this sort of thing to think very very hard indeed.

All 15 comments

I couldn't figure out the best area label to add to this issue. If you have write-permissions please help me learn by adding exactly one area label.

cc @danmosemsft @jkotas @GrabYourPitchforks

to not have to think as much when doing something like encryption or compression

I want anyone doing any of this sort of thing to think very very hard indeed.

gc (-nogc)

Can you use Zero Garbage Collector?

exception handling (-noexcept)

So if any exception is thrown, then the CLR immediately terminates the process without looking for handlers?

memory access (-norca)

Is this for BIOS code that cannot use any RAM until it has configured the chipset?
Such code cannot be JIT-compiled, either, and all function calls might have to be inlined, unless the stack can be locked in the processor's cache.

array access (-nooob)

Do you want to disable arrays entirely, or suppress some of the runtime checks generated for array accesses? For the latter, ECMA-335 defines the no. prefix, although I don't know whether CoreCLR supports that all; opcode.def maps 0xFE 0x19 to CEE_UNUSED69, and verbildump.cpp does not realize that the third byte is part of the instruction.

Common Language Infrastructure (CLI) Partition III CIL Instruction Set (With Added Microsoft Specific Implementation Notes) also says that "The Microsoft CLI does not currently support the no. prefix." There used to be a link to that document from ECMA C# and Common Language Infrastructure Standards, but the page has apparently been removed (archive copy).

string modification (-nostringintern)

Can you use Char[] or String.Create instead?

default thread stack size upon creation (-tss)
default stack size of threads in the thread pool and number (--tpss --tpcount)
initial tier for jit, max tier (disabled by -1 or 0) (-itier, -mtier)

The number of threads in the thread pool can already be configured in runtimeconfig.json; see Threading config settings. The rest of these might make sense as well.

Consider -Xverify:none from java...

Java is trying to remove this, and deprecated it in Java 13, I don't think .NET should add something similar for the same reasons Java is trying to prevent it from being used.

Find bugs faster with hand rails turned off

Can you elaborate on this?

Hand rails should make finding bugs easier, not harder. All too often when you are intentionally not using guard rails, folks end up doing things that are very hard to track down, like a use-after-free or indexing out of bounds. Often enough these situations silently "work" or don't throw, or you corrupt some segment of memory that won't be noticed until much, much, later.

Consider -Xverify:none from java...

Java is trying to remove this, and deprecated it in Java 13, I don't think .NET should add something similar for the same reasons Java is trying to prevent it from being used.

Find bugs faster with hand rails turned off

Can you elaborate on this?

Hand rails should make finding bugs easier, not harder. All too often when you are intentionally not using guard rails, folks end up doing things that are very hard to track down, like a use-after-free or indexing out of bounds. Often enough these situations silently "work" or don't throw, or you corrupt some segment of memory that won't be noticed until much, much, later.

Yes, you can more quickly find weather you have problems of scale or resources such that if you know your code is already not buggy, you can turn off some switches and the program should get faster and run the same, if it crashes (you might not get a stack trace without the right switches) then you know your probably doing something wrong.

gc (-nogc)

Can you use Zero Garbage Collector?

Should I have to?

exception handling (-noexcept)

So if any exception is thrown, then the CLR immediately terminates the process without looking for handlers?

Basically many also an option to hookup a single handler like OnUnhandledException in such cases then terminate.

memory access (-norca)

Is this for BIOS code that cannot use any RAM until it has configured the chipset?
Such code cannot be JIT-compiled, either, and all function calls might have to be inlined, unless the stack can be locked in the processor's cache.

...

array access (-nooob)

Do you want to disable arrays entirely, or suppress some of the runtime checks generated for array accesses? For the latter, ECMA-335 defines the no. prefix, although I don't know whether CoreCLR supports that all; opcode.def maps 0xFE 0x19 to CEE_UNUSED69, and verbildump.cpp does not realize that the third byte is part of the instruction.

Potentially both, if I am using stackalloc an unsafe and I know my stack size then I shouldn't need any arrays but definitely in less extreme cases all the bounds checking.

Common Language Infrastructure (CLI) Partition III CIL Instruction Set (With Added Microsoft Specific Implementation Notes) also says that "The Microsoft CLI does not currently support the no. prefix." There used to be a link to that document from ECMA C# and Common Language Infrastructure Standards, but the page has apparently been removed (archive copy).

string modification (-nostringintern)

Can you use Char[] or String.Create instead?

default thread stack size upon creation (-tss)
default stack size of threads in the thread pool and number (--tpss --tpcount)
initial tier for jit, max tier (disabled by -1 or 0) (-itier, -mtier)

Then I would need to use arrays.

The number of threads in the thread pool can already be configured in runtimeconfig.json; see Threading config settings. The rest of these might make sense as well.

What about their stack size, thread quantums etc? More scheduler knobs and also an easy to ready manual on how to replace the scheduler...

Consider this code which is valid on Mono 5 but not the clr

ilverify is the tool for tracing down bad IL. It works very nicely for your example:

C:\repro>dotnet ilverify x.exe -r "c:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.3\*.dll"
[IL]: Error [UnsatisfiedMethodInst]: [C:\repro\x.exe : .<Module>::Main()][offset 0x00000006][found [Hello]<Module>.M<Test>()] Method instantiation has unsatisfied method type parameter constraints.
1 Error(s) Verifying C:\repro\x.exe
C:\repro>dotnet ilverify y.exe -r "c:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.3\*.dll"
All Classes and Methods in C:\repro\y.exe Verified.

bounds checking

We won't ever introduce runtime options that allow you to turn off validations like array bounds checks or generic constraint for the whole program with unknown security ramifications. It would violate our principles around security. If you need to disable validations like this for your experiments, you have to build your custom version of the runtime.

We won't ever introduce runtime options that allow you to turn off validations like array bounds checks or generic constraint for the whole program with unknown security ramifications.

That said, IL can represent things like "please deref this element from the array with no bounds check." I'd personally like it if there were a nicer syntax for this that didn't involve three levels of nested Unsafe statements. You'd still want the call site to make it perfectly clear that the checks are being disabled. While not having to scroll the IDE left and right to fit the entire line on the screen. :)

Consider this code which is valid on Mono 5 but not the clr

ilverify is the tool for tracing down bad IL. It works very nicely for your example:

C:\repro>dotnet ilverify x.exe -r "c:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.3\*.dll"
[IL]: Error [UnsatisfiedMethodInst]: [C:\repro\x.exe : .<Module>::Main()][offset 0x00000006][found [Hello]<Module>.M<Test>()] Method instantiation has unsatisfied method type parameter constraints.
1 Error(s) Verifying C:\repro\x.exe
C:\repro>dotnet ilverify y.exe -r "c:\Program Files\dotnet\shared\Microsoft.NETCore.App\3.1.3\*.dll"
All Classes and Methods in C:\repro\y.exe Verified.

bounds checking

We won't ever introduce runtime options that allow you to turn off validations like array bounds checks or generic constraint for the whole program with unknown security ramifications. It would violate our principles around security. If you need to disable validations like this for your experiments, you have to build your custom version of the runtime.

My only response would be why offer Unsafe and the verification around memory checking at all then?
Things like SkipLocalsInit etc?

Thank you for your time and consideration!

My only response would be why offer Unsafe and the verification around memory checking at all then?

Those are opt-in at the individual call sites. They don't affect program-wide execution. Even SkipLocalsInit applies only to the library where it's annotated, not the entire app.

The runtime needs to allow the ability for high performance or interop code to be written, and that involves allowing the code author to make the conscientious decision to _selectively_ disable certain checks. Doing this program wide would disable these checks for routines which relied on them.

My only response would be why offer Unsafe and the verification around memory checking at all then?

Those are opt-in at the individual call sites. They don't affect program-wide execution. Even SkipLocalsInit applies only to the library where it's annotated, not the entire app.

The runtime needs to allow the ability for high performance or interop code to be written, and that involves allowing the code author to make the conscientious decision to _selectively_ disable certain checks. Doing this program wide would disable these checks for routines which relied on them.

There is no reason the same things could not be turned on or off at call sites right? I will admit that is getting to add quite a bit of metadata and checks to things, perhaps though it's worth it especially as the JIT eventually moves to C#. Most of the code will be eliminated during and after JIT anyway.. Doesn't it make that job easier in some regards?

Thank you for your time.

Yes, you can more quickly find weather you have problems of scale or resources such that if you know your code is already not buggy, you can turn off some switches and the program should get faster and run the same, if it crashes (you might not get a stack trace without the right switches) then you know your probably doing something wrong.

... this sounds suspicious. Like, if array bounds checks are making the difference, that should show up in a regular profiler. That's ignoring something like the JIT eliding bounds checking, which it does already. Toggling this sort of flag is not the first thing I'd try.

There is no reason the same things could not be turned on or off at call sites right?

If you make something easier (and a CLI switch is really easy compared to a custom IL rewriter), you implicitly signal that it should be more widely used (and it will be widely used, I am sure you can already see people "speeding up" their apps by using "this magic switch Microsoft won't tell you about"), which, I am sure you'll agree, is not a good thing for such a fundamental security feature like bounds checking. The whole appeal of .NET as a managed platform is that some things are not possible by design.

As previously noted, Unsafe is essentially a hack and a workaround for a fact that C# cannot represent some IL constructs. In a perfect world, we would not have to have Unsafe, MemoryMarshal or pointers, we would much rather have a perfect JIT that elides all unnecessary bounds checks in zero time, so it makes sense to invest into that.

That said, IL can represent things like "please deref this element from the array with no bounds check." I'd personally like it if there were a nicer syntax for this that didn't involve three levels of nested Unsafe statements.

An application could define its own static ref T UnsafeElementRef<T>(this T[] array, int index) with code similar to your comment https://github.com/dotnet/runtime/issues/29003#issuecomment-474164060 and then each element access would syntactically be just one call. I have not tested how well that would get inlined, though.

I could also imagine a C# syntax for omitting the bounds check:

element_access
    : primary_no_array_creation_expression 'unsafe'? '[' expression_list ']'
    ;

but that would be more costly to maintain and not much easier to use.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

matty-hall picture matty-hall  Â·  3Comments

Timovzl picture Timovzl  Â·  3Comments

noahfalk picture noahfalk  Â·  3Comments

GitAntoinee picture GitAntoinee  Â·  3Comments

jzabroski picture jzabroski  Â·  3Comments