不安全代码对安全代码有影响吗?

发布于 2024-11-01 14:40:51 字数 493 浏览 0 评论 0 原文

据我了解,将方法标记为不安全将禁用对该代码的某些 CLR 检查,但这对系统的其余安全部分有任何影响吗?除了 DLL/EXE 无法运行之外不受信任的环境。

特别是,

  1. 它们是否有任何安全检查,因为它被标记为不安全而无法对整个 dll 起作用?
  2. 如果 DLL 被标记为不安全,但标记为不安全的方法是 实际上没有被调用,这与 DLL 被标记为相同吗 安全的?
  3. 将不安全代码保留在运行时是否有任何好处? 单独的DLL?

我在 64 位 Windows 上重绘嵌套控件时遇到问题,详细信息 此处 以及解决方案(似乎有效的解决方案)涉及不安全代码,我想了解添加此代码对我的项目的影响。

As I understand it, marking an method as unsafe will disable some of the CLR checks on that code, but does this have any effect on the rest of the system which is safe, other than the fact that the DLL/EXE can not run in a untrusted environment.

In particular,

  1. Are they are any safety checks that will not work on the complete dll because it is marked as unsafe?
  2. If a DLL is marked as unsafe, but the methods marked as unsafe are
    not actually called, is this the same as if the DLL is marked as
    safe?
  3. Are they any run-time benefits on keeping the unsafe code in a
    separate DLL?

I have the problem with redrawing nested controls on 64-bit windows as detailed here and the one the solutions (the one that appears to work) involves unsafe code and I would like to understand the effect that adding this code has to my project.

如果你对这篇内容有疑问,欢迎到本站社区发帖提问 参与讨论,获取更多帮助,或者扫码二维码加入 Web 技术交流群。

扫码二维码加入Web技术交流群

发布评论

需要 登录 才能够评论, 你可以免费 注册 一个本站的账号。

评论(2

为你鎻心 2024-11-08 14:40:51

不安全的代码能够破坏托管堆。因此,同一进程中运行的任何内容都可能受到影响。

这包括同一进程中的所有其他以及可能的所有其他AppDomains


更新

这是一个例子:
http://blogs.msdn.com/b/tess/archive/2006/02/09/net-crash-management-heap-corruption-calling-unmanagement-code.aspx


更新2

所编写的代码是否不安全
勤奋不好?

不。.NET 框架本身存在大量不安全代码。示例有很多,但以下是 System.String 中的一个:

public static unsafe string Copy(string str)
{
    if (str == null)
    {
        throw new ArgumentNullException("str");
    }
    int length = str.Length;
    string str2 = FastAllocateString(length);
    fixed (char* chRef = &str2.m_firstChar)
    {
        fixed (char* chRef2 = &str.m_firstChar)
        {
            wstrcpyPtrAligned(chRef, chRef2, length);
        }
    }
    return str2;
}

An unsafe code is capable of corrupting the managed heap. As such, anything that runs in the same process can be affected.

This includes all other libraries and potentially all other AppDomains in the same process.


UPDATE

Here is an example:
http://blogs.msdn.com/b/tess/archive/2006/02/09/net-crash-managed-heap-corruption-calling-unmanaged-code.aspx


UPDATE 2

Is unsafe code that is written
diligently bad?

No. There are tons of unsafe code in the .NET framework itself. Examples many, but here is one in the System.String:

public static unsafe string Copy(string str)
{
    if (str == null)
    {
        throw new ArgumentNullException("str");
    }
    int length = str.Length;
    string str2 = FastAllocateString(length);
    fixed (char* chRef = &str2.m_firstChar)
    {
        fixed (char* chRef2 = &str.m_firstChar)
        {
            wstrcpyPtrAligned(chRef, chRef2, length);
        }
    }
    return str2;
}
吹泡泡o 2024-11-08 14:40:51

您的问题的答案是:unsafe关键字并不意味着“不安全”,它意味着“潜在不安全”。编译器和框架无法确保其安全。您需要确保代码不能对内存执行不安全的读取或写入。

我强烈鼓励您遵循您链接的文章中给出的建议:

1) 重新设计应用程序以减少容器减少嵌套级别的数量

如果您使用容器的唯一目的是控制排列,请编写自己的容器,可以在一个级别上完成所有排列。

已更新

您可以修改该文章中的代码,使其不使用指针(即不需要 unsafe 关键字)。请记住,这现在需要编组,这意味着额外的复制。这可能是一件好事,因为原始代码将 WINDOWPOS 指针从操作系统传递到 BeginInvoke,而 BeginInvoke 不会在操作系统生成指针的同一调度事件期间执行。换句话说,该代码已经很臭了。

