Vraag Overlappen meerdere CLR-referentievelden met elkaar in expliciete struct?


Bewerk: Ik ben me er goed van bewust dat dit heel goed werkt met waardetypes, mijn specifieke vraag gaat over het gebruik van dit voor referentietypen.

Edit2: Ik ben me er ook van bewust dat je referentietypen en waardetypes niet in een struct kunt overlappen, dit is alleen voor het geval dat meerdere referentietype velden elkaar overlappen.

Ik heb gesleuteld met structs in .NET / C #, en ik heb net ontdekt dat je dit kunt doen:

using System;
using System.Runtime.InteropServices;

namespace ConsoleApplication1 {

    class Foo { }
    class Bar { }

    [StructLayout(LayoutKind.Explicit)]
    struct Overlaid {
        [FieldOffset(0)] public object AsObject;
        [FieldOffset(0)] public Foo AsFoo;
        [FieldOffset(0)] public Bar AsBar;
    }

    class Program {
        static void Main(string[] args) {
            var overlaid = new Overlaid();
            overlaid.AsObject = new Bar();
            Console.WriteLine(overlaid.AsBar);

            overlaid.AsObject = new Foo();
            Console.WriteLine(overlaid.AsFoo);
            Console.ReadLine();
        }
    }
}

In principe omzeilen om dynamische casting te doen tijdens runtime door een struct te gebruiken die een expliciete veldlay-out heeft en vervolgens toegang te krijgen tot het object binnenin, aangezien het het juiste type is.

Nu is mijn vraag: kan dit op de een of andere manier tot lekken van geheugen leiden, of tot enig ander ongedefinieerd gedrag binnen de CLR? Of is dit een volledig ondersteunde conventie die zonder problemen kan worden gebruikt?

Ik ben me ervan bewust dat dit een van de donkerste hoeken van de CLR is, en dat deze techniek slechts in een paar specifieke gevallen een haalbare optie is.


10
2018-04-24 07:25


oorsprong


antwoorden:


Ik kan niet zien hoe de expliciete lay-outversie kan worden gecontroleerd zonder dat de runtime extra controles uitvoert hoe dan ook, omdat u hiermee een niet-lege verwijzing naar iets ziet dat niet van het gedeclareerde type is.

Dit zou veiliger zijn:

struct Overlaid { // could also be a class for reference-type semantics
    private object asObject;
    public object AsObject {get {return asObject;} set {asObject = value;} }
    public Foo AsFoo { get {return asObject as Foo;} set {asObject = value;} }
    public Bar AsBar { get {return asObject as Bar;} set {asObject = value;} }
}

Geen risico op gescheurde referenties enz., En nog steeds slechts een enkel veld. Het bevat geen risicovolle code, enz. In het bijzonder riskeert het niet iets dwaas zoals:

    [FieldOffset(0)]
    public object AsObject;
    [FieldOffset(0)]
    public Foo AsFoo;
    [FieldOffset(1)]
    public Bar AsBar; // kaboom!!!!

Een ander probleem is dat u slechts één veld op deze manier kunt ondersteunen, tenzij u de CPU-modus kunt garanderen; offset 0 is eenvoudig, maar het wordt lastiger als u meerdere velden nodig heeft en x86 en x64 moet ondersteunen.


2
2018-04-24 07:51



Nou, je hebt een lus gevonden, de CLR staat dit toe, omdat alle overlappende velden objecten zijn. Alles dat u toestaat om te knoeien met een objectreferentie, wordt direct geweigerd met een TypeLoadException:

  [StructLayout(LayoutKind.Explicit)]
  struct Overlaid {
    [FieldOffset(0)]
    public object AsObject;
    [FieldOffset(0)]
    public IntPtr AsPointer;
  }

Maar je kunt het gebruiken door de klassenvelden te geven. Niets ergs gebeurt, als u alleen maar de veldwaarden leest, kunt u bijvoorbeeld de waarde van de tracking-greep op die manier krijgen.

Het schrijven van die velden leidt echter tot een ExecutionEngineException. Ik denk echter dat het een exploit is als je de waarde van een volghandvat correct kunt raden. Praktisch gebruik is echter voldoende dichtbij nul.


3
2018-04-24 08:32



Als u het type op een onveilige manier uitlijnt, gooit de runtime een a TypeLoadException bij laden, zelfs bij het compileren met /unsafe. Dus ik denk dat je veilig bent.

Ik gok - omdat je kunt gebruiken StructLayout en compileer uw code zonder /unsafe vlaggen - dat dit een kenmerk van de CLR is. U hebt het kenmerk StructLayout eenvoudig nodig omdat C # geen directe manier heeft om typen op deze manier te declareren.

Kijk eens naar deze pagina welke details van de manier waarop C # structs vertaalt naar IL, zul je merken dat er veel ondersteuning voor de geheugenlay-outs ingebouwd is in de IL / CLR zelf.


2
2018-04-24 07:57



Omdat de garbage collector niet is getypt en alleen onderscheid maakt tussen objectreferenties en gewone bits, zullen overlappende referenties het niet verwarren. Hoewel de ene objectreferentie de andere echter volledig kan overlappen, is deze niet-verifieerbaar, ook wel onveilig (ECMA-335 standaard, pagina 180, II.10.7 Beheerde instantie-indeling). Het is eenvoudig om een ​​programma te maken dat misbruik maakt van deze oncontroleerbaarheid om verschrikkelijk te crashen:

using System.Runtime.InteropServices;

class Bar
{
    public virtual void func() { }
}

[StructLayout(LayoutKind.Explicit)]
struct Overlaid
{
    [FieldOffset(0)]
    public object foo;

    [FieldOffset(0)]
    public Bar bar;
}

class Program
{
    static void Main(string[] args)
    {
        var overlaid = new Overlaid();
        overlaid.foo = new object();
        overlaid.bar.func();
    }
}

Hier laadt de func-aanroep een functie-aanwijzer van één voorbij het laatste element van de virtuele tabel van de objectklasse. Volgens Dit artikel na de vtbl is er een handle table. Behandelen als een functie aanwijzer leidt tot een System.AccessViolationException.


1
2018-06-30 01:19



Ik ken geen problemen ermee. Verder betwijfel ik of Microsoft dit gebruik zou toestaan ​​als het op een niet voor de hand liggende manier gevaarlijk was.


-1
2018-04-24 07:36