Gaurav Kamathe 发布的文章

会议是科技行业的福利之一。以下是如何充分利用你所参加的会议的方法。

我在 2023 年 2 月参加了两个针对开源软件的技术会议。我是比利时根特举办的 Config Management Camp 的演讲者,也是比利时布鲁塞尔 FOSDEM 的与会者。本文旨在着重讲述我在会议上的经验,并为你提供一些如何充分利用这种机会的建议。

保持目的性

不同的人参加会议有不同的原因。有些人是某个主题、知识或兴趣领域的演讲者。另外一些人是希望从这些讲座中获得知识并与其他志同道合的人建立联系的与会者。也有一些代表他们公司的与会者。你很可能属于其中的一类。明确你希望从会议中获得什么,这是进行一次成功会议访问的第一步。如果你是一个演讲者,这意味着你要熟练掌握你所演讲的东西。如果你是一个与会者,你应该对你想从会议中获得什么有一个认识。

了解场地和会议日程

FOSDEM 是一个非常大的会议,在两天的时间里至少有六千人参加。对于一个参加这样一个大型的会议的与会者来说,由于许多演讲会在同一时间举行,无法参加所有你感兴趣的会议并不奇怪。通常,这样的大型会议在大学或会议中心等宽敞的场地举行。由于面积非常大,会议会根据特定的主题分散在整个会场。这些会议会有一个固定的日程,所以有时你可能必须迅速地从场地的一边跑到另一边。场馆地图可以在场馆网站上轻松获取。在第一天早点到达会场并熟悉它是非常有用的。这有助于节省你在一个演讲结束时匆忙赶往另一个演讲的时间。

记些笔记

在现场集中注意力和专注于演讲当然很好。然而,你的头脑只能记住这么多。当然,有些人会试着尽可能地利用他们的手机拍摄正在演示的幻灯片(顺着演讲者的演讲节奏)。如果你想在社交媒体上快速更新你正在参加的会议信息,这是很好的。但是它不是很有效的笔记。通常幻灯片上的有效信息是最少的。但是如果演讲者在台上深入地解释了一些东西,你可能会错过这些解释。我建议你随身携带一个记事本和一支笔。你甚至可以带上笔记本电脑做笔记。这个做法的目的是在于通过在演讲中对有趣的花絮做一个简短的笔记,以便你可以在以后重温它们。而且它让你总是能在演讲快结束时回想起要向演讲者提问的问题。

建立关系和促成协作

会议可能是与志同道合的人彼此交往的最佳场所。他们和你一样对同样的话题感兴趣。最好利用这段时间来了解在感兴趣的话题上正在发生什么,看看人们如何解决令人感兴趣的问题,他们如何处理事情,并掌握整个行业的脉搏。你在会议上的时间有限,所以一定要把你介绍给那些从事与你有关的工作的人。这是一个收集信息的好机会,以便以后与他们沟通。你可以通过电子邮件、Mastodon、领英等方式交换个人信息。

腾出时间看展位和礼品

大多数技术会议都有来自各种公司或上游项目的展位,希望推销他们的产品和服务。为了吸引更多参展的人,展位上经常会有各种各样的免费的礼品(在大多数情况下)。这些礼品通常是贴纸、凉水瓶、有趣的小玩意、毛绒玩具、笔之类。一定要把它们收集起来,这样你就有东西送给你的同事和朋友了。参观展位不应该只是为了礼品。你应该利用这个机会与来自不同公司的人交谈(即使他们是竞争对手),以了解他们能提供什么。谁没准你就会得到以后的项目知识!

放松

参加会议不应该只是为了工作。这也让你你从平时忙碌的日程中休息一下,放松一下。很有可能你正在前往一个你还没有访问过的不同的国家或城市。会议、会谈和技术讨论都很重要。但这只是整个体验的一部分。另一半的经验是旅行,它可以使人了解另一个国家、它的文化、它的人民、食物、语言和不同的生活方式。退一步讲,享受所有这些经历,留下终生的回忆。我建议你在住宿的地方找一些著名的地标兼做。你也应该尝试当地的美食,或者你可以和当地人聊天。最后,你会发现你认为从来没有存在过的自己的另一面。

写下你的经历

一旦你从会议回来,不要只是忘记它然后回到你的日常生活,就好像什么都没发生一样。利用这个机会写下你的经历,并分享你认为最好的演讲以及原因。会议和旅行的主要收获是什么?你应该记录你学到的东西。你应该主动联系你在会议上遇到的人。你也可以在社交媒体上关注你可能错过的事情。

总结

会议是科技行业的福利之一。我建议每个人在职业生涯中的某个时候都去一次。我希望这篇文章能帮助你了解如何在参加技术会议时最大限度地利用会议。

(题图:MJ/conference illustration talk meetup in high resolution, very detailed, 8k)


via: https://opensource.com/article/23/4/tips-tech-conference

作者:Gaurav Kamathe 选题:lkxed 译者:Taivas Jumala 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Rustlings 是由 Rust 团队维护的开源项目,旨在帮助你通过调试代码的方式来学习 Rust。

Ferris the crab under the sea, unofficial logo for Rust programming language

在我上一篇 关于 Rustup 的文章 中,我向你们展示了如何安装 Rust 工具链。但是,如果不能上手操作一下 Rust 的话下载工具链又有什么用?学习任何语言都包括阅读现有的代码和写很多的示例程序,这是精通一门语言的好方法。然而,我们还可以走第三条路:调试代码。

通过调试来学习牵扯到尝试去编译一个已经写好的(满是漏洞的)示例程序,理解编译器生成的错误信息,修复示例代码,然后再重新编译。重复这个过程直到代码能够成功被编译并运行。

Rustlings 是一个由 Rust 团队维护的开源项目,旨在帮助你通过调试代码来学习 Rust。它也会一路为你提供提示。如果你是一名 Rust 初学者,并且刚开始阅读或已经读完了 Rust 书籍,那么 Rustlings 就是理想的下一步。Rustllings 帮助你将运用书中所学,并转向开发更大的项目。

安装 Rustlings

我使用(并推荐)Fedora 电脑来体验 Rustlings,但是任何 Linux 发行版都可以。要安装 Rustlings,你必须下载并运行它的安装脚本。通常建议你以不具备任何特别权限的普通用户(非 root 用户)来运行脚本。

记住,你需要 Rust 工具链来使用 Rustlings。如果你还没有这些工具链,请参考我 关于 Rustup 的文章

当你准备好时,下载这个安装脚本:

