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

Posted

技术标签:

【中文标题】不安全代码对安全代码有影响吗?【英文标题】:Does unsafe code have any effect on safe code? 【发布时间】:2011-08-06 08:21:12 【问题描述】:

据我了解,将方法标记为不安全将禁用对该代码的某些 CLR 检查,但这对系统的其他安全部分是否有任何影响,除了 DLL/EXE 可以不要在不受信任的环境中运行。

特别是,

    它们是不是因为被标记为不安全而对完整的 dll 不起作用的任何安全检查? 如果 DLL 被标记为不安全,但标记为不安全的方法是 没有实际调用,这是否与 DLL 被标记为一样 安全吗? 将不安全代码保存在 单独的 DLL?

我在 64 位窗口上重绘嵌套控件时遇到问题,详细说明 here 并且解决方案(似乎可行的解决方案)涉及不安全代码,我想了解添加此代码的效果到我的项目。

【问题讨论】:

【参考方案1】:

您的问题的答案是:unsafe 关键字并不表示“不安全”,而是表示“可能不安全”。编译器和框架无法确保它是安全的。您可以确保代码不能对内存执行不安全的读取或写入操作。

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

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

如果您将容器仅用于控制排列,请编写您自己的容器,它可以在一个级别完成所有排列。

更新

您可以修改该文章中的代码,使其不使用指针(即不需要 unsafe 关键字)。请记住,这现在需要编组,这意味着额外的复制。这可能是件好事,因为原始代码将 WINDOWPOS 指针从操作系统传递给 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 调用拆箱并复制。

此时您可以看到,份数甚至都不是问题。它被转换为引用类型(装箱)这一事实意味着我们最好将其作为引用类型开始。

【讨论】:

感谢您的回答。我仍然希望找到一种避免使用不安全代码的方法,但还没有找到可靠的方法(到目前为止)。重新设计应用程序并非易事,因为涉及在不同地方重复使用的多个用户控件。但是,我认为您(和其他人)错过了我试图(并且显然失败)提出的问题的重点,即将 dll 标记为不安全并将方法标记为不安全是否对其余代码有任何影响即使我们假设实际的不安全代码永远不会出错。 @sgmoore:我更新了我的答案以展示如何从代码中删除指针的使用。它没有回答问题,但我不知道使用 /unsafe 开关所涉及的细节。 感谢这似乎有效。我显然必须做更多的测试,并试图弄清楚它的作用。例如,如果 ReSizeChild 不执行任何操作,则调用 ReSizeChild 而不调用 base.WndProc(ref m) 的后果。 此外,“安全”代码只是“潜在安全”,因为安全代码可以 dllimport,以及处理或修改交给其他人的 dllimport 的 IntPtr。这两者都可能导致崩溃或内存损坏。乞求“安全甚至意味着什么?”的问题 @Tergiver - David Jeske 已经证明了为什么 Verifiable 不能保证内存写入是有效的。【参考方案2】:

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

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


更新

这是一个例子: http://blogs.msdn.com/b/tess/archive/2006/02/09/net-crash-managed-heap-corruption-calling-unmanaged-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;

【讨论】:

我理解并且必须接受如果不安全代码中存在错误所涉及的风险,但假设不安全代码可以完美运行(或从未执行),我的整个系统是否会更不安全。 没错。他们将关键字设为“不安全”是有原因的。 @Harry:确实,即使 MS 营销部门不希望团队打电话给 unsafe (artima.com/intv/choices2.html)。很高兴他们确实称它为unsafe “安全”代码也能够破坏堆,因为安全代码可以 dllimport 并传递该入口点无效指针,包括“new IntPtr(666)”。

以上是关于不安全代码对安全代码有影响吗?的主要内容,如果未能解决你的问题,请参考以下文章

Firefox 开源 为啥安全

禁用右键单击对安全性有影响吗?

代码静态扫描属于安全测试吗

Log4j 安全违规漏洞对 log4net 有影响吗?

JSPatch 部署安全策略

kms激活到底安全不安全