VB安全编码规范(续)

VB安全编码规范(续)

时间:2016-08-12 作者:zhankehua 评论:0 点击:3075 次

 

1. VB编程规范

1.1.   文件和结构

×一定不要在一个源文件内拥有一个以上的Public类型,除非它们只有泛型参数个数的差别,或者具有嵌套关系。一个文件内有多个内部类型是允许的。

一定要以源文件所含的Public类名命名该文件。比如,MainForm类应该在MainForm.vb 文件内,而 List<T>类应该在List.vb 文件内。

1.2.   命名规范

1.2.1.    综合命名规范

一定是为各种类型,函数,变量,特性和数据结构选取有意义的命名。其命名应能反映其作用。

×不应该在标识符名中使用缩短或缩略形式的词。例如,使用“GetWindow”而不是“GetWin”。对于公共类型,线程方法,窗口方法,和对话框方法函数,为“ThreadProc”, “DialogProc”, “WndProc”等使用公共后缀。

×一定不要使用下划线,连字号,或其他任何非字母数字的字符。

1.2.2.    标识符的大小写命名规范

如下表格描述了对不同类型标识符的大小写命名规范。

标识符

规范

命名结构

示例

类、结构体

Pascal规范

名词

public   class ComplexNumber {...}

public   struct ComplextStruct

{...}

模块

Pascal规范

名词

应该在给模块命名时,他的名称除了必须以名词命名外,加以后缀

 

Module SharedFunctionModule

    ...

End Module

命名空间

Pascal规范

名词

×一定不要以相同的名称来命名命名空间和其内部的类型。

namespace  

枚举

Pascal规范

名词

一定要以复数名词或名词短语来命名标志枚举,以单数名词或名词短语来命名简单枚举。

<Flags()>

    Public Enum FomeFlags

  

    End Enum

方法

Pascal规范

动词或动词短语

public   sub Print()

...

End   sub

Public属性

Pascal规范

名词或形容词

一定要以集合中项目的复数形式命名该集合,或者单数名词后面跟 “List” 或者 “Collection”。

一定要以肯定短语来命名布尔属性,(CanSeek ,而不是CantSeek)。当以 “Is,” “Can,” or “Has” 作布尔属性的前缀有意义时,也可以这样做。

Public CustomerNames As String

Public ItemCollection As Items

非Public属性

Camel规范或_camel规范

名词或形容词

一定要在您使用'_' 前缀时,保持代码一致性。

Private   name As string

Private   _name As string

事件

Pascal规范

动词或动词短语

一定要用现在式或过去式来表明事件之前或是之后的概念。

×一定不要使用 “Before” 或者“After”   前缀或后缀来指明事件的先后。

Public   WindowClosed As event

public WindowClosing As event

 

委托

Pascal规范

一定要为用于事件的委托增加‘EventHandler后缀。

一定要为除了用于事件处理程序之外的委托增加‘Callback后缀。

×一定不要为委托增加    “Delegate” 后缀。

Public   WindowClosedEventHandler As delegate

接口

Pascal规范,带有‘I’前缀

名词

Public   IDictionary As interface

常量


名词

Pascal规范用于Public常量;

Camel规范用于Internal常量;

只有1或2个字符的缩写需全部字符大写。

Public   const MessageText As string =   "A"

Private const As messageText string = "B"

Public   const PI As double =   3.14159

参数,变量

Camel规范

名词

Dim    customerID As   Integer

泛型参数

Pascal规范,带有‘T’ 前缀

名词

一定要以描述性名称命名泛型参数,除非单字符名称已有足够描述性。

一定要以T作为描述性类型参数的前缀。

您应该使用 T 作为单字符类型参数的名称。

T, TItem, TPolicy

1.2.3.    匈牙利命名法

×一定不要在.NET中使用匈牙利命名法(例如,不要在变量名称内带有其类型指示符)。

1.2.4.    户界面控件命名规范

用户控件应该使用如下前缀,其重要目的是使代码更易读。

控件类型

前缀

Button

btn

CheckBox

chk

CheckedListBox

lst

ComboBox

cmb

ContextMenu

mnu

DataGrid

dg

DateTimePicker

dtp

Form

suffix: XXXForm

GroupBox

grp

ImageList

iml

Label

lb

ListBox

lst

ListView

lvw

Menu

mnu

MenuItem

mnu

NotificationIcon

nfy

Panel

pnl

PictureBox

pct

ProgressBar