$ curl -L https://raw.githubusercontent.com/rust-lang/rustlings/main/install.sh  > rustlings_install.sh
$ file rustlings_install.sh
rustlings_install.sh: Bourne-Again shell script, ASCII text executable

阅读脚本以了解它会做什么:

$ less rustlings_install.sh

然后运行安装:

$ bash rustlings_install.sh
[...]
Installing /home/tux/.cargo/bin/rustlings
Installed package `rustlings v4.8.0 (/home/tux/rustlings)` (executable `rustlings`)
All done!

运行 rustlings 以开始。

Rustlings 练习

你现在可以使用命令 rustlings。与标志 --help 一起执行来查看可选的选项。

$ rustlings --help

这个安装脚本也克隆了 Rustlings 的 Git 仓库,并安装了运行示例程序所需的依赖。你可以在 ruslings 下的 exercises 目录查阅这些示例程序。

$ cd rustlings
$ pwd
/home/tux/rustlings
$ ls
AUTHORS.md  Cargo.toml        CONTRIBUTING.md  info.toml install.sh README.md  target Cargo.lock  CHANGELOG.md  exercises install.ps1  LICENSE src tests
$ ls -m exercises/
advanced_errors, clippy, collections, conversions, enums, error_handling, functions, generics, if, intro, macros, mod.rs, 
modules, move_semantics, option, primitive_types, quiz1.rs, quiz2.rs, quiz3.rs, quiz4.rs, README.md, 
standard_library_types, strings, structs, tests, threads, traits, variables

从命令行列出所有练习

命令 ruslings 提供给你一个 list 命令用以展示每个示例程序,它的完整路径,以及状态 (默认为 “待定”)。

$ rustlings list
Name         Path                                 Status
intro1       exercises/intro/intro1.rs            Pending
intro2       exercises/intro/intro2.rs            Pending
variables1   exercises/variables/variables1.rs    Pending
variables2   exercises/variables/variables2.rs    Pending
variables3   exercises/variables/variables3.rs    Pending
[...]

在显示结尾处,你会有一个进度报告用来追踪进度。

Progress: You completed 0 / 84 exercises (0.00 %).

查看示例程序

命令 rustlings list 向你展示了现有的程序,所以你可以在任何时候查看这些程序的代码,你只需要将完整路径复制到你的终端作为命令 cat 或者 less 的参数:

$ cat exercises/intro/intro1.rs

验证你的程序

现在你可以开始调试程序了。你可以使用命令 verify 来做这件事。注意 Rustlings 选择了列表里的第一个程序(intro1.rs)并尝试去编译它,最后编译成功:

$ rustlings verify
Progress: [-----------------------------------] 0/84
✅ Successfully ran exercises/intro/intro1.rs!

You can keep working on this exercise,
or jump into the next one by removing the `I AM NOT DONE` comment:

 6 |  // Execute the command `rustlings hint intro1` for a hint.
 7 |  
 8 |  // I AM NOT DONE
 9 |

正如你从结果中所见,尽管示例代码成功编译了,你依然需要做一些工作。每个示例程序的源文件中都带有以下注释:

$ grep "NOT DONE" exercises/intro/intro1.rs
// I AM NOT DONE

虽然第一个程序的编译没有问题,除非你去掉注释 I AM NOT DONE,Rustlings 不会移到下一个程序。

来到下一个练习

一旦你从 intro1.rs 中去掉这些注释,你就可以通过再一次运行命令 rustlings verify 来到下一个练习。这一次,你会发现 Rustlings 尝试去编译这个系列中的下一个程序(intro2.rs),但是遇到了一个错误。你应该调试并修复这个问题,并前进。这是你理解为什么 Rust 说程序有漏洞的至关重要的一步。

$ rustlings verify
Progress: [>------------------------] 1/84
⚠️  Compiling of exercises/intro/intro2.rs failed! Please try again. Here's the output:
error: 1 positional argument in format string, but no arguments were given
 --> exercises/intro/intro2.rs:8:21
  |
8 |         println!("Hello {}!");
  |                         ^^

error: aborting due to previous error

来点提示

Rustlings 有一个非常好用的 hint 参数,这个参数会告诉你示例程序中哪里出错了,以及如何去修复它。你可以认为这是在编译错误信息基础之上,一个额外的帮助选项。

$ rustlings hint intro2
Add an argument after the format string.

基于以上提示,修复这个程序就很简单了。你只需要在语句 println 中加一个额外的参数。这个 diff 对比应该能帮你理解发生的变化:

< println!("Hello {}!", "world");
---
> println!("Hello {}!");

一旦你做出了修改,并从源代码中去掉了注释 NOT DONE,你可以再一次运行 rustlings verify 来编译并运行代码。

$ rustlings verify
Progress: [>-------------------------------------] 1/84
✅ Successfully ran exercises/intro/intro2.rs!

追踪进度

你无法在一天之内做完所有的练习,忘记练到哪也很常见。你可以执行命令 list 来查看你的练习状态。

$ rustlings list
Name         Path                                  Status
intro1       exercises/intro/intro1.rs             Done   
intro2       exercises/intro/intro2.rs             Done   
variables1   exercises/variables/variables1.rs     Pending
variables2   exercises/variables/variables2.rs     Pending
variables3   exercises/variables/variables3.rs     Pending
[...]

运行特定的练习

如果你不想从头开始并且想要跳过一些练习,Rustlings 允许你使用命令 rustlings run 来专注特定的练习。如此可以运行指定的程序而不需要验证之前的课程。例如:

$ rustlings run intro2
Hello world!
✅ Successfully ran exercises/intro/intro2.rs
$ rustlings run variables1

敲入练习名字可能会变得乏味,但 Rustlings 为你准备了便利的命令 next 用来移向系列中的下一个练习。

$ rustlings run next

替代命令 watch

如果你不想在每次修改后还要敲一次 verify,你可以在终端窗口中运行命令 watch,然后再继续修改源代码以解决问题。命令 watch 会检测到这些修改,然后重新编译以查看这些问题是否被解决。

$ rustlings watch

通过调试学习

Rust 编译器以提供非常有意义的错误信息而被熟知,这些错误信息会帮助你理解在你代码中的问题。这通常意味着更快的调试。Rustlings 是练习 Rust,学会阅读错误信息,并理解 Rust 语言的优秀途径。来看看 GitHub 上 Rustlings 5.0.0 的最新功能吧。

下载 Rust 速查表

via: https://opensource.com/article/22/7/learn-rust-rustlings

