94

Based on the following question asked a few days ago in SO: GetType() and polymorphism and reading Eric Lippert's answer, I started thinking if making GetType() not be virtual really ensured that an object could not lie about its Type.

Specifically, Eric's answer states the following:

The framework designers are not going to add an incredibly dangerous feature such as allowing an object to lie about its type merely to make it consistent with three other methods on the same type.

Now the question is: can I make an object that does lie about its type without it being immediately obvious? I may be profoundly wrong here and I'd love clarification if that is the case, but consider the following code:

public interface IFoo
{
    Type GetType();
}

And the following two implementations of said interface:

public class BadFoo : IFoo
{
    Type IFoo.GetType()
    {
        return typeof(int);
    }
}

public class NiceFoo : IFoo
{
}

Then if you run the following simple program:

static void Main(string[] args)
{
    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    Console.WriteLine("BadFoo says he's a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '{0}'", niceFoo.GetType().ToString());
    Console.ReadLine();
}

Sure enough badFoo outputs an erroneous Type.

Now I don't know if this has any serious implications based on Eric describing this behavior as an "incredibly dangerous feature", but could this pattern pose a credible threat?

4

8 回答 8

45

好问题!在我看来,如果 GetType 在对象上是虚拟的,那么您只能真正误导其他开发人员,而事实并非如此。

您所做的类似于遮蔽 GetType,如下所示:

public class BadFoo
{
    public new Type GetType()
    {
        return typeof(int);
    }
}

使用此类(并使用MSDN 中的示例代码获取 GetType() 方法),您确实可以拥有:

int n1 = 12;
BadFoo foo = new BadFoo();

Console.WriteLine("n1 and n2 are the same type: {0}",
                  Object.ReferenceEquals(n1.GetType(), foo.GetType())); 
// output: 
// n1 and n2 are the same type: True

所以,哎呀,你成功地撒了谎,对吧?好吧,是的,也不是……考虑一下,将其用作漏洞利用意味着将您的 BadFoo 实例用作某处方法的参数,该方法可能期望object对象层次结构的一个或公共基类型。像这样的东西:

public void CheckIfInt(object ob)
{
    if(ob.GetType() == typeof(int))
    {
        Console.WriteLine("got an int! Initiate destruction of Universe!");
    }
    else
    {
        Console.WriteLine("not an int");
    }
}

CheckIfInt(foo)打印“不是 int”。

因此,基本上(回到您的示例),您实际上只能使用某人针对您的界面编写的代码来利用您的“说谎类型” ,这对于它具有“自定义”方法IFoo这一事实非常明确。GetType()

只有当 GetType() 在对象上是虚拟的时,您才能制作一种“说谎”类型,该类型可以与CheckIfInt上述方法一起使用,从而在其他人编写的库中造成严重破坏。

于 2013-05-28T09:19:02.017 回答
32

有两种方法可以确定类型:

  1. typeof在不能重载的类型上使用

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    
    Console.WriteLine("BadFoo says he's a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '{0}'", niceFoo.GetType().ToString());
    
    Console.WriteLine("BadFoo really is a '{0}'", typeof(BadFoo));
    Console.WriteLine("NiceFoo really is a '{0}'", typeof(NiceFoo));
    Console.ReadLine();
    
  2. 将实例转换为object并调用GetType()方法

    IFoo badFoo = new BadFoo();
    IFoo niceFoo = new NiceFoo();
    
    Console.WriteLine("BadFoo says he's a '{0}'", badFoo.GetType().ToString());
    Console.WriteLine("NiceFoo says he's a '{0}'", niceFoo.GetType().ToString());
    
    Console.WriteLine("BadFoo really is a '{0}'", ((object)badFoo).GetType());
    Console.WriteLine("NiceFoo really is a '{0}'", ((object)niceFoo).GetType());
    Console.ReadLine();
    
于 2013-05-28T09:05:55.757 回答
10

不,你不能让 GetType 撒谎。您只是在介绍一种新方法。只有知道此方法的代码才会调用它。

例如,您不能让第三方或框架代码调用您的新 GetType 方法而不是真正的方法,因为该代码不知道您的方法存在,因此永远不会调用它。

但是,您可以用这样的声明混淆您自己的开发人员。任何使用您的声明编译并使用类型为 IFoo 的参数或变量或任何从该类型派生的类型的代码确实会使用您的新方法。但由于这只影响你自己的代码,它并没有真正施加“威胁”。