prg

RadioButton

rad

Splitter

spl

StatusBar

sts

TabControl

tab

TabPage

tab

TextBox

tb

Timer

tmr

TreeView

tvw

 

比如,对于 “File | Save” 菜单选线, “Save” 菜单项应该命名为 “mnuFileSave”。

1.3.   常量

一定要将那些永远不会改变值定义为常量字段。编译器直接将常量字段嵌入调用代码处。所以常量值永远不会被改变,且并不会打破兼容性。

Public   Class Int32

Public Const MaxValue AsInteger = &H7FFFFFFF

Public Const MinValue AsInteger = &H80000000

End Class

一定要为预定义的对象实例使用public static (shared)readonly字段。如果有预定义的类型实例,也将该类型定义为public static readonly。举例,

Public Class ShellFolder

Public Shared ReadOnly ProgramData As New ShellFolder"ProgramData"

Public Shared ReadOnly ProgramFiles As New ShellFolder"ProgramFiles"

...

End Class

1.4.   字符串

×一定不要使用 ‘+’ 操作符( VB.NET中的‘&’)来拼接大量字符串。相反,您应该使用StringBuilder 来实现拼接工作。然而,拼接少量的字符串时,可以使用 ‘+’ 操作符( VB.NET中的‘&’)。

Good:

Dim sbXML As New StringBuilder

sbXML.Append"<parent>"

sbXML.Append"<child>"

sbXML.Append"Data"

sbXML.Append"</child>"

sbXML.Append"</parent>"

应该在连接字符串时使用&运算符,避免使用+运算符。下面例子用+运算符来连接可能会导致问题。例如:

Dim vntVar1 As String = "10.01"

        Dim vntVar2 As Integer = 11

        Dim vntResult1 As String = vntVar1 + vntVar2 'vntResult = 21.01

        Dim vntResult2 As String = vntVar1 & vntVar2 'vntResult = 10.0111

        lbString1.Text = vntResult1

lbString2.Text = vntResult2

003.png

 

一定要使用显式地指定了字符串比较规则的重载函数。一般来说,需要调用带有StringComparison类型参数的重载函数。

一定要使用String.Equals的重载版本来测试2个字符串是否相等。比如,忽略大小写后,判断2个字符串是否相等,

×一定不要使用 String.Compare或CompareTo的重载版本来检验返回值是否为0,来判断字符串是否相等。这2个函数是用于字符串排序,而非检查相等性。

一定要在字符串比较时,以String.ToUpperInvariant函数使字符串规范化,而不用String.ToLowerInvariant。

1.5.   数组和集合

应该在低层次函数中使用数组,来减少内存消耗,增强性能表现。对于公开接口,则推荐使用集合。集合提供了对于其内容更多的控制权,可以随着时间改善,提高可用性。另外,不推荐在只读场合下使用数组,因为数组克隆的代价太高。

×一定不要使用只读的数组字段。字段本身只读,不能被修改,但是其内部元素可以被修改。以下示例展示了使用只读数组字段的陷阱:

Bad:

Private ReadOnly InvalidPathChars() As String = {"'", "<", ">", "|"}

这允许调用者修改数组内的值:

InvalidPathChars(0)= '&'

应该使用不规则数组来代替使用多维数组。一个不规则数组是指其元素本身也是一个数组。构成元素的数组可能有不同大小,这样与多维数组相比能减少一些数据集的空间浪费(例如,稀疏矩阵)。另外,CLR能够对不规则数组的索引操作进行优化,所以在某些情景下,具有更好的性能表现。

// 不规则数组

 

Dim multiDimArray(,)As Integer = _

{ _

    {1, 2, 3, 4}, _

    {5, 6, 7, 0}, _

    {8, 0, 0, 0}, _

    {9, 0, 0, 0} _

}

应该重新考虑对于ArrayList 的使用,因为所有添加至其中的对象都被当做System.Object ,当从ArrayList 取回值时,这些对象都会拆箱,并返回其真实的值类型。所以我们推荐您使用定制类型的集合,而不是ArrayList。例如:.NET 在

System.Collection.Specialized命名空间内为String提供了强类型集合StringCollection。

您应该重新考虑对于Hashtable的使用。相反,您应该尝试其他字典类,例如StringDictionary,NameValueCollection, HybridCollection。除非Hashtable 只存储少量值,最好不要使用Hashtable。

