wireshark 是如何避免段错误的呢?
下列章节选自 README.developer 开发者手册的第 3 点。
前言
翻译完本小结,总结的很好,但是缺失简单的例子,这需要在阅读源码时留意本小结对 wireshark 源码的指导实践,。
3. Robustness.
3. 健壮性
Wireshark is not guaranteed to read only network traces that contain correctly-
formed packets. Wireshark is commonly used to track down networking
problems, and the problems might be due to a buggy protocol implementation
sending out bad packets.
Wireshark不能保证只读取包含正确格式数据包的网络跟踪文件。Wireshark通常用于跟踪网络问题,而这些问题可能是由于有错误的协议实现发送了错误的数据包导致的。
Therefore, code does not only have to be able to handle
correctly-formed packets without, for example, crashing or looping
infinitely, they also have to be able to handle *incorrectly*-formed
packets without crashing or looping infinitely.
因此,代码不仅需要能够处理正确格式的数据包,比如避免崩溃或无限循环,还需要能够处理不正确格式的数据包,同样也要避免崩溃或无限循环。
//即 wireshark 在处理错误格式的数据包时如何避免段错误和死循环呢?
Here are some suggestions for making code more robust in the face
of incorrectly-formed packets:
以下是一些使代码在面对格式不正确的数据包时更加健壮的建议:
Do *NOT* use "ws_assert()" or "ws_assert_not_reached()" with input data in dissectors.
*NO* value in a packet's data should be considered "wrong" in the sense
that it's a problem with the dissector if found; if it cannot do
anything else with a particular value from a packet's data, the
dissector should put into the protocol tree an indication that the
value is invalid, and should return. The "expert" mechanism should be
used for that purpose.
在解剖器中不要使用“ws_assert()”或“ws_assert_not_reached()”来处理输入数据。 不要认为数据包中的任何值都是“错误”的(译者注:因为所有可能的值都应该被视为有效的输入),如果发现一个值无法使用,解剖器应该在协议树中放置一个指示,表明该值是无效的,并返回。为此,应该使用“expert”机制,而不是使用断言。
//"NO" 表示“没有一个值”,意思是“没有一个数据包的数据值应该被视为“错误”(即有问题的)”,因为所有可能的值都应该被视为有效的输入。
Use assertions to catch logic errors in your program. A failed assertion
indicates a bug in the code. Use ws_assert() instead of g_assert() to
test a logic condition. Note that ws_assert() will be removed with
WS_DISABLE_ASSERT. Therefore assertions should not have any side-effects,
otherwise the program may behave inconsistently.
使用断言来捕获程序中的逻辑错误。断言失败表明代码中存在错误。使用ws_assert()来测试逻辑条件,而不是使用g_assert()。请注意,ws_assert()将被WS_DISABLE_ASSERT移除。因此,断言不应具有任何副作用,否则程序可能表现不一致。
//在代码中搜索, ws_assert() 断言用在非 packet-*.c 的代码文件中,这里的解剖器指的是packet-*.c 文件。
Use ws_assert_not_reached() instead of g_assert_not_reached() for
unreachable error conditions. For example if (and only if) you know
'myvar' can only have the values 1 and 2 do:
对于无法到达的错误情况,使用 ws_assert_not_reached() 而不是 g_assert_not_reached() 。 例如,如果(且仅当)您知道“myvar”只能具有值 1 和 2 时:
switch(myvar) {
case 1:
(...)
break;
case 2:
(...)
break;
default:
ws_assert_not_reached();
break;
}
For dissectors use DISSECTOR_ASSERT() and DISSECTOR_ASSERT_NOT_REACHED()
instead, with the same caveats as above.
对于解剖器,请改用 DISSECTOR_ASSERT() 和 DISSECTOR_ASSERT_NOT_REACHED(),注意事项与上述相同。//此处的及解析器指的是什么?
You should continue to use g_assert_true(), g_assert_cmpstr(), etc for
"test code", such as unit testing. These assertions are always active.
See the GLib Testing API documentation for the details on each of those
functions.
您应该继续将 g_assert_true()、g_assert_cmpstr() 等用于“测试代码”,例如单元测试。 这些断言总是处于激活状态。
有关这些函数的详细信息,请参阅 GLib Testing API 文档。
If there is a case where you are checking not for an invalid data item
in the packet, but for a bug in the dissector (for example, an
assumption being made at a particular point in the code about the
internal state of the dissector), use the DISSECTOR_ASSERT macro for
that purpose; this will put into the protocol tree an indication that
the dissector has a bug in it, and will not crash the application.
如果有这样的情况,你不是在检查数据包中的无效数据项,而是在检查解析器中的错误(例如,在代码的特定点对解析器内部状态做出了某种假设),则应使用DISSECTOR_ASSERT宏来进行检查;这将在协议树中插入一个指示解析器存在错误的标记,并且不会导致应用程序崩溃。
If you are allocating a chunk of memory to contain data from a packet,
or to contain information derived from data in a packet, and the size of
the chunk of memory is derived from a size field in the packet, make
sure all the data is present in the packet before allocating the buffer.
Doing so means that:
如果你正在分配一个用于包含数据包中的数据或从数据包中导出的信息的内存块,并且内存块的大小是从数据包中的大小(Size/Length)字段中导出的,请确保在分配缓冲区之前所有数据都存在于数据包中。这样做意味着:
1) Wireshark won't leak that chunk of memory if an attempt to
fetch data not present in the packet throws an exception.
1)如果尝试获取数据包中不存在的数据导致异常抛出,Wireshark不会泄漏该内存块。
and
2) it won't crash trying to allocate an absurdly-large chunk of
memory if the size field has a bogus large value.
2)如果大小(Size/Length)字段有一个错误的大值,尝试分配一个非常大的内存块时,它不会崩溃。
If you're fetching into such a chunk of memory a string from the buffer,
and the string has a specified size, you can use "tvb_get_*_string()",
which will check whether the entire string is present before allocating
a buffer for the string, and will also put a trailing '\0' at the end of
the buffer.
如果你正在从缓冲区中获取一个指定大小的字符串到这样的内存块中,你可以使用"tvb_get_*_string()"函数。它将检查整个字符串是否存在于缓冲区中,然后再为字符串分配缓冲区,并在缓冲区的末尾放置一个尾随的'\0'。
If you're fetching into such a chunk of memory a 2-byte Unicode string
from the buffer, and the string has a specified size, you can use
"tvb_get_faked_unicode()", which will check whether the entire string
is present before allocating a buffer for the string, and will also
put a trailing '\0' at the end of the buffer. The resulting string will be
a sequence of single-byte characters; the only Unicode characters that
will be handled correctly are those in the ASCII range. (Wireshark's
ability to handle non-ASCII strings is limited; it needs to be
improved.)
如果你正在从缓冲区中获取一个指定大小的 2 字节 Unicode 字符串到这样的内存块中,你可以使用 "tvb_get_faked_unicode()" 函数。它将检查整个字符串是否存在于缓冲区中,然后再为字符串分配缓冲区,并在缓冲区的末尾放置一个尾随的 '\0'。生成的字符串将是一系列单字节字符;Wireshark 能够正确处理的 Unicode 字符仅限于 ASCII 范围内的字符。(Wireshark 处理非 ASCII字符串的能力有限,需要进一步改进。)
If you're fetching into such a chunk of memory a sequence of bytes from
the buffer, and the sequence has a specified size, you can use
"tvb_memdup()", which will check whether the entire sequence is present
before allocating a buffer for it.
如果你正在从缓冲区中获取一个指定大小的字节序列到这样的内存块中,你可以使用"tvb_memdup()"函数。它将检查整个序列是否存在于缓冲区中,然后再为序列分配缓冲区。
Otherwise, you can check whether the data is present by using
"tvb_ensure_bytes_exist()" although this frequently is not needed: the
TVB-accessor routines can handle requests to read data beyond the end of
the TVB (by throwing an exception which will either mark the frame as
truncated--not all the data was captured--or as malformed).
否则,你可以使用"tvb_ensure_bytes_exist()"函数来检查数据是否存在,尽管通常不需要这样做:TVB访问器例程可以处理读取超出TVB结尾的数据请求(通过抛出异常,该异常将标记帧为被截断——未捕获所有数据——或不正常)。
Note also that you should only fetch string data into a fixed-length
buffer if the code ensures that no more bytes than will fit into the
buffer are fetched ("the protocol ensures" isn't good enough, as
protocol specifications can't ensure only packets that conform to the
specification will be transmitted or that only packets for the protocol
in question will be interpreted as packets for that protocol by
Wireshark). If there's no maximum length of string data to be fetched,
routines such as "tvb_get_*_string()" are safer, as they allocate a buffer
large enough to hold the string. (Note that some variants of this call
require you to free the string once you're finished with it.)
还要注意的是,只有在代码确保获取的字节数不超过缓冲区可容纳的字节数时,才应将字符串数据获取到固定长度的缓冲区中。仅仅依靠协议规范是不够的,因为协议规范不能保证只有符合规范的数据包会被传输,或者只有针对该协议的数据包会被Wireshark解释为该协议的数据包。如果没有字符串数据的最大长度限制,则使用"tvb_get_*_string()"等例程更安全,因为它们会分配一个足够大的缓冲区来容纳字符串。(请注意,这些调用的某些变体要求你在使用完字符串后释放它。)
If you have gotten a pointer using "tvb_get_ptr()" (which you should not
have: you should seriously consider a better alternative to this function),
you must make sure that you do not refer to any data past the length passed
as the last argument to "tvb_get_ptr()"; while the various "tvb_get"
routines perform bounds checking and throw an exception if you refer to data
not available in the tvbuff, direct references through a pointer gotten from
"tvb_get_ptr()" do not do any bounds checking.
如果您使用"tvb_get_ptr()"获取了一个指针(您不应该这样做,应该认真考虑使用更好的替代函数),那么您必须确保不要引用超出传递给"tvb_get_ptr()"作为最后一个参数的长度的任何数据;虽然各种"tvb_get"例程执行边界检查,并在引用不可用于tvbuff的数据时抛出异常,但通过从"tvb_get_ptr()"获取的指针进行的直接引用不进行任何边界检查。
//这些函数是如何做边界检查的呢?
If you have a loop that dissects a sequence of items, each of which has
a length field, with the offset in the tvbuff advanced by the length of
the item, then, if the length field is the total length of the item, and
thus can be zero, you *MUST* check for a zero-length item and abort the
loop if you see one. Otherwise, a zero-length item could cause the
dissector to loop infinitely. You should also check that the offset,
after having the length added to it, is greater than the offset before
the length was added to it, if the length field is greater than 24 bits
long, so that, if the length value is *very* large and adding it to the
offset causes an overflow, that overflow is detected.
如果您有一个循环来分析一系列具有长度字段的项目,且在 tvbuff 中的偏移量按项目的长度前进,那么,如果长度字段是项目的总长度,因此可以为零,您必须检查是否有零长度的项目,并在看到一个时中止循环。否则,零长度的项目可能会导致解析器无限循环。如果长度字段大于24位,则在将长度添加到偏移量之前,还应检查添加后的偏移量是否大于添加长度之前的偏移量,以便在长度值非常大且将其添加到偏移量会导致溢出时检测到该溢出。
If you have a
for (i = {start}; i < {end}; i++)
loop, make sure that the type of the loop index variable is large enough
to hold the maximum {end} value plus 1; otherwise, the loop index
variable can overflow before it ever reaches its maximum value. In
particular, be very careful when using gint8, guint8, gint16, or guint16
variables as loop indices; you almost always want to use an "int"/"gint"
or "unsigned int"/"guint" as the loop index rather than a shorter type.
如果你使用如下形式的for循环:
for (i = {start}; i < {end}; i++)
请确保循环索引变量的类型足够大,能够存储最大的{end}值加1;否则,在达到最大值之前,循环索引变量可能会溢出。特别是,在使用gint8、guint8、gint16或guint16变量作为循环索引时要非常小心;通常情况下,你应该使用“int” / “gint”或“unsigned int” / “guint”作为循环索引,而不是使用较短的类型。
If you are fetching a length field from the buffer, corresponding to the
length of a portion of the packet, and subtracting from that length a
value corresponding to the length of, for example, a header in the
packet portion in question, *ALWAYS* check that the value of the length
field is greater than or equal to the length you're subtracting from it,
and report an error in the packet and stop dissecting the packet if it's
less than the length you're subtracting from it. Otherwise, the
resulting length value will be negative, which will either cause errors
in the dissector or routines called by the dissector, or, if the value
is interpreted as an unsigned integer, will cause the value to be
interpreted as a very large positive value.
如果您正在从缓冲区中提取长度字段,该字段对应于数据包的某个部分的长度,并从该长度中减去某个值,该值对应于数据包部分中的头的长度,那么请始终检查该长度字段的值是否大于或等于您要从中减去的长度,并在其小于您要从中减去的长度时报告数据包错误并停止解析数据包。否则,将导致所得到的长度值为负数,这将导致解析器或解析器调用的例程中出现错误,或者如果该值被解释为无符号整数,则会导致该值被解释为非常大的正值。
Any tvbuff offset that is added to as processing is done on a packet
should be stored in a 32-bit variable, such as an "int"; if you store it
in an 8-bit or 16-bit variable, you run the risk of the variable
overflowing.
任何在分析数据包时被增加的 tvbuff 偏移量,都应该存储在一个 32 位变量中,例如 "int" 类型。如果将其存储在 8 位或 16 位变量中,可能会发生变量溢出的风险。
sprintf() -> snprintf()
Prevent yourself from using the sprintf() function, as it does not test the
length of the given output buffer and might be writing into unintended memory
areas. This function is one of the main causes of security problems like buffer
exploits and many other bugs that are very hard to find. It's much better to
use the snprintf() function declared by <stdio.h> instead.
sprintf() -> snprintf() 不要使用sprintf()函数,因为它不会检测给定输出缓冲区的长度,可能会写入到意想不到的内存区域。这个函数是安全问题(如缓冲区溢出)和许多其他难以找到的错误的主要原因之一。最好使用< stdio.h>声明的snprintf()函数。
You should test your dissector against incorrectly-formed packets. This
can be done using the randpkt and editcap utilities that come with the
Wireshark distribution. Testing using randpkt can be done by generating
output at the same layer as your protocol, and forcing Wireshark/TShark
to decode it as your protocol, e.g. if your protocol sits on top of UDP:
您应该针对格式不正确的数据包测试您的解析器。这可以使用随附在 Wireshark 分发中的 randpkt和 editcap 实用程序来完成。使用 randpkt 进行测试可以通过在与您的协议相同的层级生成输出,并强制 Wireshark / TShark 将其解码为您的协议来完成,例如,如果您的协议位于UDP之上:
randpkt -c 50000 -t dns randpkt.pcap
tshark -nVr randpkt.pcap -d udp.port==53,<myproto>
Testing using editcap can be done using preexisting capture files and the
"-E" flag, which introduces errors in a capture file. E.g.:
使用editcap进行测试可以使用现有的抓包文件和“-E”标志,该标志会在捕获文件中引入错误。例如:
//牛哇
editcap -E 0.03 infile.pcap outfile.pcap
tshark -nVr outfile.pcap
//解释一下这个命令;这将使用“badchecksum”错误修改“infile.cap”文件,并将结果写入“outfile.cap”。此错误会更改捕获文件中的一个或多个校验和,导致Wireshark / TShark不会将它们视为正确的协议数据。您可以使用其他错误标志来模拟其他类型的错误,例如“trunc”,该错误会将捕获文件截断到指定的大小。
The script fuzz-test.sh is available to help automate these tests.
可使用fuzz-test.sh脚本来自动化执行这些测试。
//解释一下:脚本 fuzz-test.sh 是一种工具,用于针对大量随机生成和格式错误的数据包捕获自动测试 Wireshark/TShark 解析器。 它使用 randpkt 和 editcap 实用程序生成测试用例,并使用 diff 工具将解析器的输出与预期输出进行比较。 通过运行此脚本,您可以快速识别解析器的错误和问题,并在发布代码之前修复它们。
完~