如果您确实想为类提供自定义类型描述,则应使用自定义类型描述符来完成,也许通过使用TypeDescriptionProviderAttribute注释您的类。这在某些情况下很有用。

于 2013-05-28T09:22:12.300 回答
7

好吧,实际上已经有一种类型可以存在于GetType:任何可为空的类型。

这段代码

int? x = 0; int y = 0;
Console.WriteLine(x.GetType() == y.GetType());

输出True


其实,不是int?谁在说谎,只是隐式转换成一个object盒子。但是,您仍然无法从with中分辨出来。int?intint?intGetType()

于 2013-05-28T19:32:29.007 回答
5

我认为不会,因为每个调用 GetType 的库代码都会将变量声明为“对象”或通用类型“T”

以下代码:

    public static void Main(string[] args)
    {
        IFoo badFoo = new BadFoo();
        IFoo niceFoo = new NiceFoo();
        PrintObjectType("BadFoo", badFoo);
        PrintObjectType("NiceFoo", niceFoo);
        PrintGenericType("BadFoo", badFoo);
        PrintGenericType("NiceFoo", niceFoo);
    }

    public static void PrintObjectType(string actualName, object instance)
    {
        Console.WriteLine("Object {0} says he's a '{1}'", actualName, instance.GetType());
    }

    public static void PrintGenericType<T>(string actualName, T instance)
    {
        Console.WriteLine("Generic Type {0} says he's a '{1}'", actualName, instance.GetType());
    }

印刷:

对象 BadFoo 说他是“TypeConcept.BadFoo”

对象 NiceFoo 说他是“TypeConcept.NiceFoo”

通用类型 BadFoo 说他是“TypeConcept.BadFoo”

通用类型 NiceFoo 说他是“TypeConcept.NiceFoo”

唯一一次这种代码会导致糟糕的情况是在您自己的代码中,您将参数类型声明为 IFoo

    public static void Main(string[] args)
    {
        IFoo badFoo = new BadFoo();
        IFoo niceFoo = new NiceFoo();
        PrintIFoo("BadFoo", badFoo);
        PrintIFoo("NiceFoo", niceFoo);
    }

    public static void PrintIFoo(string actualName, IFoo instance)
    {
        Console.WriteLine("IFoo {0} says he's a '{1}'", actualName, instance.GetType());
    }

IFoo BadFoo 说他是“System.Int32”

IFoo NiceFoo 说他是“TypeConcept.NiceFoo”

于 2013-05-28T09:08:36.387 回答
4

据我所知,最糟糕的情况是误导那些碰巧使用毒类的无辜程序员,例如:

Type type = myInstance.GetType();
string fullName = type.FullName;
string output;
if (fullName.Contains(".Web"))
{
    output = "this is webby";
}
else if (fullName.Contains(".Customer"))
{
    output = "this is customer related class";
}
else
{
    output = "unknown class";
}

如果myInstance是您在问题中描述的类的实例,它将被视为未知类型。

所以我的回答是否定的,在这里看不到任何真正的威胁。

于 2013-05-28T09:01:59.063 回答
3

如果您想安全应对这种黑客攻击,您有一些选择:

首先投射到对象

GetType()您可以通过首先将实例转换为一个来调用原始方法object

 Console.WriteLine("BadFoo says he's a '{0}'", ((object)badFoo).GetType());

结果是:

BadFoo says he's a 'ConsoleApplication.BadFoo'

使用模板方法

使用这个模板方法也会给你真正的类型:

static Type GetType<T>(T obj)
{
    return obj.GetType();
}

GetType(badFoo);
于 2013-05-28T09:19:08.423 回答
2

object.GetType和之间有区别IFoo.GetTypeGetType在编译时对未知对象调用,而不是在接口上调用。在您的示例中,输出badFoo.GetType是预期的行为,因为您重载了该方法。唯一的问题是,其他程序员可能会对这种行为感到困惑。

但是如果你使用typeof()它会输出类型相同,并且你不能覆盖typeof().

程序员也可以在编译时看到GetType他调用了哪个方法。

所以对你的问题:这种模式不能构成可信的威胁,但它也不是最好的编码风格。

于 2013-05-28T09:08:14.230 回答