您应该在实现集合类型时,为其实现IEnumerable 接口,这样该集合便能用于LINQ to Objects。

×一定不要在同一个类型上同时实现IEnumerator(Of T)和 IEnumerable(Of T)接口。同样,也不要同时实现非泛型接口IEnumerator 和 IEnumerable。所以,一个类型只能成为一个集合或者一个枚举器,而不可二者皆得。

×一定不要返回数组或集合的null引用。空值数组或集合的含义在代码环境中很难被理解。例如,一个用户可能假定如下代码能够正常运行,所以应该返回一个空数组或集合,而不是null引用。(示例代码代码怎么写,有待完善)

1.6.   结构体

一定要确保将所有实例数据设置为0值,false或者是null。当创建结构体数组时,这样能防止意外创建了无效实例。

一定要为值类型实现 IEquatable(Of T)接口。值类型的Object.Equals 方法会引起装箱操作。且因为使用了反射特性,所以其默认实现效率不高。IEquatable(Of T).Equals 较其有相当大的性能提升,且其实现可以不引发装箱操作。

1.6.1.    结构体VS类

×一定不要定义结构体,除非其具有如下特性:

      它在逻辑上代表了一个单值,类似于原始类型(例如,Integer、 double,等等)。

      其示例大小小于16字节。

      它是不可变的。

      它不会引发频繁的装箱拆箱操作。

其余情况下,您应该定义类,而不是结构体。

1.7.  

一定要使用继承来表示 “is a” 关系,例如 “猫是一种动物”。

一定要使用接口,例如IDisposable,来表示 “can do” 关系,例如 “对象能被释放”。

1.7.1.    字段

×一定不要提供Public或Protected的实例字段。Public或Protected字段并没有受到代码访问安全需求的保护。我们应该使用Private字段,并通过属性来提供访问接口。

一定要将预定义对象实例定义为public static readonly字段。

一定要将永远不会改变的字段定义为常量字段。

×一定不要将可变类型定义为只读字段。

1.7.2.    属性

一定要创建只读属性,如果用户不应该具有修改这些属性值的能力。

×一定不要提供只写属性。如果没有提供属性设置器,请使用一个方法来实现相同的功能。方法名应该以Set开头,后面跟着属性名。

一定要为所有属性提供合理的默认值,并确保默认值不会引发安全漏洞或一个极端低效的设计。

×不应该从属性访问器内抛出异常。属性访问器应该只包含简单的操作,且不带任何前置条件。如果一个属性访问器可能抛出异常,那么考虑将其重新设计为一个方法。该推荐方法并不适用于索引器。索引器可以因为无效实参而抛出异常。从属性设置器内抛出异常是有效且可接受的。

1.7.3.    构造函数

一定要尽量减少构造函数的工作量。除了得到构造函数参数,设置主要数据成员,构造函数不应该有太多的工作量。其余工作量应该被推迟,直到必须。

一定要在恰当的时候,从实例构造函数内抛出异常。

一定要在需要默认构造函数的情况下,显式的声明它。即使有时编译器为自动的为您的类增加一个默认构造函数,但是显式的声明使得代码更易维护。这样即使您增加了一个带有参数的构造函数,也能确保默认构造函数仍然会被定义。

×一定不要在对象构造函数内部调用虚方法。调用虚方法时,实际调用了继承体系最底层的覆盖方法,而未考虑定义了该方法的类的构造函数是否已被调用。

1.7.4.    方法

一定要将所有输出参数放置于所有传值和传引用参数(除去参数数组)的之后,即使它引起了重载方法之前不一致的参数顺序。

一定要验证传递给Public,Protected或显式实现的成员方法的实参。如果验证失败,则抛出System.ArgumentException,或者其子集:如果一个 null 实参传递给成员,而成员方法不支持null实参,则抛出ArgumentNullException。如果实参值超出由调用方法定义的可接受范围,则抛出 ArgumentOutOfRangeException异常。

1.7.5.    事件

一定要注意事件处理方法中可能会执行任意代码。考虑将引发事件的代码放入一个try-catch块中,以避免由事件处理方法中抛出的未处理异常引起的程序终止。

×一定不要在有性能要求的API中使用事件。虽然事件易于理解和使用,但是就性能和内存消耗而言,它们不如虚函数。

1.7.6.    成员方法重载