internal class MyTabPage : TabPage
{
    private const int WM_WINDOWPOSCHANGING = 70;
    private const int WM_SETREDRAW = 0xB;
    private const int SWP_NOACTIVATE = 0x0010;
    private const int SWP_NOZORDER = 0x0004;
    private const int SWP_NOSIZE = 0x0001;
    private const int SWP_NOMOVE = 0x0002;

    [DllImport("User32.dll", CharSet = CharSet.Auto)]
    extern static int SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);

    [DllImport("User32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    extern static bool SetWindowPos(HandleRef hWnd, HandleRef hWndInsertAfter,
    int x, int y, int cx, int cy, int flags);

    [StructLayout(LayoutKind.Sequential)]
    private class WINDOWPOS
    {
        public IntPtr hwnd;
        public IntPtr hwndInsertAfter;
        public int x;
        public int y;
        public int cx;
        public int cy;
        public int flags;
    };

    private delegate void ResizeChildDelegate(WINDOWPOS wpos);

    private void ResizeChild(WINDOWPOS wpos)
    {
        // verify if it's the right instance of MyPanel if needed
        if ((this.Controls.Count == 1) && (this.Controls[0] is Panel))
        {
            Panel child = this.Controls[0] as Panel;

            // stop window redraw to avoid flicker
            SendMessage(new HandleRef(child, child.Handle), WM_SETREDRAW, 0, 0);

            // start a new stack of SetWindowPos calls
            SetWindowPos(new HandleRef(child, child.Handle), new HandleRef(null, IntPtr.Zero),
            0, 0, wpos.cx, wpos.cy, SWP_NOACTIVATE | SWP_NOZORDER);

            // turn window repainting back on 
            SendMessage(new HandleRef(child, child.Handle), WM_SETREDRAW, 1, 0);

            // send repaint message to this control and its children
            this.Invalidate(true);
        }
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_WINDOWPOSCHANGING)
        {
            WINDOWPOS wpos = new WINDOWPOS();
            Marshal.PtrToStructure(m.LParam, wpos);

            Debug.WriteLine("WM_WINDOWPOSCHANGING received by " + this.Name + " flags " + wpos.flags);

            if (((wpos.flags & (SWP_NOZORDER | SWP_NOACTIVATE)) == (SWP_NOZORDER | SWP_NOACTIVATE)) &&
            ((wpos.flags & ~(SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE)) == 0))
            {
                if ((wpos.cx != this.Width) || (wpos.cy != this.Height))
                {
                    BeginInvoke(new ResizeChildDelegate(ResizeChild), wpos);
                    return;
                }
            }
        }

        base.WndProc(ref m);
    }
}

注意:WINDOWPOS 从值类型更改为引用类型是有意为之。使用引用类型可将副本数量减少到只有一个(初始编组)(**)。

再次更新
我刚刚注意到代码最初公开了 p/invoke 声明。永远不要在类之外暴露 p/invoke(*)。如果您的目的是公开所提供的功能,请编写调用私有 p/invoke 声明的托管方法;在这种情况下不正确,p/invoke 严格是内部的。

(*) 好的,有一个例外。您正在创建 NativeMethodsUnsafeNativeMethods 等。这是 FxCop 执行 p/invoke 的推荐方法。

更新

(**) 我被要求(在其他地方)准确描述为什么在这里使用引用类型更好,所以我在这里添加了该信息。我被问到的问题是,“这不会增加内存压力吗?”

如果 WINDOWPOS 是值类型,则事件顺序如下:

1) 从非托管内存复制到托管内存

WINDOWPOS wpos = Marshal.PtrToStructure(m.LParam, typeof(WINDOWPOS));

2) 第二次复制?

BeginInvoke(new ResizeChildDelegate(ResizeChild), wpos);

等待! BeginInvoke 的签名是(Delegate, params object[])。这意味着 wpos 将被封杀。所以是的,第二个副本发生在这里:装箱操作。

BeginInvoke 将把委托和对象[]添加到调用列表中并发布注册的窗口消息。当消息泵从队列中删除该消息时,将使用 object[] 参数调用委托。

3) 拆箱并复制以供 ResizeChild 调用。

此时您会发现副本数量根本不是问题。它被转换为引用类型(盒装)的事实意味着我们最好一开始就将其设为引用类型。

The answer to your question is: The unsafe keyword does not mean "unsafe", it means "potentially unsafe". The compiler and framework cannot work to make certain that it's safe. It is up to you to make certain that the code cannot perform unsafe reads or writes to memory.

I would strongly encourage you to follow this advice given in the article you linked:

1) Redesign the application to have less containers and reduce the number of nesting levels.

If you're using containers for the sole purpose of control arrangement, write your own container that can do all the arrangement with one level.

Updated

You can modify the code in that article so that it doesn't use pointers (i.e. doesn't require the unsafe keyword). Keep in mind that this will now require marshalling which means extra copying. This is probably a good thing because the original code is passing a WINDOWPOS pointer from the OS to BeginInvoke which does not execute during the same dispatch event that the OS generated the pointer in. In other words, that code was smelly already.

