Nils Hasenbanck ist der Gründer der Tsukisoft GmbH und ein Senior Developer. Seine Leidenschaft ist das Bauen von technisch eleganten, …
Bitte lüge nicht über Sicherheit
Is das sicher?
Kürzlich stolperte ich über eine Funktion aus einer Upstream Bibliothek, die ich benutzte und die ungefähr so aussah:
/// Data must be aligned to 32 byte.
fn load_data(data: &[u8]) {
// Some call to a FFI function (C).
}
Ich habe einen Blick in die FFI-Funktion geworfen, und die Daten sollten in der Tat auf 32 Byte ausgerichtet sein. Dies wirft die Frage auf: Ist dies tatsächlich eine sichere Funktion? Die Funktion nahm eine Slice von Bytes und hat nicht sicher gestellt, dass die Daten tatsächlich auf 32 Bytes ausgerichtet sind. Da die Dokumentation der FFI-Funktion erwähnt, dass die Daten auf 32 Bytes ausgerichtet sein müssen, muss ich annehmen, dass ein “Nasal Demons” auftauchen könnten, wenn das nicht der Fall wäre.
Ich würde argumentieren, dass eine solche Funktion nicht sicher ist und nicht als solche gekennzeichnet werden sollte.
Wie können wir das also beheben?
Lösung 1: Das ist nicht unser Problem.
Die einfachste Lösung wäre, diese Funktion als “unsafe” zu kennzeichnen und zu dokumentieren, wie man diese Funktion sicher verwendet. Wir erwarten somit, dass der Benutzer die Sicherheitseinschränkungen handhabt:
/// # Safety
/// Data must be aligned to 32 bytes.
unsafe fn load_data(data: &[u8]) {
//...
}
Lösung 2: Fehlschlagen, wenn die Sicherheitsbedingung nicht erfüllt ist.
Eine andere Lösung wäre, dass die Funktion fehlschlägt, wenn die Daten nicht richtig ausgerichtet sind. Wir stellen natürlich sicher, dass dieses Verhalten ordentlich dokumentiert ist:
/// # Panics
/// Panics if the data is not aligned to 32 bytes.
fn load_data(data: &[u8]) {
let ptr = data.as_ptr();
assert_eq!(ptr.align_offset(32), 0);
//...
}
Lösung 3: Entwerfe eine API, die den Benutzer der Funktion anleitet.
Ich denke, der beste Ansatz wäre hier, die API so zu gestalten, dass sie den Benutzer der Funktion zur richtigen Verwendung führt. Es könnte eine gute Idee sein, einen eigenen Typ einzuführen, der 32 Bytes ausgerichtet ist, und dessen Verwendung zu erfordern:
#[repr(C, align(32))]
pub struct Aligned32([u8; 32]);
fn load_data(data: &[Aligned32]) {
//...
}
Diese Lösung hat die schöne Eigenschaft, dass die Funktion nicht fehlschlagen kann.
Natürlich könnten wir auch eine Hilfsfunktion zur Verfügung stellen, um die
ausgerichteten Daten einfach aus einem beliebigen AsRef<[u8]>
oder einem Reader zu erzeugen.