一定要使用成员方法重载,而不是定义带有默认参数的成员方法。默认参数并不是CLS兼容的,所以不能被某些语言重用。同时,带有默认参数的成员方法存在一个版本问题。我们考虑成员方法的版本1将可选参数默认设置为123。当编译代码调用该方法,且没有指定可选参数时,编译器在调用处直接将123嵌入代码中。现在,版本2将默认参数修改为863,如调用代码没有重新编译,那么它会调用版本2的方法,并传递123作为其参数。(123是版本1的默认参数,而不是版本2的。)。

Good:

Public Overloads Sub RotateByVal data As Matrix)

    Rotate(data, 180)

EndSub

 

Public Overloads Sub RotateByVal data As Matrix, ByVal degrees AsInteger

     …

EndSub

 

Bad:

Public Sub RotateByVal data As Matrix, OptionalByVal degrees AsInteger = 180)

     …

EndSub

×一定不要任意改动重载方法中的参数名。如果一个重载函数中的参数代表着另一个重载函数中相同的参数,该参数则应该有相同的命名。具有相同命名的参数应该在重载函数中出现在同一位置。

1.7.7.    接口成员

×不应该在没有合理理由的情况下显式的实现接口成员。显式实现的成员可能使开发者感到困惑,因为他们不会出现在Public成员列表内,且会造成对值类型不必要的装箱拆箱操作。

应该在成员只通过接口来调用的情况下,显式的实现成员接口。

1.7.8.    虚成员方法

相较于回调和事件,虚成员方法性能上有更好的表现。但是比非虚方法在性能上低一点。

×一定不要在没有合理理由的情况下,将成员方法设置为虚方法,您必须意识到相关设计,测试,维护虚方法带来的成本。

应该倾向于为虚成员方法设置为Protected的访问性,而不是Public访问性。Public成员应该通过调用Protected的虚方法来提供拓展性(如果需要的话)。

1.7.9.    静态类

一定要合理使用静态类。静态类应该被用于框架内基于对象的核心支持辅助类。

1.7.10.  抽象类

×一定不要在抽象类中定义Public或Protected Internal的构造函数。

一定要为抽象类定义一个Protected,或Internal构造函数。

Protected构造函数更常见,因为其允许当子类创建时,基类可以完成自己的初始化工作。internal构造函数用于限制将抽象类的实现具化到定义该类的程序集。

1.8.   命名空间

通常,一个工程使用一个名字空间,通常不需要用Namespace语句,而是在工程选项的“Root Namespace”中指定,使用根名字空间可以使代码更加整齐,容易修改,这一点是VB十足的优点。名字空间的语法是:公司名.产品名[.组件名的复数] 如:

Namespace Ninputer.VirtualScreen

    ...

End Namespace

1.9.   错误和异常

1.9.1.    抛出异常

一定要通过抛出异常来告知执行失败。异常在框架内是告知错误的主要手段。如果一个成员方法不能成功的如预期般执行,便应该认为是执行失败,并抛出一个异常。一定不要返回一个错误代码。

一定要抛出最明确,最有意义的异常(继承体系最底层的)。例如,如果传递了一个null实参,则抛出ArgumentNullException ,而不是其基类ArgumentException 。抛出并捕获System.Exception异常通常都是没有意义的。

×一定不要将异常用于常规的控制流。除了系统故障或者带有潜在竞争条件的操作,编写的代码都不应该抛出异常。比如,在调用可能失败或抛出异常的方法前,您可以检查其前置条件,举例,

If((Not collection Is NothingAndNot collection.IsReadOnly))Then

    collection.Add(additionalNumber)

End If

×一定不要从异常过滤器块内抛出异常。当一个异常过滤器引发了一个异常时,该异常会被CLR捕获,该过滤器返回false。该行为很难与过滤器显式的执行并返回错误区分开,所以会增加调试难度。

' 这是一个很不好的设计

Try

...

Catch e As ArgumentException _

When e.InnerException.Message.StartsWith"File"

...

End Try

×一定不要显式的从finally块内抛出异常。从调用方法内隐式的抛出异常是可以接受的。

1.9.2.    异常处理

×不应该通过捕获笼统的异常,例如System.Exception,System.SystemException,或者.NET代码中其他异常,来隐藏错误。一定要捕获代码能够处理的、明确的异常。您应该捕获更加明确的异常,或者在Catch块的最后一条语句处重新抛出该普通异常。以下情况隐藏错误是可以的,但是其发生几率很低:

 

Good:

Try

    ...

Catch exc As System.NullReferenceException

    ...