作者:Gaurav Kamathe 选题:lkxed 译者:yzuowei 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

这篇文章能让你了解如何使用 Checksec ,来识别一个可执行文件的安全属性,了解安全属性的含义,并知道如何使用它们。

编译源代码会生成一个二进制文件(LCTT 译注:即 .o 文件)。在编译期间,你可以向 gcc 编译器提供 标志 flags ,以启用或禁用二进制文件的某些属性,这些属性与安全性相关。

Checksec 是一个漂亮的小工具,同时它也是一个 shell 脚本。Checksec 可以识别编译时构建到二进制文件中的安全属性。编译器可能会默认启用一些安全属性,你也可以提供特定的标志,来启用其他的安全属性。

本文将介绍如何使用 Checksec ,来识别二进制文件的安全属性,包括:

  1. Checksec 在查找有关安全属性的信息时,使用了什么底层的命令
  2. 在将源代码编译成二进制文件时,如何使用 GNU 编译器套件 GNU Compiler Collection (即 GCC)来启用安全属性

安装 checksec

要在 Fedora 和其他基于 RPM 的 Linux 系统上,安装 Checksec,请使用以下命令:

$ sudo dnf install checksec

对于基于 Debian 的 Linux 发行版,使用对应的 apt 命令,来安装 Checksec。

$ sudo apt install checksec

shell 脚本

在安装完 Checksec 后,能够发现 Checksec 是一个单文件的 shell 脚本,它位于 /usr/bin/checksec,并且这个文件挺大的。Checksec 的一个优点是你可以通过快速通读这个 shell 脚本,从而了解 Checksec 的执行原理、明白所有能查找有关二进制文件或可执行文件的安全属性的系统命令

$ file /usr/bin/checksec
/usr/bin/checksec: Bourne-Again shell script, ASCII text executable, with very long lines

$ wc -l /usr/bin/checksec
2111 /usr/bin/checksec

以下的命令展示了如何对你每天都会使用的:ls 命令的二进制文件运行 Checksec。Checksec 命令的格式是:checksec --file=,后面再跟上二进制文件的绝对路径:

$ checksec --file=/usr/bin/ls
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Full RELRO      Canary found      NX enabled    PIE enabled     No RPATH   No RUNPATH   No Symbols        Yes   5       17              /usr/bin/ls

当你在终端中对某个二进制文件运行 Checksec 时,你会看到安全属性有颜色上的区分,显示什么是好的安全属性(绿色),什么可能不是好的安全属性(红色)。我在这里说 “可能” 是因为即使有些安全属性是红色的,也不一定意味着这个二进制文件很糟糕,它可能只是表明发行版供应商在编译二进制文件时做了一些权衡,从而舍弃了部分安全属性。

Checksec 输出的第一行提供了二进制文件的各种安全属性,例如 RELROSTACK CANARYNX 等(我将在后文进行详细解释)。第二行打印出给定二进制文件(本例中为 ls)在这些安全属性的状态(例如,NX enabled 表示为堆栈中的数据没有执行权限)。

示例二进制文件

在本文中,我将使用以下的 “hello world” 程序作为示例二进制文件。

#include <stdio.h>

int main()
{
        printf("Hello World\n");
        return 0;
}
 

请注意,在编译源文件 hello.c 的时候,我没有给 gcc 提供任何额外的标志:

$ gcc hello.c -o hello
 
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped

$ ./hello
Hello World

使用 Checksec 运行二进制文件 hello,打印的某些安全属性的状态,与上面的 ls 二进制文件的结果不同(在你的屏幕上,某些属性可能显示为红色):

$ checksec --file=./hello
RELRO           STACK CANARY      NX            PIE             RPATH      RUNPATH      Symbols         FORTIFY Fortified       Fortifiable     FILE
Partial RELRO   No canary found   NX enabled    No PIE          No RPATH   No RUNPATH   85) Symbols       No    0       0./hello
$

(LCTT 译注:在我的 Ubuntu 22.04 虚拟机,使用 11.3.0 版本的 gcc,结果与上述不太相同,利用默认参数进行编译,会得到 RELRO、PIE、NX 保护是全开的情况。)

更改 Checksec 的输出格式

Checksec 允许自定义各种输出格式,你可以使用 --output 来自定义输出格式。我将选择的输出格式是 JSON 格式,并将输出结果通过管道传输到 jq 实用程序,来得到漂亮的打印。

接下来,确保你已安装好了 jq,因为本教程会使用 jq 从 Checksec 的输出结果中,用 grep 来快速得到某一特定的安全属性状态,并报告该安全属性是否启动(启动为 yes,未启动为 no):

$ checksec --file=./hello --output=json | jq
{
  "hello": {
    "relro": "partial",
    "canary": "no",
    "nx": "yes",
    "pie": "no",
    "rpath": "no",
    "runpath": "no",
    "symbols": "yes",
    "fortify_source": "no",
    "fortified": "0",
    "fortify-able": "0"
  }
}

看一看所有的安全属性

上面的二进制文件 hello 包括几个安全属性。我将该二进制文件与 ls 的二进制文件进行比较,以检查启用的安全属性有何不同,并解释 Checksec 是如何找到此信息。

1、符号(Symbol)

我先从简单的讲起。在编译期间,某些 符号 symbols 包含在二进制文件中,这些符号主要用作于调试。开发软件时,需要用到这些符号,来调试和修复错误。

这些符号通常会从供用户普遍使用的最终二进制文件中删除。删除这些符号不会影响到二进制文件的执行。删除符号通常是为了节省空间,因为一旦符号被删除了,二进制文件就会稍微小一些。在闭源或专有软件中,符号通常都会被删除,因为把这些符号放在二进制文件中,可以很容易地推断出软件的内部工作原理。

根据 Checksec 的结果,在二进制文件 hello 中有符号,但在 ls 的二进制文件中不会有符号。同样地,你还可以用 file 命令,来找到符号的信息,在二进制文件 hello 的输出结果的最后,看到 not stripped,表明二进制文件 hello 有符号:

$ checksec --file=/bin/ls --output=json | jq | grep symbols
    "symbols": "no",

$ checksec --file=./hello --output=json | jq | grep symbols
    "symbols": "yes",

$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped

Checksec 是如何找到符号的信息呢?Checksec 提供了一个方便的 --debug 选项,来显示运行了哪些函数。因此,运行以下的命令,会显示在 shell 脚本中运行了哪些函数:

$ checksec --debug --file=./hello

