Compare commits

...

9 Commits

Author SHA1 Message Date
GalaxyShard
f7af8e61b8
Merge d38cd31480 into f845fa04a0 2024-11-21 11:21:36 +11:00
Alex Rønne Petersen
f845fa04a0 std.debug: Gracefully handle process_vm_readv() EPERM in MemoryAccessor.read().
Closes #21815.
2024-11-20 23:07:46 +01:00
Frank Denis
a5d4ad17b7
crypto.keccak.State: add checks to prevent insecure transitions (#22020)
* crypto.keccak.State: don't unconditionally permute after a squeeze()

Now, squeeze() behaves like absorb()

Namely,

squeeze(x[0..t]);
squeeze(x[t..n)); with t <= n

becomes equivalent to squeeze(x[0..n]).

* keccak: in debug mode, track transitions to prevent insecure ones.

Fixes #22019
2024-11-20 11:16:09 +01:00
Shawn Gao
dafe1a910d Append disabled LLVM CPU features after enabled ones 2024-11-20 10:09:03 +01:00
Frank Denis
acba2645f7
crypto.aes.soft: use std.atomic.cache_line instead of a harcoded value (#22026) 2024-11-20 03:48:18 +00:00
xdBronch
5f3a70ed5f Fix peer type resolution with allowzero pointers 2024-11-20 02:09:50 +02:00
GalaxyShard
d38cd31480
account for CRLF 2024-11-17 02:33:38 -05:00
GalaxyShard
4f479ac162
add test for addEmbedPath 2024-11-17 02:33:38 -05:00
GalaxyShard
9ae92eb488
std.Build: implement addEmbedPath for adding C #embed search directories 2024-11-17 02:33:37 -05:00
13 changed files with 266 additions and 45 deletions

View File

@ -135,6 +135,7 @@ pub const IncludeDir = union(enum) {
framework_path_system: LazyPath,
other_step: *Step.Compile,
config_header_step: *Step.ConfigHeader,
embed_path: LazyPath,
pub fn appendZigProcessFlags(
include_dir: IncludeDir,
@ -171,6 +172,9 @@ pub const IncludeDir = union(enum) {
const header_dir_path = full_file_path[0 .. full_file_path.len - config_header.include_path.len];
try zig_args.appendSlice(&.{ "-I", header_dir_path });
},
.embed_path => |embed_path| {
try zig_args.append(try std.mem.concat(b.allocator, u8, &.{ "--embed-dir=", embed_path.getPath2(b, asking_step) }));
},
}
}
};
@ -639,6 +643,12 @@ pub fn addFrameworkPath(m: *Module, directory_path: LazyPath) void {
addLazyPathDependenciesOnly(m, directory_path);
}
pub fn addEmbedPath(m: *Module, lazy_path: LazyPath) void {
const b = m.owner;
m.include_dirs.append(b.allocator, .{ .embed_path = lazy_path.dupe(b) }) catch @panic("OOM");
addLazyPathDependenciesOnly(m, lazy_path);
}
pub fn addLibraryPath(m: *Module, directory_path: LazyPath) void {
const b = m.owner;
m.lib_paths.append(b.allocator, directory_path.dupe(b)) catch @panic("OOM");

View File

@ -937,6 +937,10 @@ pub fn addConfigHeader(compile: *Compile, config_header: *Step.ConfigHeader) voi
compile.root_module.addConfigHeader(config_header);
}
pub fn addEmbedPath(compile: *Compile, lazy_path: LazyPath) void {
compile.root_module.addEmbedPath(lazy_path);
}
pub fn addLibraryPath(compile: *Compile, directory_path: LazyPath) void {
compile.root_module.addLibraryPath(directory_path);
}

View File

@ -669,7 +669,7 @@ fn mul(a: u8, b: u8) u8 {
return @as(u8, @truncate(s));
}
const cache_line_bytes = 64;
const cache_line_bytes = std.atomic.cache_line;
inline fn sbox_lookup(sbox: *align(64) const [256]u8, idx0: u8, idx1: u8, idx2: u8, idx3: u8) [4]u8 {
if (side_channels_mitigations == .none) {
@ -683,8 +683,8 @@ inline fn sbox_lookup(sbox: *align(64) const [256]u8, idx0: u8, idx1: u8, idx2:
const stride = switch (side_channels_mitigations) {
.none => unreachable,
.basic => sbox.len / 4,
.medium => sbox.len / (sbox.len / cache_line_bytes) * 2,
.full => sbox.len / (sbox.len / cache_line_bytes),
.medium => @min(sbox.len, 2 * cache_line_bytes),
.full => @min(sbox.len, cache_line_bytes),
};
const of0 = idx0 % stride;
const of1 = idx1 % stride;
@ -718,12 +718,11 @@ inline fn table_lookup(table: *align(64) const [4][256]u32, idx0: u8, idx1: u8,
table[3][idx3],
};
} else {
const table_bytes = @sizeOf(@TypeOf(table[0]));
const stride = switch (side_channels_mitigations) {
.none => unreachable,
.basic => table[0].len / 4,
.medium => table[0].len / (table_bytes / cache_line_bytes) * 2,
.full => table[0].len / (table_bytes / cache_line_bytes),
.medium => @max(1, @min(table[0].len, 2 * cache_line_bytes / 4)),
.full => @max(1, @min(table[0].len, cache_line_bytes / 4)),
};
const of0 = idx0 % stride;
const of1 = idx1 % stride;

View File

@ -4,6 +4,7 @@ const assert = std.debug.assert;
const math = std.math;
const mem = std.mem;
const native_endian = builtin.cpu.arch.endian();
const mode = @import("builtin").mode;
/// The Keccak-f permutation.
pub fn KeccakF(comptime f: u11) type {
@ -199,6 +200,46 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type
comptime assert(f >= 200 and f <= 1600 and f % 200 == 0); // invalid state size
comptime assert(capacity < f and capacity % 8 == 0); // invalid capacity size
// In debug mode, track transitions to prevent insecure ones.
const Op = enum { uninitialized, initialized, updated, absorb, squeeze };
const TransitionTracker = if (mode == .Debug) struct {
op: Op = .uninitialized,
fn to(tracker: *@This(), next_op: Op) void {
switch (next_op) {
.updated => {
switch (tracker.op) {
.uninitialized => @panic("cannot permute before initializing"),
else => {},
}
},
.absorb => {
switch (tracker.op) {
.squeeze => @panic("cannot absorb right after squeezing"),
else => {},
}
},
.squeeze => {
switch (tracker.op) {
.uninitialized => @panic("cannot squeeze before initializing"),
.initialized => @panic("cannot squeeze right after initializing"),
.absorb => @panic("cannot squeeze right after absorbing"),
else => {},
}
},
.uninitialized => @panic("cannot transition to uninitialized"),
.initialized => {},
}
tracker.op = next_op;
}
} else struct {
// No-op in non-debug modes.
inline fn to(tracker: *@This(), next_op: Op) void {
_ = tracker; // no-op
_ = next_op; // no-op
}
};
return struct {
const Self = @This();
@ -215,67 +256,108 @@ pub fn State(comptime f: u11, comptime capacity: u11, comptime rounds: u5) type
st: KeccakF(f) = .{},
transition: TransitionTracker = .{},
/// Absorb a slice of bytes into the sponge.
pub fn absorb(self: *Self, bytes_: []const u8) void {
var bytes = bytes_;
pub fn absorb(self: *Self, bytes: []const u8) void {
self.transition.to(.absorb);
var i: usize = 0;
if (self.offset > 0) {
const left = @min(rate - self.offset, bytes.len);
@memcpy(self.buf[self.offset..][0..left], bytes[0..left]);
self.offset += left;
if (left == bytes.len) return;
if (self.offset == rate) {
self.offset = 0;
self.st.addBytes(self.buf[0..]);
self.st.permuteR(rounds);
self.offset = 0;
}
if (left == bytes.len) return;
bytes = bytes[left..];
i = left;
}
while (bytes.len >= rate) {
self.st.addBytes(bytes[0..rate]);
while (i + rate < bytes.len) : (i += rate) {
self.st.addBytes(bytes[i..][0..rate]);
self.st.permuteR(rounds);
bytes = bytes[rate..];
}
if (bytes.len > 0) {
@memcpy(self.buf[0..bytes.len], bytes);
self.offset = bytes.len;
const left = bytes.len - i;
if (left > 0) {
@memcpy(self.buf[0..left], bytes[i..][0..left]);
}
self.offset = left;
}
/// Initialize the state from a slice of bytes.
pub fn init(bytes: [f / 8]u8) Self {
return .{ .st = KeccakF(f).init(bytes) };
pub fn init(bytes: [f / 8]u8, delim: u8) Self {
var st = Self{ .st = KeccakF(f).init(bytes), .delim = delim };
st.transition.to(.initialized);
return st;
}
/// Permute the state
pub fn permute(self: *Self) void {
if (mode == .Debug) {
if (self.transition.op == .absorb and self.offset > 0) {
@panic("cannot permute with pending input - call fillBlock() or pad() instead");
}
}
self.transition.to(.updated);
self.st.permuteR(rounds);
self.offset = 0;
}
/// Align the input to the rate boundary.
/// Align the input to the rate boundary and permute.
pub fn fillBlock(self: *Self) void {
self.transition.to(.absorb);
self.st.addBytes(self.buf[0..self.offset]);
self.st.permuteR(rounds);
self.offset = 0;
self.transition.to(.updated);
}
/// Mark the end of the input.
pub fn pad(self: *Self) void {
self.transition.to(.absorb);
self.st.addBytes(self.buf[0..self.offset]);
if (self.offset == rate) {
self.st.permuteR(rounds);
self.offset = 0;
}
self.st.addByte(self.delim, self.offset);
self.st.addByte(0x80, rate - 1);
self.st.permuteR(rounds);
self.offset = 0;
self.transition.to(.updated);
}
/// Squeeze a slice of bytes from the sponge.
/// The function can be called multiple times.
pub fn squeeze(self: *Self, out: []u8) void {
self.transition.to(.squeeze);
var i: usize = 0;
while (i < out.len) : (i += rate) {
const left = @min(rate, out.len - i);
self.st.extractBytes(out[i..][0..left]);
if (self.offset == rate) {
self.st.permuteR(rounds);
} else if (self.offset > 0) {
@branchHint(.unlikely);
var buf: [rate]u8 = undefined;
self.st.extractBytes(buf[0..]);
const left = @min(rate - self.offset, out.len);
@memcpy(out[0..left], buf[self.offset..][0..left]);
self.offset += left;
if (left == out.len) return;
if (self.offset == rate) {
self.offset = 0;
self.st.permuteR(rounds);
}
i = left;
}
while (i + rate < out.len) : (i += rate) {
self.st.extractBytes(out[i..][0..rate]);
self.st.permuteR(rounds);
}
const left = out.len - i;
if (left > 0) {
self.st.extractBytes(out[i..][0..left]);
}
self.offset = left;
}
};
}
@ -298,3 +380,26 @@ test "Keccak-f800" {
};
try std.testing.expectEqualSlices(u32, &st.st, &expected);
}
test "squeeze" {
var st = State(800, 256, 22).init([_]u8{0x80} ** 100, 0x01);
var out0: [15]u8 = undefined;
var out1: [out0.len]u8 = undefined;
st.permute();
var st0 = st;
st0.squeeze(out0[0..]);
var st1 = st;
st1.squeeze(out1[0 .. out1.len / 2]);
st1.squeeze(out1[out1.len / 2 ..]);
try std.testing.expectEqualSlices(u8, &out0, &out1);
var out2: [100]u8 = undefined;
var out3: [out2.len]u8 = undefined;
var st2 = st;
st2.squeeze(out2[0..]);
var st3 = st;
st3.squeeze(out3[0 .. out2.len / 2]);
st3.squeeze(out3[out2.len / 2 ..]);
try std.testing.expectEqualSlices(u8, &out2, &out3);
}

View File

@ -48,7 +48,8 @@ fn read(ma: *MemoryAccessor, address: usize, buf: []u8) bool {
switch (linux.E.init(bytes_read)) {
.SUCCESS => return bytes_read == buf.len,
.FAULT => return false,
.INVAL, .PERM, .SRCH => unreachable, // own pid is always valid
.INVAL, .SRCH => unreachable, // own pid is always valid
.PERM => {}, // Known to happen in containers.
.NOMEM => {},
.NOSYS => {}, // QEMU is known not to implement this syscall.
else => unreachable, // unexpected

View File

@ -312,18 +312,29 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module {
if (!options.global.use_llvm) break :b null;
var buf = std.ArrayList(u8).init(arena);
for (target.cpu.arch.allFeaturesList(), 0..) |feature, index_usize| {
const index = @as(std.Target.Cpu.Feature.Set.Index, @intCast(index_usize));
const is_enabled = target.cpu.features.isEnabled(index);
var disabled_features = std.ArrayList(u8).init(arena);
defer disabled_features.deinit();
// Append disabled features after enabled ones, so that their effects aren't overwritten.
for (target.cpu.arch.allFeaturesList()) |feature| {
if (feature.llvm_name) |llvm_name| {
const plus_or_minus = "-+"[@intFromBool(is_enabled)];
try buf.ensureUnusedCapacity(2 + llvm_name.len);
buf.appendAssumeCapacity(plus_or_minus);
buf.appendSliceAssumeCapacity(llvm_name);
buf.appendSliceAssumeCapacity(",");
const is_enabled = target.cpu.features.isEnabled(feature.index);
if (is_enabled) {
try buf.ensureUnusedCapacity(2 + llvm_name.len);
buf.appendAssumeCapacity('+');
buf.appendSliceAssumeCapacity(llvm_name);
buf.appendAssumeCapacity(',');
} else {
try disabled_features.ensureUnusedCapacity(2 + llvm_name.len);
disabled_features.appendAssumeCapacity('-');
disabled_features.appendSliceAssumeCapacity(llvm_name);
disabled_features.appendAssumeCapacity(',');
}
}
}
try buf.appendSlice(disabled_features.items);
if (buf.items.len == 0) break :b "";
assert(std.mem.endsWith(u8, buf.items, ","));
buf.items[buf.items.len - 1] = 0;

View File

@ -35076,6 +35076,7 @@ fn resolvePeerTypesInner(
ptr_info.flags.is_const = ptr_info.flags.is_const or peer_info.flags.is_const;
ptr_info.flags.is_volatile = ptr_info.flags.is_volatile or peer_info.flags.is_volatile;
ptr_info.flags.is_allowzero = ptr_info.flags.is_allowzero or peer_info.flags.is_allowzero;
const peer_sentinel: InternPool.Index = switch (peer_info.flags.size) {
.One => switch (ip.indexToKey(peer_info.child)) {

View File

@ -519,6 +519,7 @@ const usage_build_generic =
\\ -idirafter [dir] Add directory to AFTER include search path
\\ -isystem [dir] Add directory to SYSTEM include search path
\\ -I[dir] Add directory to include search path
\\ --embed-dir=[dir] Add directory to embed search path
\\ -D[macro]=[value] Define C [macro] to [value] (1 if [value] omitted)
\\ -cflags [flags] -- Set extra flags for the next positional C source files
\\ -rcflags [flags] -- Set extra flags for the next positional .rc source files
@ -1258,6 +1259,8 @@ fn buildOutputType(
try cc_argv.appendSlice(arena, &.{ arg, args_iter.nextOrFatal() });
} else if (mem.eql(u8, arg, "-I")) {
try cssan.addIncludePath(arena, &cc_argv, .I, arg, args_iter.nextOrFatal(), false);
} else if (mem.startsWith(u8, arg, "--embed-dir=")) {
try cssan.addIncludePath(arena, &cc_argv, .embed_dir, arg, arg["--embed-dir=".len..], true);
} else if (mem.eql(u8, arg, "-isystem")) {
try cssan.addIncludePath(arena, &cc_argv, .isystem, arg, args_iter.nextOrFatal(), false);
} else if (mem.eql(u8, arg, "-iwithsysroot")) {
@ -6748,13 +6751,17 @@ const ClangSearchSanitizer = struct {
m.iframeworkwithsysroot = true;
if (m.iwithsysroot) warn(wtxt, .{ dir, "iframeworkwithsysroot", "iwithsysroot" });
},
.embed_dir => {
if (m.embed_dir) return;
m.embed_dir = true;
},
}
try argv.ensureUnusedCapacity(ally, 2);
argv.appendAssumeCapacity(arg);
if (!joined) argv.appendAssumeCapacity(dir);
}
const Group = enum { I, isystem, iwithsysroot, idirafter, iframework, iframeworkwithsysroot };
const Group = enum { I, isystem, iwithsysroot, idirafter, iframework, iframeworkwithsysroot, embed_dir };
const Membership = packed struct {
I: bool = false,
@ -6763,6 +6770,7 @@ const ClangSearchSanitizer = struct {
idirafter: bool = false,
iframework: bool = false,
iframeworkwithsysroot: bool = false,
embed_dir: bool = false,
};
};

View File

@ -2200,39 +2200,77 @@ test "peer type resolution: pointer attributes are combined correctly" {
var buf_a align(4) = "foo".*;
var buf_b align(4) = "bar".*;
var buf_c align(4) = "baz".*;
var buf_d align(4) = "qux".*;
const a: [*:0]align(4) const u8 = &buf_a;
const b: *align(2) volatile [3:0]u8 = &buf_b;
const c: [*:0]align(4) u8 = &buf_c;
const d: [*:0]allowzero align(4) u8 = &buf_d;
comptime assert(@TypeOf(a, b, c) == [*:0]align(2) const volatile u8);
comptime assert(@TypeOf(a, c, b) == [*:0]align(2) const volatile u8);
comptime assert(@TypeOf(b, a, c) == [*:0]align(2) const volatile u8);
comptime assert(@TypeOf(b, c, a) == [*:0]align(2) const volatile u8);
comptime assert(@TypeOf(c, a, b) == [*:0]align(2) const volatile u8);
comptime assert(@TypeOf(c, b, a) == [*:0]align(2) const volatile u8);
comptime assert(@TypeOf(a, b, c, d) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(a, b, d, c) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(a, c, b, d) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(a, c, d, b) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(a, d, b, c) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(a, d, c, b) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(b, a, c, d) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(b, a, d, c) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(b, c, a, d) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(b, c, d, a) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(b, d, c, a) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(b, d, a, c) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(c, a, b, d) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(c, a, d, b) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(c, b, a, d) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(c, b, d, a) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(c, d, b, a) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(c, d, a, b) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(d, a, b, c) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(d, a, c, b) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(d, b, a, c) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(d, b, c, a) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(d, c, b, a) == [*:0]allowzero align(2) const volatile u8);
comptime assert(@TypeOf(d, c, a, b) == [*:0]allowzero align(2) const volatile u8);
var x: u8 = 0;
_ = &x;
const r1 = switch (x) {
0 => a,
1 => b,
else => c,
2 => c,
else => d,
};
const r2 = switch (x) {
0 => b,
1 => a,
else => c,
2 => c,
else => d,
};
const r3 = switch (x) {
0 => c,
1 => a,
else => b,
2 => b,
else => d,
};
const r4 = switch (x) {
0 => d,
1 => a,
2 => b,
else => c,
};
try expectEqualSlices(u8, std.mem.span(@volatileCast(r1)), "foo");
try expectEqualSlices(u8, std.mem.span(@volatileCast(r2)), "bar");
try expectEqualSlices(u8, std.mem.span(@volatileCast(r3)), "baz");
const NonAllowZero = comptime blk: {
var ti = @typeInfo(@TypeOf(r1, r2, r3, r4));
ti.pointer.is_allowzero = false;
break :blk @Type(ti);
};
try expectEqualSlices(u8, std.mem.span(@volatileCast(@as(NonAllowZero, @ptrCast(r1)))), "foo");
try expectEqualSlices(u8, std.mem.span(@volatileCast(@as(NonAllowZero, @ptrCast(r2)))), "bar");
try expectEqualSlices(u8, std.mem.span(@volatileCast(@as(NonAllowZero, @ptrCast(r3)))), "baz");
try expectEqualSlices(u8, std.mem.span(@volatileCast(@as(NonAllowZero, @ptrCast(r4)))), "qux");
}
test "peer type resolution: arrays of compatible types" {

View File

@ -126,6 +126,9 @@
.c_compiler = .{
.path = "c_compiler",
},
.c_embed_path = .{
.path = "c_embed_path",
},
.pie = .{
.path = "pie",
},

View File

@ -0,0 +1,25 @@
const std = @import("std");
pub fn build(b: *std.Build) void {
const test_step = b.step("test", "Test it");
b.default_step = test_step;
const optimize: std.builtin.OptimizeMode = .Debug;
const exe = b.addExecutable(.{
.name = "test",
.target = b.graph.host,
.optimize = optimize,
});
exe.addCSourceFile(.{
.file = b.path("test.c"),
.flags = &.{"-std=c23"},
});
exe.linkLibC();
exe.addEmbedPath(b.path("data"));
const run_c_cmd = b.addRunArtifact(exe);
run_c_cmd.expectExitCode(0);
run_c_cmd.skip_foreign_checks = true;
test_step.dependOn(&run_c_cmd.step);
}

View File

@ -0,0 +1 @@
This text is the contents of foo.data

View File

@ -0,0 +1,15 @@
#include <stdlib.h>
#include <string.h>
int main(void) {
// Raw bytes; not a C string
const char data[] = {
#embed <foo.data>
};
const char *expected = "This text is the contents of foo.data";
if (sizeof data == strlen(expected) && memcmp(data, expected, sizeof data) == 0) {
return EXIT_SUCCESS;
} else {
return EXIT_FAILURE;
}
}