internal class MyTabPage : TabPage
{
    private const int WM_WINDOWPOSCHANGING = 70;
    private const int WM_SETREDRAW = 0xB;
    private const int SWP_NOACTIVATE = 0x0010;
    private const int SWP_NOZORDER = 0x0004;
    private const int SWP_NOSIZE = 0x0001;
    private const int SWP_NOMOVE = 0x0002;

    [DllImport("User32.dll", CharSet = CharSet.Auto)]
    extern static int SendMessage(HandleRef hWnd, int msg, int wParam, int lParam);

    [DllImport("User32.dll", ExactSpelling = true, CharSet = System.Runtime.InteropServices.CharSet.Auto)]
    extern static bool SetWindowPos(HandleRef hWnd, HandleRef hWndInsertAfter,
    int x, int y, int cx, int cy, int flags);

    [StructLayout(LayoutKind.Sequential)]
    private class WINDOWPOS
    {
        public IntPtr hwnd;
        public IntPtr hwndInsertAfter;
        public int x;
        public int y;
        public int cx;
        public int cy;
        public int flags;
    };

    private delegate void ResizeChildDelegate(WINDOWPOS wpos);

    private void ResizeChild(WINDOWPOS wpos)
    {
        // verify if it's the right instance of MyPanel if needed
        if ((this.Controls.Count == 1) && (this.Controls[0] is Panel))
        {
            Panel child = this.Controls[0] as Panel;

            // stop window redraw to avoid flicker
            SendMessage(new HandleRef(child, child.Handle), WM_SETREDRAW, 0, 0);

            // start a new stack of SetWindowPos calls
            SetWindowPos(new HandleRef(child, child.Handle), new HandleRef(null, IntPtr.Zero),
            0, 0, wpos.cx, wpos.cy, SWP_NOACTIVATE | SWP_NOZORDER);

            // turn window repainting back on 
            SendMessage(new HandleRef(child, child.Handle), WM_SETREDRAW, 1, 0);

            // send repaint message to this control and its children
            this.Invalidate(true);
        }
    }

    protected override void WndProc(ref Message m)
    {
        if (m.Msg == WM_WINDOWPOSCHANGING)
        {
            WINDOWPOS wpos = new WINDOWPOS();
            Marshal.PtrToStructure(m.LParam, wpos);

            Debug.WriteLine("WM_WINDOWPOSCHANGING received by " + this.Name + " flags " + wpos.flags);

            if (((wpos.flags & (SWP_NOZORDER | SWP_NOACTIVATE)) == (SWP_NOZORDER | SWP_NOACTIVATE)) &&
            ((wpos.flags & ~(SWP_NOMOVE | SWP_NOSIZE | SWP_NOZORDER | SWP_NOACTIVATE)) == 0))
            {
                if ((wpos.cx != this.Width) || (wpos.cy != this.Height))
                {
                    BeginInvoke(new ResizeChildDelegate(ResizeChild), wpos);
                    return;
                }
            }
        }

        base.WndProc(ref m);
    }
}

Note: The change in WINDOWPOS from value type to reference type is intentional. Using a reference type reduces the number of copies to just one (the initial marshal)(**).

Updated Again
I just noticed that the code originally made the p/invoke declarations public. Never, ever expose p/invoke outside of a class(*). Write managed methods that invoke private p/invoke declarations if your intent is to expose the capabilities provided; which in this case is not true, the p/invoke is strictly internal.

(*) Ok, one exception. You're creating a NativeMethods, UnsafeNativeMethods, etc. Which is the recommended way to do p/invoke by FxCop.

Updated

(**) I was asked (elsewhere) to describe precicely why using a reference type here is better, so I've added that info here. The question I was asked was, "Doesn't this add memory pressure?"

If WINDOWPOS was a value type, this would be the sequence of events:

1) Copy from unmanaged to managed memory

WINDOWPOS wpos = Marshal.PtrToStructure(m.LParam, typeof(WINDOWPOS));

2) Second copy?

BeginInvoke(new ResizeChildDelegate(ResizeChild), wpos);

Wait! The signature of BeginInvoke is (Delegate, params object[]). That means wpos is going to get boxed. So yes, a second copy occurs here: The boxing operation.

BeginInvoke will add the delegate and object[] to an invocation list and post a registered window message. When that message is removed from the queue by the message pump, the delegate will be called with the object[] parameters.

3) Unbox and copy for ResizeChild call.

At this point you can see that the number of copies isn't even the issue. The fact that it gets converted to a reference type (boxed) means that we are better off making it a reference type to begin with.

~没有更多了~
我们使用 Cookies 和其他技术来定制您的体验包括您的登录状态等。通过阅读我们的 隐私政策 了解更多相关信息。 单击 接受 或继续使用网站,即表示您同意使用 Cookies 和您的相关数据。
原文