Catch exc As System.ArgumentOutOfRangeException

    ...

Catch exc As System.InvalidCastException

    ...

End Try

Bad:

Try

    ...

Catch ex As Exception

    ...

EndTry

一定要在捕获并重新抛出异常时,倾向使用throw 。这是保持异常调用栈的最佳途径:

Good:

Try

    ...' 读文件操作

Catch ex As Exception

    file.Position = position

Throw '抛出异常

End Try

Bad:

Try

...

 Catch ex As Exception

     file.Position = position

Throw ex '再次抛出异常,这样做不好

End Try

 

×不应该使用On Error Resume Next,因为该代码包含一个指令,忽略错误,这可能会导致不可预知的结果,在执行方法中,可能会导致问题,使攻击成为可能,因为执行继续在不寻常的或不受欢迎的情况下。

1.10. 资源清理

×一定不要使用GC.Collect来进行强制垃圾回收。

1.10.1.  Try-finally

一定要使用try-finally 块来清理资源,try-catch 块来处理错误恢复。

×一定不要使用 catch 块来清理资源。一般来说,清理的逻辑是回滚资源(特别是原生资源)分配。举例:

 

Dim stream As FileStream = Nothing

Try

    stream = New FileStream(...)

    ...

Catch ex As Exception

If(stream IsNotNothingThen

        stream.Close()

End If

End Try

为了清理实现了IDisposable 接口的对象,VB.NET 提供了using语句来替代 try-finally 块。

Using stream As New FileStream(...)

    ...

End Using

许多语言特性都会自动的为您写入try-finally 块。例如 C#/VB的using语句, C#的lock语句, VB的SyncLock 语句, C#的foreach语句以及VBFor Each 语句。

1.10.2.  基础Dispose 模式

该模式的基础实现包括实现System.IDisposable 接口,声明实现了所有资源清理逻辑的 Dispose(bool)方法,该方法被Dispose 方法和可选的终结器所共享。请注意,本章节并不讨论如何编写一个终结器。可终结类型是该简单模式的拓展,我们会在下个章节中讨论。如下展示了基础模式的简单实现:

 

Public Class DisposableResourceHolder Implements IDisposable

 

Private disposed AsBoolean = False

Private resource As SafeHandle

 

Public Sub New()

        resource = ...

End Sub

 

Public Sub DoSomething()

If(disposed)Then

Throw New ObjectDisposedException(...)

End If

 

'现在可以调用本地方法来使用资源

        ...

End Sub

 

Public Sub Dispose()Implements IDisposable.Dispose

    DisposeTrue

    GC.SuppressFinalizeMe

End Sub

 

Protected Overridable Sub DisposeByVal disposing AsBoolean

If disposed Then Return

If disposing Then

If(resource IsNotNothingThen

           resource.Dispose()

End If

End If

disposed = True

End Sub

End Class

一定要为包含了可释放类型的类型实现基础Dispose 模式。

一定要拓展基础 Dispose 模式来提供一个终结器。比如,为存储非托管内存的缓冲实现该模式。

您应该为即使类本身不包含非托管代码或可清理对象,但是其子类可能带有的类实现该基础 Dispose 模式。一个绝佳的例子便是System.IO.Stream 类。虽然它是一个不带任何资源的抽象类,大多数子类却带有资源,所以应该为其实现该模式。

一定要声明一个protected virtual void Dispose(bool disposing)方法来集中所有释放非托管资源的逻辑。所有资源清理都应该在该方法中完成。用终结器和 IDisposable.Dispose 方法来调用该方法。如果从终结器内调用,则其参数为 false 。它应该用于确保任何在终结中运行的代码不应该被其他可终结对象访问到。下一章节我们会详细介绍终结器的细节。

Protected Overridable Sub DisposeByVal disposing As Boolean

If disposed Then Return

 

If disposing Then

If(resource IsNotNothingThen

   resource.Dispose()

End If

End If

    disposed = True

End Sub

一定要通过简单的调用Dispose(true),以及GC.SuppressFinalize(this)来实现IDisposable 接口。仅当Dispose(true)成功执行完后才能调用 SuppressFinalize 。

Public Sub Dispose()Implements IDisposable.Dispose

    DisposeTrue

    GC.SuppressFinalizeMe

End Sub

×一定不要将无参Dispose 方法定义为虚函数。 Dispose(bool)方法应该被子类重写。

×您不应该从Dispose(bool)内抛出异常,除非包含它的进程被破坏(内存泄露,不一致的共享状态,等等)等极端条件。用户不希望调用Dispose 会引发异常。如果Dispose 可能引发异常,那么finally块的清理逻辑不会被执行。为了解决这一点,需要将所有对于Dispose (在它们的 finally 块内!)的调用放入try 块内,这将导致一个非常复杂的清理处理程序。如果执行Dispose(bool disposing)方法,即使终结失败也不会抛出异常。如果在一个终结器环境内这样做会终止当前流程。

一定要在对象被终结之后,为任何不能再被使用的成员抛出一个ObjectDisposedException 异常。

Public Class DisposableResourceHolder Implements IDisposable

 

Private disposed As Boolean = False

Private resource As SafeHandle ' 资源句柄

 

Public Sub DoSomething()

If(disposed)Then

Throw New ObjectDisposedException(...)

End If

 

' 现在调用本地方法来使用资源

        ...

End Sub

 

Protected Overridable Sub DisposeByVal disposing As Boolean

' 防止被多次调用

If disposed Then Return

 

' 清除

        ...

        disposed = True

End Sub

 

End Class

1.10.3.  可终结类型

可终结类型是通过重写终结器并在Dispose(bool)中提供终结代码路径来拓展基础Dispose模式的类型。如下代码是一个可终结类型的示例:

 

Public Class DisposableResourceHolder Implements IDisposable

 

Private disposed As Boolean = False

Private buffer As IntPtr ' 非托管的内存缓冲

Private resource As SafeHandle ' 资源句柄

 

Public Sub New()

    buffer = ... ' 分配内存

    resource = ... ' 分配本地资源

End Sub

 

Public Sub DoSomething()

If(disposed)Then

Throw New ObjectDisposedException(...)

End If

 

' 调用本地方法使用资源

        ...

End Sub

 

Protected Overrides Sub Finalize()

    DisposeFalse

MyBase.Finalize()

End Sub

 

Public Sub Dispose()Implements IDisposable.Dispose

        DisposeTrue

        GC.SuppressFinalizeMe

End Sub

 

Protected Overridable Sub DisposeByVal disposing As Boolean

' 防止多次调用

If disposed Then Return

 

If disposing Then

' 清理所有托管资源

If(resource IsNotNothingThen

      resource.Dispose()

End If

End If

 

' 清理所有本地资源

ReleaseBuffer(Buffer)

disposed = True

End Sub

End Class

一定要在类型应该为释放非托管资源负责,且自身没有终结器的情况下,将该类型定义为可终结的。当实现终结器时,简单的调用Dispose(false),并将所有资源清理逻辑放入 Dispose(bool disposing)方法。

 

PublicClass DisposableResourceHolder Implements IDisposable

 

...

Protected Overrides Sub Finalize()

     DisposeFalse

MyBase.Finalize()

End Sub

 

Protected Overridable Sub DisposeByVal disposing As Boolean

...

End Sub

 

EndClass

 

一定要谨慎的定义可终结类型。仔细考虑任何一个您需要终结器的情况。带有终结器的实例从性能和复杂性角度来说,都需付出不小的代价。

一定要为每一个可终结类型实现基础Dispose 模式。该模式的细节请参考先前章节。这给予该类型的使用者以一种显式的方式去清理其拥有的资源。

您应该在终结器即使面临强制的应用程序域卸载或线程中止的情况也必须被执行时,创建并使用临界可终结对象(一个带有包含了CriticalFinalizerObject 的类型层次的类型)。

一定要尽量使用基于 SafeHandle 或SafeHandleZeroOrMinusOneIsInvalid(对于 Win32 资源句柄,其值如果为0或者-1 ,则代表其为无效句柄)的资源封装器,而不是自己来编写终结器。这样,我们便无需终结器,封装器会为其资源清理负责。安全句柄实现了IDisposable 接口,并继承自CriticalFinalizerObject,所以即使面临强制的应用程序域卸载或线程中止,终结器的逻辑也会被执行。

×一定不要在终结器代码路径内访问任何可终结对象,因为这样会有一个极大的风险:它们已经被终结了。例如,一个可终结对象A ,其拥有一个指向另一个可终结对象B的对象,在A的终结器内并不能很安心的使用B,反之亦然。终结器是被随机调用的。但是操作拆箱的值类型字段是可以接受的。同时也请注意,在应用程序域卸载或进程退出的某些时间点上,存储于静态变量的对象会被回收。如果Environment.HasShutdownStarted返回True,则访问引用了一个可终结对象的静态变量(或调用使用了存储于静态变量的值的静态函数)可能会不安全。

×一定不要从终结器的逻辑内抛出异常,除非是系统严重故障。如果从终结器内抛出异常,CLR可能会停止整个进程来阻止其他终结器被执行,并阻止资源以一个受控制的方式释放。

1.10.4.  重写 Dispose

如果您继承了实现IDisposable接口的基类,您必须也实现IDisposable 接口。记得要调用您基类的  Dispose(bool)。

1.11. 交互操作

1.11.1.  COM交互操作

×一定不要以GC.Collect来进行强制垃圾回收以释放有性能要求的API内的COM对象。释放COM对象一个常见的方法是将RCW引用设置为 null,并调用System.GC.Collect 以及System.GC.WaitForPendingFinalizers。如果对性能有较高要求,我们并不推荐这样做,因为这样会引起垃圾回收运行太频繁。如果在服务器应用程序代码中使用该方法则极大的降低了性能和可拓展性。您应该让垃圾回收器自己决定何时执行垃圾回收。

您应该使用 Marshal.FinalReleaseComObject或者 Marshal.ReleaseComObject来手动管理RCW的生存周期。相较与GC.Collect 来进行强制垃圾回收,这样做具有较高的效率。

×一定不要进行跨套间调用。当您从托管应用程序调用一个COM对象时,请确保托管代码的套间类型和COM对象套间类型是相匹配的。通过使用匹配的套间,您可以避免跨套间调用时的线程切换。

1.12. 程序设计约定

一定要同一个程序在一台机器同时只能运行一次,既不能同时运行同一个程序的多个副本。即If App.PrevInstance = True Then End

一定要同一个登录用户同时只能登录同一个系统一次。

应该使用Long型的变量来代替Integer类型。这样做可以减少一些数据溢出的错误,而且,在Win32位平台上,CPU处理32位的数据比16位的数据速度更快。

×一定不要不要将类型转换的工作交给VB自动去做,而使用以下类型转换函数。CBool(expression)、CByte(expression)、CCur(expression)、CDate(expression)、CDbl(expression) 、CDec(expression、CInt(expression) 、CLng(expression)、CSng(expression)、CStr(expression) 、CVar(expression)、Val(expression)。

1.13.     微软禁用的函数

×不应该使用CopyMemory函数,这是微软安全开发生命周期禁用的函数。如果可能,尽量不要使用。使用memcpy_s来代替CopyMemory,来确保源缓冲区足够保存拷贝的数据。

×不应该使用RtlCopyMemory函数,这是微软安全开发生命周期禁用的函数。如果可能,尽量不要使用。使用memcpy_s来代替RtlCopyMemory,来确保源缓冲区足够保存拷贝的数据。

1.14.     用户环境变量函数

×不应该使用GetTempPath函数,这个函数是从用户环境变量中获取路径,并且可以提供一个你所希望的不同路径。当这个函数返回值是其他程序可能会改写或读取这个值,所以作答DLL或输入的文件,在这个位置上都存在潜在被破坏的可能。如果一定要使用,请做好权限限制。

1.15.     输入验证

×不应该使用<%@ Page validateRequest="false" %>,因为应用程序禁止了请求验证,这可能导致恶意攻击,例如跨站脚本XSS攻击。如果禁止了,那么建议应用程序显式检查所有输入。

应该在使用LoadXml函数之前做好检查,这个函数存在潜在的XXE(外部实体攻击) Dos攻击。

1.16.     不安全的加密

×不应该设置X509CertificateValidationMode.None,因为代码使用了不安全的证书验证模式。

×一定不要使用Xor进行加密,因为应用程序使用异或进行加密,这基本没有什么用处,很容易进行反转。

×不应该使用SHA1加密,因为此加密算法不再认为是安全的,需要一个潜代的,更安全的算法。例如:sha2

×不要使用CipherMode.ECB进行加密,因为使用电子密码本加密模式,它不能隐藏模式,所以不应加密带有模式的数据。例如:英语,以及大多数的数据文件。

×不要再使用MD5加密,因为此算法不再认是安全的,需要使用一个更安全的加密算法代替。

1.17.     测试函数

一定要删除Helloworld之类的测试代码,代码中包含测试功能函数,可能会被攻击者利用。进行人工检查,确实哪些是可执行的,然后删除。