在本教程中,我试图寻找 Checksec 查找安全属性信息时,使用了什么底层命令。由于 Checksec 是一个 shell 脚本,因此你始终可以使用 Bash 功能。以下的命令将输出从 shell 脚本中运行的每个命令:

$ bash -x /usr/bin/checksec --file=./hello

如果你滚动浏览上述的输出结果的话,你会看到 echo_message 后面有各个安全属性的类别。以下显示了 Checksec 检测二进制文件是否包含符号时,运行的底层命令:

+ readelf -W --symbols ./hello
+ grep -q '\\.symtab'
+ echo_message '\033[31m96) Symbols\t\033[m  ' Symbols, ' symbols="yes"' '"symbols":"yes",'

上面的输出显示,Checksec 利用 readelf,来读取二进制文件,并提供一个特殊 --symbols 标志,来列出二进制文件中的所有符号。然后它会查找一个特殊值:.symtab,它提供了所能找到的条目的计数(即符号的个数)。你可以在上面编译的测试二进制文件 hello 上,尝试以下命令,得到与 Checksec 查看二进制文件类似的符号信息:

$ readelf -W --symbols ./hello
$ readelf -W --symbols ./hello | grep -i symtab

(LCTT 译注:也可以通过直接查看 /usr/bin/checksec 下的 Checksec 源文件。)

如何删除符号

你可以在编译后或编译时删除符号。

  • 编译后: 在编译后,你可以使用 strip,手动地来删除二进制文件的符号。删除后,使用 file 命令,来检验是否还有符号,现在显示 stripped,表明二进制文件 hello 无符号了:
$ gcc hello.c -o hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, for GNU/Linux 3.2.0, not stripped
$
$ strip hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=322037496cf6a2029dcdcf68649a4ebc63780138, for GNU/Linux 3.2.0, stripped
$ 
  • 编译时: 你也可以在编译时,用 -s 参数让 gcc 编译器帮你自动地删除符号:
$ gcc -s hello.c -o hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=247de82a8ad84e7d8f20751ce79ea9e0cf4bd263, for GNU/Linux 3.2.0, stripped
$

重新运行 Checksec,你可以看到现在二进制文件 hellosymbols 这一属性的值是no

$ checksec --file=./hello --output=json | jq | grep symbols
    "symbols": "no",
$

2、Canary(堆栈溢出哨兵)

Canary 是放置在缓冲区和 stack 上的控制数据之间的已知值,它用于监视缓冲区是否溢出。当应用程序执行时,会为其分配两种内存,其中之一就是 。栈是一个具有两个操作的数据结构:第一个操作 push,将数据压入堆栈;第二个操作 pop,以后进先出的顺序从栈中弹出数据。恶意的输入可能会导致栈溢出,或使用特制的输入破坏栈,并导致程序崩溃:

$ checksec --file=/bin/ls --output=json | jq | grep canary
    "canary": "yes",
$
$ checksec --file=./hello --output=json | jq | grep canary
    "canary": "no",
$

Checksec 是如何确定二进制文件是否启用了 Canary 的呢?使用上述同样的方法,得到 Checksec 在检测二进制文件是否启用 Canary 时,运行的底层命令:

$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'
启用 Canary

为了防止栈溢出等情况,编译器提供了 -stack-protector-all 标志,它向二进制文件添加了额外的代码,来检查缓冲区是否溢出:

$ gcc -fstack-protector-all hello.c -o hello

$ checksec --file=./hello --output=json | jq | grep canary
    "canary": "yes",

Checksec 显示 Canary 属性现已启用。你还可以通过以下方式,来验证这一点:

$ readelf -W -s ./hello | grep -E '__stack_chk_fail|__intel_security_cookie'
     2: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@GLIBC_2.4 (3)
    83: 0000000000000000     0 FUNC    GLOBAL DEFAULT  UND __stack_chk_fail@@GLIBC_2.4
$

3、位置无关可执行文件(PIE)

位置无关可执行文件 Position-Independent Executable (PIE),顾名思义,它指的是放置在内存中某处执行的代码,不管其绝对地址的位置,即代码段、数据段地址随机化(ASLR):

$ checksec --file=/bin/ls --output=json | jq | grep pie
    "pie": "yes",

$ checksec --file=./hello --output=json | jq | grep pie
    "pie": "no",

通常,PIE 仅对 libraries 启用,并不对独立命令行程序启用 PIE。在下面的输出中,hello 显示为 LSB executable,而 libc 标准库(.so) 文件被标记为 LSB shared object

$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=014b8966ba43e3ae47fab5acae051e208ec9074c, for GNU/Linux 3.2.0, not stripped

$ file /lib64/libc-2.32.so
/lib64/libc-2.32.so: ELF 64-bit LSB shared object, x86-64, version 1 (GNU/Linux), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=4a7fb374097fb927fb93d35ef98ba89262d0c4a4, for GNU/Linux 3.2.0, not stripped

Checksec 查找是否启用 PIE 的底层命令如下:

$ readelf -W -h ./hello | grep EXEC
  Type:                              EXEC (Executable file)

如果你在共享库上尝试相同的命令,你将看到 DYN,而不是 EXEC

$ readelf -W -h /lib64/libc-2.32.so | grep DYN
  Type:                              DYN (Shared object file)
启用 PIE

要在测试程序 hello.c 上启用 PIE,请在编译时,使用以下命令:

$ gcc -pie -fpie hello.c -o hello`

你可以使用 Checksec,来验证 PIE 是否已启用:

$ checksec --file=./hello --output=json | jq | grep pie
    "pie": "yes",
$

现在,应该会显示为 “ PIE 可执行 pie executable ”,其类型从 EXEC 更改为 DYN

$ file hello
hello: ELF 64-bit LSB pie executable, x86-64, version 1 (SYSV), dynamically linked, interpreter /lib64/ld-linux-x86-64.so.2, BuildID[sha1]=bb039adf2530d97e02f534a94f0f668cd540f940, for GNU/Linux 3.2.0, not stripped

$ readelf -W -h ./hello | grep DYN
  Type:                              DYN (Shared object file)

4、NX(堆栈禁止执行)

NX 代表 不可执行 non-executable 。它通常在 CPU 层面上启用,因此启用 NX 的操作系统可以将某些内存区域标记为不可执行。通常,缓冲区溢出漏洞将恶意代码放在堆栈上,然后尝试执行它。但是,让堆栈这些可写区域变得不可执行,可以防止这种攻击。在使用 gcc 对源程序进行编译时,默认启用此安全属性:

$ checksec --file=/bin/ls --output=json | jq | grep nx
    "nx": "yes",

$ checksec --file=./hello --output=json | jq | grep nx
    "nx": "yes",

Checksec 使用以下底层命令,来确定是否启用了 NX。在尾部的 RW 表示堆栈是可读可写的;因为没有 E,所以堆栈是不可执行的:

$ readelf -W -l ./hello | grep GNU_STACK
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RW  0x10
演示如何禁用 NX

我们不建议禁用 NX,但你可以在编译程序时,使用 -z execstack 参数,来禁用 NX:

$ gcc -z execstack hello.c -o hello

$ checksec --file=./hello --output=json | jq | grep nx
    "nx": "no",

编译后,堆栈会变为可读可写可执行(RWE),允许在堆栈上的恶意代码执行:

$ readelf -W -l ./hello | grep GNU_STACK
  GNU_STACK      0x000000 0x0000000000000000 0x0000000000000000 0x000000 0x000000 RWE 0x10

5、RELRO(GOT 写保护)

RELRO 代表 “ 重定位只读 Relocation Read-Only ”。可执行链接格式(ELF)二进制文件使用全局偏移表(GOT)来动态地解析函数。启用 RELRO 后,会设置二进制文件中的 GOT 表为只读,从而防止重定位攻击:

$ checksec --file=/bin/ls --output=json | jq | grep relro
    "relro": "full",

$ checksec --file=./hello --output=json | jq | grep relro
    "relro": "partial",

Checksec 使用以下底层命令,来查找是否启用 RELRO。在二进制文件 hello 仅启用了 RELRO 属性中的一个属性,因此,在 Checksec 验证时,显示 partial

$ readelf -W -l ./hello | grep GNU_RELRO
  GNU_RELRO      0x002e10 0x0000000000403e10 0x0000000000403e10 0x0001f0 0x0001f0 R   0x1

$ readelf -W -d ./hello | grep BIND_NOW
启用全 RELRO

要启用全 RELRO,请在 gcc 编译时,使用以下命令行参数:

$ gcc -Wl,-z,relro,-z,now hello.c -o hello

$ checksec --file=./hello --output=json | jq | grep relro
    "relro": "full",

现在, RELRO 中的第二个属性也被启用,使程序变成全 RELRO:

$ readelf -W -l ./hello | grep GNU_RELRO
  GNU_RELRO      0x002dd0 0x0000000000403dd0 0x0000000000403dd0 0x000230 0x000230 R   0x1

$ readelf -W -d ./hello | grep BIND_NOW
 0x0000000000000018 (BIND_NOW)       

6、Fortify

Fortify 是另一个安全属性,但它超出了本文的范围。Checksec 是如何在二进制文件中验证 Fortify,以及如何在 gcc 编译时启用 Fortify,作为你需要解决的课后练习。

$ checksec --file=/bin/ls --output=json | jq  | grep -i forti
    "fortify_source": "yes",
    "fortified": "5",
    "fortify-able": "17"

$ checksec --file=./hello --output=json | jq  | grep -i forti
    "fortify_source": "no",
    "fortified": "0",
    "fortify-able": "0"

其他的 Checksec 功能

关于安全性的话题是永无止境的,不可能在本文涵盖所有关于安全性的内容,但我还想提一下 Checksec 命令的一些其他功能,这些功能也很好用。

对多个二进制文件运行 Checksec

你不必对每个二进制文件都进行一次 Checksec。相反,你可以提供多个二进制文件所在的目录路径,Checksec 将一次性为你验证所有文件:

$ checksec --dir=/usr

对进程运行 Checksec

Checksec 除了能检查二进制文件的安全属性,Checksec 还能对程序起作用。以下的命令用于查找你系统上所有正在运行的程序的安全属性。如果你希望 Checksec 检查所有正在运行的进程,可以使用 --proc-all,或者你也可以使用进程名称,选择特定的进程进行检查:

$ checksec --proc-all

$ checksec --proc=bash

对内核运行 Checksec

除了本文介绍的用 Checksec 检查用户态应用程序的安全属性之外,你还可以使用它来检查系统内置的 内核属性 kernel properties

$ checksec --kernel

快来试一试 Checksec 吧

Checksec 是一个能了解哪些用户空间和内核的安全属性被启用的好方法。现在,你就可以开始使用 Checksec,来了解每个安全属性是什么,并明白启用每个安全属性的原因,以及它能阻止的攻击类型。


via: https://opensource.com/article/21/6/linux-checksec

作者:Gaurav Kamathe 选题:lujun9972 译者:chai001125 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

rustup 可用于 Rust 安装与更新。它还能够在稳定版、测试版和每日更新版之间无缝切换 Rust 编译器及其工具。

Rust 编程语言 如今变得越来越流行,受到爱好者和公司的一致好评。它受欢迎的原因之一是 Rust 提供的令人惊叹的工具,使其成为开发人员使用的乐趣。rustup 是管理 Rust 工具的官方工具。它不仅可以安装和更新 Rust ,它还能够在稳定版、测试版和每日更新版之间无缝切换 Rust 编译器及其工具。本文将向你介绍 rustup 及其一些常用命令。

默认 Rust 安装方式

如果你想在 Linux 上安装 Rust,你可以使用你的包管理器。在 Fedora 或 CentOS Stream 上,你可以这样:

$ sudo dnf install rust cargo

这提供了一个稳定版的 Rust 工具链,如果你是 Rust 的初学者,并想尝试编译和运行简单的程序,它会非常有用。但是,由于 Rust 是一种新的编程语言,它变化很快,并且经常添加许多新功能。这些功能是 Rust 工具链的每日更新版和之后测试版的一部分。要试用这些功能,你需要安装这些较新版本的工具链,而不会影响系统上的稳定版本。不幸的是,你的发行版的包管理器在这里无法做到。

使用 rustup 安装 Rust 工具链

要解决上述问题,你可以下载安装脚本:

$ curl --proto '=https' --tlsv1.2 \
    -sSf https://sh.rustup.rs > sh.rustup.rs

检查它,然后运行它。它不需要 root 权限,并根据你的本地用户权限安装 Rust:

$ file sh.rustup.rs
sh.rustup.rs: POSIX shell script, ASCII text executable
$ less sh.rustup.rs
$ bash sh.rustup.rs

出现提示时选择选项 1

1) Proceed with installation (default)
2) Customize installation
3) Cancel installation
> 1

安装后,你必须获取环境变量以确保 rustup 命令立即可供你运行:

$ source $HOME/.cargo/env

验证是否安装了 Rust 编译器(rustc)和 Rust 包管理器(cargo):

$ rustc --version
$ cargo --version

查看已安装和可用的工具链

你可以使用以下命令查看已安装的不同工具链以及哪个工具链是可用的:

$ rustup show

在工具链之间切换

你可以查看默认工具链并根据需要进行更改。如果你当前使用的是稳定版工具链,并希望尝试每日更新版中提供的新功能,你可以轻松切换到每日更新版工具链:

$ rustup default
$ rustup default nightly

要查看 Rust 的编译器和包管理器的完整路径:

$ rustup which rustc
$ rustup which cargo

检查和更新工具链

要检查是否有新的 Rust 工具链可用:

$ rustup check

假设一个新版本的 Rust 发布了,其中包含一些有趣的特性,并且你想要获取最新版本的 Rust。你可以使用 update 子命令来做到这一点:

$ rustup update

帮助和文档

以上命令对于日常使用来说绰绰有余。尽管如此,rustup 有多种命令,你可以参考帮助部分了解更多详细信息:

$ rustup --help

rustup 在 GitHub 上有完整的 参考手册,你可以用作参考。所有 Rust 文档都安装在你的本地系统上,不需要你连接到互联网。你可以访问包括书籍、标准库等在内的本地文档:

$ rustup doc
$ rustup doc --book
$ rustup doc --std
$ rustup doc --cargo

Rust 是一种正在积极开发中的令人兴奋的语言。如果你对编程的发展方向感兴趣,请关注 Rust!


via: https://opensource.com/article/22/6/rust-toolchain-rustup

作者:Gaurav Kamathe 选题:lkxed 译者:geekpi 校对:turbokernel

本文由 LCTT 原创编译,Linux中国 荣誉推出

这些方便的 Go 构建选项可以帮助你更好地理解 Go 的编译过程。

学习一门新的编程语言最令人欣慰的部分之一,就是最终运行了一个可执行文件,并获得预期的输出。当我开始学习 Go 这门编程语言时,我先是阅读一些示例程序来熟悉语法,然后是尝试写一些小的测试程序。随着时间的推移,这种方法帮助我熟悉了编译和构建程序的过程。

Go 的构建选项提供了更好地控制构建过程的方法。它们还可以提供额外的信息,帮助把这个过程分成更小的部分。在这篇文章中,我将演示我所使用的一些选项。注意:我使用的“ 构建 build ”和“ 编译 compile ”这两个词是同一个意思。

开始使用 Go

我使用的 Go 版本是 1.16.7。但是,这里给出的命令应该也能在最新的版本上运行。如果你没有安装 Go,你可以从 Go 官网 上下载它,并按照说明进行安装。你可以通过打开一个命令提示符,并键入下面的命令来验证你所安装的版本:

$ go version

你应该会得到类似下面这样的输出,具体取决于你安装的版本:

go version go1.16.7 linux/amd64

基本的 Go 程序的编译和执行方法

我将从一个在屏幕上简单打印 “Hello World” 的 Go 程序示例开始,就像下面这样:

$ cat hello.go
package main

import "fmt"

func main() {
    fmt.Println("Hello World")
}

在讨论更高级的选项之前,我将解释如何编译这个 Go 示例程序。我使用了 build 命令,后面跟着 Go 程序的源文件名,本例中是 hello.go,就像下面这样:

$ go build hello.go

如果一切工作正常,你应该看到在你的当前目录下创建了一个名为 hello 的可执行文件。你可以通过使用 file 命令验证它是 ELF 二进制可执行格式(在 Linux 平台上)。你也可以直接执行它,你会看到它输出 “Hello World”。

$ ls
hello  hello.go

$ file ./hello
./hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

$ ./hello
Hello World

Go 提供了一个方便的 run 命令,以便你只是想看看程序是否能正常工作,并获得预期的输出,而不想生成一个最终的二进制文件。请记住,即使你在当前目录中没有看到可执行文件,Go 仍然会在某个地方编译并生成可执行文件并运行它,然后把它从系统中删除。我将在本文后面的章节中解释。

$ go run hello.go
Hello World

$ ls
hello.go

更多细节

上面的命令就像一阵风一样,一下子就运行完了我的程序。然而,如果你想知道 Go 在编译这些程序的过程中做了什么,Go 提供了一个 -x 选项,它可以打印出 Go 为产生这个可执行文件所做的一切。

简单看一下你就会发现,Go 在 /tmp 内创建了一个临时工作目录,并生成了可执行文件,然后把它移到了 Go 源程序所在的当前目录。

$ go build -x hello.go

WORK=/tmp/go-build1944767317
mkdir -p $WORK/b001/

<< snip >>

mkdir -p $WORK/b001/exe/
cd .
/usr/lib/golang/pkg/tool/linux_amd64/link -o $WORK \
/b001/exe/a.out -importcfg $WORK/b001 \
/importcfg.link -buildmode=exe -buildid=K26hEYzgDkqJjx2Hf-wz/\
nDueg0kBjIygx25rYwbK/W-eJaGIOdPEWgwC6o546 \
/K26hEYzgDkqJjx2Hf-wz -extld=gcc /root/.cache/go-build /cc \
/cc72cb2f4fbb61229885fc434995964a7a4d6e10692a23cc0ada6707c5d3435b-d
/usr/lib/golang/pkg/tool/linux_amd64/buildid -w $WORK \
/b001/exe/a.out # internal
mv $WORK/b001/exe/a.out hello
rm -r $WORK/b001/

这有助于解决在程序运行后却在当前目录下没有生成可执行文件的谜团。使用 -x 显示可执行文件确实在 /tmp 工作目录下创建并被执行了。然而,与 build 命令不同的是,可执行文件并没有移动到当前目录,这使得看起来没有可执行文件被创建。

$ go run -x hello.go


mkdir -p $WORK/b001/exe/
cd .
/usr/lib/golang/pkg/tool/linux_amd64/link -o $WORK/b001 \
/exe/hello -importcfg $WORK/b001/importcfg.link -s -w -buildmode=exe -buildid=hK3wnAP20DapUDeuvAAS/E_TzkbzwXz6tM5dEC8Mx \
/7HYBzuaDGVdaZwSMEWAa/hK3wnAP20DapUDeuvAAS -extld=gcc \
/root/.cache/go-build/75/ \
7531fcf5e48444eed677bfc5cda1276a52b73c62ebac3aa99da3c4094fa57dc3-d
$WORK/b001/exe/hello
Hello World

模仿编译而不产生可执行文件

假设你不想编译程序并产生一个实际的二进制文件,但你确实想看到这个过程中的所有步骤。你可以通过使用 -n 这个构建选项来做到这一点,该选项会打印出通常的执行步骤,而不会实际创建二进制文件。

$ go build -n hello.go

保存临时目录

很多工作都发生在 /tmp 工作目录中,一旦可执行文件被创建和运行,它就会被删除。但是如果你想看看哪些文件是在编译过程中创建的呢?Go 提供了一个 -work 选项,它可以在编译程序时使用。-work 选项除了运行程序外,还打印了工作目录的路径,但它并不会在这之后删除工作目录,所以你可以切换到该目录,检查在编译过程中创建的所有文件。

$ go run -work hello.go
WORK=/tmp/go-build3209320645
Hello World

$ find /tmp/go-build3209320645
/tmp/go-build3209320645
/tmp/go-build3209320645/b001
/tmp/go-build3209320645/b001/importcfg.link
/tmp/go-build3209320645/b001/exe
/tmp/go-build3209320645/b001/exe/hello

$ /tmp/go-build3209320645/b001/exe/hello
Hello World

其他编译选项

如果说,你想手动编译程序,而不是使用 Go 的 buildrun 这两个方便的命令,最后得到一个可以直接由你的操作系统(这里指 Linux)运行的可执行文件。那么,你该怎么做呢?这个过程可以分为两部分:编译和链接。你可以使用 tool 选项来看看它是如何工作的。

首先,使用 tool compile 命令产生结果的 ar 归档文件,它包含了 .o 中间文件。接下来,对这个 hello.o 文件执行 tool link 命令,产生最终的可执行文件,然后你就可以运行它了。

$ go tool compile hello.go

$ file hello.o
hello.o: current ar archive

$ ar t hello.o
__.PKGDEF
_go_.o

$ go tool link -o hello hello.o

$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped

$ ./hello
Hello World

如果你想进一步查看基于 hello.o 文件产生可执行文件的链接过程,你可以使用 -v 选项,它会搜索每个 Go 可执行文件中包含的 runtime.a 文件。

$ go tool link -v -o hello hello.o
HEADER = -H5 -T0x401000 -R0x1000
searching for runtime.a in /usr/lib/golang/pkg/linux_amd64/runtime.a
82052 symbols, 18774 reachable
        1 package symbols, 1106 hashed symbols, 77185 non-package symbols, 3760 external symbols
81968 liveness data

交叉编译选项

现在我已经解释了 Go 程序的编译过程,接下来,我将演示 Go 如何通过在实际的 build 命令之前提供 GOOSGOARCH 这两个环境变量,来允许你构建针对不同硬件架构和操作系统的可执行文件。

这有什么用呢?举个例子,你会发现为 ARM(arch64)架构制作的可执行文件不能在英特尔(x86\_64)架构上运行,而且会产生一个 Exec 格式错误。

下面的这些选项使得生成跨平台的二进制文件变得小菜一碟:

$ GOOS=linux GOARCH=arm64 go build hello.go

$ file ./hello
./hello: ELF 64-bit LSB executable, ARM aarch64, version 1 (SYSV), statically linked, not stripped

$ ./hello
bash: ./hello: cannot execute binary file: Exec format error

$ uname -m
x86_64

你可以阅读我之前的博文,以更多了解我在 使用 Go 进行交叉编译 方面的经验。

查看底层汇编指令

源代码并不会直接转换为可执行文件,尽管它生成了一种中间汇编格式,然后最终被组装为可执行文件。在 Go 中,这被映射为一种中间汇编格式,而不是底层硬件汇编指令。

要查看这个中间汇编格式,请在使用 build 命令时,提供 -gcflags 选项,后面跟着 -S。这个命令将会显示使用到的汇编指令:

$ go build -gcflags="-S" hello.go
# command-line-arguments
"".main STEXT size=138 args=0x0 locals=0x58 funcid=0x0
        0x0000 00000 (/test/hello.go:5) TEXT    "".main(SB), ABIInternal, $88-0
        0x0000 00000 (/test/hello.go:5) MOVQ    (TLS), CX
        0x0009 00009 (/test/hello.go:5) CMPQ    SP, 16(CX)
        0x000d 00013 (/test/hello.go:5) PCDATA  $0, $-2
        0x000d 00013 (/test/hello.go:5) JLS     128

<< snip >>

你也可以使用 objdump -s 选项,来查看已经编译好的可执行程序的汇编指令,就像下面这样:

$ ls
hello  hello.go

$ go tool objdump -s main.main hello
TEXT main.main(SB) /test/hello.go
  hello.go:5            0x4975a0                64488b0c25f8ffffff      MOVQ FS:0xfffffff8, CX                  
  hello.go:5            0x4975a9                483b6110                CMPQ 0x10(CX), SP                       
  hello.go:5            0x4975ad                7671                    JBE 0x497620                            
  hello.go:5            0x4975af                4883ec58                SUBQ $0x58, SP                          
  hello.go:6            0x4975d8                4889442448              MOVQ AX, 0x48(SP)                       

<< snip >>

分离二进制文件以减少其大小

Go 的二进制文件通常比较大。例如, 一个简单的 “Hello World” 程序将会产生一个 1.9M 大小的二进制文件。

$ go build hello.go
$
$ du -sh hello
1.9M    hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, not stripped
$

为了减少生成的二进制文件的大小,你可以分离执行过程中不需要的信息。使用 -ldflags-s -w 选项可以使生成的二进制文件略微变小为 1.3M。

$ go build -ldflags="-s -w" hello.go
$
$ du -sh hello
1.3M    hello
$
$ file hello
hello: ELF 64-bit LSB executable, x86-64, version 1 (SYSV), statically linked, stripped
$

总结

我希望这篇文章向你介绍了一些方便的 Go 编译选项,同时帮助了你更好地理解 Go 编译过程。关于构建过程的其他信息和其他有趣的选项,请参考 Go 命令帮助:

$ go help build

题图由 Ashraf ChembanPixabay 上发布。


via: https://opensource.com/article/22/4/go-build-options

作者:Gaurav Kamathe 选题:lkxed 译者:lkxed 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出

Go 团队接受了新增对模糊测试的支持的提议。

 title=

Go 的应用越来越广泛。现在它是云原生软件、容器软件、命令行工具和数据库等等的首选语言。Go 很早之前就已经有了内建的 对测试的支持。这使得写测试代码和运行都相当简单。

什么是模糊测试?

模糊测试 fuzz testing (fuzzing)是指向你的软件输入非预期的数据。理想情况下,这种测试会让你的应用程序崩溃或有非预期的表现。抛开最终的结果,从程序对非预期的输入数据的处理结果中你可以得到很多信息,这样你就可以增加一些合适的错误处理。

任何一个软件都有对不同来源的输入或数据的接收说明,软件会对这些数据进行处理并返回适当的结果。软件开发后,测试工程师团队对其进行测试,找出软件中的错误,给出测试报告,并(由开发者)修复。通常测试的目的是验证软件的行为是否符合预期。测试又可以细分为不同的类型,如功能测试、集成测试、性能测试等等。每种测试方法关注软件功能的某一个方面,以便发现错误或者提升可靠性或性能。

模糊测试在这一测试过程上更进一步,尝试向软件程序输入一些“无效”或“随机”的数据。这种输入是故意的,期望得到的结果就是程序崩溃或输出异常,这样就可以暴露程序中的错误以便由开发者来修复它们。与其他测试类似,很少需要手动进行模糊测试,业界有大量的模糊测试工具可以将这个过程自动化。

Go 中的软件测试

举个例子,假如你想测试 add.go 中的 Add() 函数,你可以在 add_test.go 中导入 testing 包并把测试体写在以 TestXXX() 开头的函数内。

考虑如下代码:

func Add(num1, num2 int) int {
}

add_test.go 文件中,你可能有如下测试代码:

import "testing"

func TestAdd(t *testing.T) {
}

运行测试:

$ go test

新增对模糊测试的支持

Go 团队已经接受了 新增对模糊测试的支持的提议,以进一步推动这项工作。这涉及到新增一个 testing.F 类型,在 _test.go 文件中新增 FuzzXXX() 函数,在 Go 工具中会新增一个 -fuzz 选项来执行这些测试。

add_test.go 文件中:

func FuzzAdd(f *testing.F) {
}

执行以下代码:

$ go test -fuzz

在本文编写时,这个 功能还是试验性的,但是应该会在 1.18 发布版本中包含。(LCTT 译注:Go 1.18 刚刚发布,已经包含了对模糊测试的支持)目前很多功能如 -keepfuzzing-race 等也还没有支持。Go 团队最近发布了一篇 模糊测试教程,值得读一下。

安装 gotip 来获取最新的功能

如果你极度渴望在正式发布之前尝试这些功能,你可以使用 gotip 来测试即将正式发布的 Go 功能并反馈给他们。你可以使用下面的命令来安装 gotip。安装之后,你可以用 gotip 程序代替以前的 go 程序来编译和运行程序。

$ go install golang.org/dl/gotip@latest
$ gotip download

$ gotip version
go version devel go1.18-f009910 Thu Jan 6 16:22:21 2022 +0000 linux/amd64

社区对于模糊测试的观点

软件社区中经常会讨论模糊测试,不同的人对模糊测试有不同的看法。有些人认为这是一种有用的技术,可以找到错误,尤其是在安全方面。然而考虑到模糊测试所需要的资源(CPU、内存),有人就认为这是一种浪费,而他们更愿意用其他的测试方法。即使在 Go 团队内部,意见也不统一。我们可以看到 Go 的联合创始人 Rob Pike 对模糊测试的使用和在 Go 中的实现是持轻微的怀疑态度的。

...虽然模糊测试有助于发现某类错误,但是它会占用大量的 CPU 和存储资源,并且效益成本比率也不明确。我担心为了写模糊测试浪费精力,或者 git 仓库中充斥大量无用的测试数据

~Rob Pike

然而,Go 安全团队的另一个成员,Filo Sottile,似乎对 Go 新增支持模糊测试很乐观,举了很多例子来支持,也希望模糊测试能成为开发过程中的一部分。

我想说模糊测试可以发现极端情况下的错误。这是我们作为安全团队对其感兴趣的原因:在极端情况下发现的错误可以避免在生产环境中成为弱点。

我们希望模糊测试能成为开发的一部分 —— 不只是构建或安全方面 —— 而是整个开发过程:它能提升相关代码的质量...

~Filo Sottile

现实中的模糊测试

对我而言,模糊测试在发现错误以及让系统变得更安全和更有弹性方面似乎非常有效。举个例子,Linux 内核也会使用名为 syzkaller 的工具进行模糊测试,这个工具已经发现了 大量 错误。

AFL 也是比较流行的模糊测试工具,用来测试 C/C++ 写的程序。

之前也有对 Go 程序进行模糊测试的观点,其中之一就是 Filo 在 GitHub 评论中提到的 go-fuzz

go-fuzz 的记录提供了相当惊人的证据,证明模糊处理能很好地找到人类没有发现的错误。根据我的经验,我们只需要消耗一点点 CPU 的时间就可以得到极端情况下非常高效的测试结果。

为什么在 Go 中新增对模糊测试的原生支持

如果我们的需求是对 Go 程序进行模糊测试,之前的工具像 go-fuzz 就可以完成,那么为什么要在这种语言中增加原生支持呢?Go 模糊测试设计草案 中说明了这样做的一些根本原因。设计的思路是让开发过程更简单,因为前面说的工具增加了开发者的工作量,还有功能缺失。如果你没有接触过模糊测试,那么我建议你读一下设计草案文档。

开发者可以使用诸如 go-fuzzfzgo(基于 go-fuzz)来解决某些需求。然而,已有的每种解决方案都需要在典型的 Go 测试上做更多的事,而且还缺少关键的功能。相比于其他的 Go 测试(如基准测试和单元测试),模糊测试不应该比它们复杂,功能也不应该比它们少。已有的解决方案增加了额外的开销,比如自定义命令行工具。

模糊测试工具

在大家期望 Go 语言新增功能的列表中,模糊测试是其中很受欢迎的一项。虽然现在还是试验性的,但在将要到来的发布版本中会变得更强大。这给了我们足够的时间去尝试它以及探索它的使用场景。我们不应该把它视为一种开销,如果使用得当它会是一种发现错误非常高效的测试工具。使用 Go 的团队应该推动它的使用,开发者可以写简单的模糊测试,测试团队去慢慢扩展以此来使用它全部的能力。


via: https://opensource.com/article/22/1/native-go-fuzz-testing

作者:Gaurav Kamathe 选题:lujun9972 译者:lxbwolf 校对:wxy

本文由 LCTT 原创编译,Linux中国 荣誉推出