В комментариях к одной из ссылок на Hacker News некто утверждал, что использование Rust предотвратило бы Heartlbeed, что код бы даже не скомпилировался. Это прозвучало как вызов!
Тред начинается вот здесь. Я не собирался ни к кому придираться, но утверждение о предотвращении Heartbleed оказалось удачно сформулировано. В отличие от расплывчатых заявлений о безопасности работы с памятью в целом, конкретно данное утверждение можно протестировать.
Я не планирую реализовать весь стек TLS на Rust, поэтому срежу путь и уменьшу масштаб проблемы. Надеюсь, что моя модель сохранит суть проблемы. В двух словах, цель такова: написать программу, которая читает файл (пакет) из файловой системы (сети) и отправляет его обратно (этакий сетевой вариант echo). Длина echo-запроса будет закодирована одним байтом, за которым следуют данные. Это эквивалентно уязвимости TLS. Наша программа будет принимать пару таких пакетов, yourping
и myping
, и отвечать пакетами yourecho
и myecho
. Если какие-либо данные из пакета your
просочатся в пакет my
, у нас проблема: heartbleed1.
Начнём с простой программы на Rust.
use std::old_io::File;
fn pingback(path : Path, outpath : Path, buffer : &mut[u8]) {
let mut fd = File::open(&path);
match fd.read(buffer) {
Err(what) => panic!("say {}", what),
Ok(x) => if x < 1 { return; }
}
let len = buffer[0] as usize;
let mut outfd = File::create(&outpath);
match outfd.write_all(&buffer[0 .. len]) {
Err(what) => panic!("say {}", what),
Ok(_) => ()
}
}
fn main() {
let buffer = &mut[0u8; 256];
pingback(Path::new("yourping"), Path::new("yourecho"), buffer);
pingback(Path::new("myping"), Path::new("myecho"), buffer);
}
Программа компилируется, хотя и с предупреждениями из-за ламерского использования std::old_io
. Не бог весть какой код, но и не самый ужасный. К примеру, мне удалось не использовать небезопасные межъязыковые интерфейсы (FFI) для вызова memcpy из C.
Давайте посмотрим, что программа делает с простыми входными данными.
$ echo #i have many secrets. this is one. > yourping
$ echo #i know your > myping
$ ./bleed
$ cat yourecho
#i have many secrets. this is one.
$ cat myecho
#i know your
secrets. this is one.
Бинго! Секретные данные утекли.
Конечно же, настоящий программист на Rust никогда не напишет подобной программы, поэтому, вероятно, я ещё и не продемонстрировал Heartbleed на Rust.
Давайте отдохнём от Rust и рассмотрим эквивалентный код на C.
#include <fcntl.h>
#include <unistd.h>
#include <assert.h>
void
pingback(char *path, char *outpath, unsigned char *buffer)
{
int fd;
if ((fd = open(path, O_RDONLY)) == -1)
assert(!"open");
if (read(fd, buffer, 256) < 1)
assert(!"read");
close(fd);
size_t len = buffer[0];
if ((fd = creat(outpath, 0644)) == -1)
assert(!"creat");
if (write(fd, buffer, len) != len)
assert(!"write");
close(fd);
}
int
main(int argc, char **argv)
{
unsigned char buffer[256];
pingback("yourping", "yourecho", buffer);
pingback("myping", "myecho", buffer);
}
Анкетирование показало, что ни один настоящий программист на C никогда не напишет такой программы. Что же мы имеем?
код, который не напишет ни один настоящий программист на C: heartbleed
код, который не напишет ни один настоящий программист на Rust: (задачка для читателя)
Смысл поста не в порицании Rust. Я мог бы написать похожую программу на Go, или даже на Haskell, если бы я был достаточно умён для понимания буррито. Смысл в том, что пока мы не поймём, что представляют собой уязвимости наподобие Heartbleed, мы едва ли сможем избежать их простым переключением на волшебный уязвимостойкий язык. Да, каждый слышал о Heartbleed, но это не обязательно делает его хорошим примером.
Возможно, аргумент о Heartbleed использовался не как отсылка к самому Heartbleed, а к пачке других больших и страшных проблем. Не уверен, что это делает аргумент лучше. «Уязвимости, подобные Heartbleed, но не слишком похожие» — плохо определённый класс проблем. Сложно оценить какие-либо утверждения о таком классе.
Говоря об уязвимостях и их разрешении, нам нужно быть точными и осторожными. Поднятый вокруг Heartbleed (Shellshock, и т.п.) хайп делает его привлекательной целью для построения аргументов, но стоит проверять сочетаемость примера и аргумента. Ошибочные примеры приводят к ошибочным решениям.
Примечания
1. bleed — сочиться, испускать
Ссылки
- Обсуждение на reddit
- Обсуждение на HackerNews (bonus: проштрафившийся пользователь принёс извинения)
- Wikipedia — Heartbleed
- The Rust Programming Language
-
Видео от Tom Scott о Heartbleed
Автор: JIghtuse