std: Add libssp implementation for GNU/Windows targets

Unlike glibc and musl, MinGW provides no libssp symbols leading to
countless compile errors if FORTIFY_SOURCE is defined.

Add a (incomplete) implementation of libssp written in Zig so that
linking succeeds.

Closes #6492
This commit is contained in:
LemonBoy 2020-10-02 16:48:18 +02:00 committed by Andrew Kelley
parent 2aff27d922
commit d44486b274
5 changed files with 276 additions and 53 deletions

View File

@ -35,6 +35,10 @@ comptime {
@export(strncmp, .{ .name = "strncmp", .linkage = .Strong }); @export(strncmp, .{ .name = "strncmp", .linkage = .Strong });
@export(strerror, .{ .name = "strerror", .linkage = .Strong }); @export(strerror, .{ .name = "strerror", .linkage = .Strong });
@export(strlen, .{ .name = "strlen", .linkage = .Strong }); @export(strlen, .{ .name = "strlen", .linkage = .Strong });
@export(strcpy, .{ .name = "strcpy", .linkage = .Strong });
@export(strncpy, .{ .name = "strncpy", .linkage = .Strong });
@export(strcat, .{ .name = "strcat", .linkage = .Strong });
@export(strncat, .{ .name = "strncat", .linkage = .Strong });
} else if (is_msvc) { } else if (is_msvc) {
@export(_fltused, .{ .name = "_fltused", .linkage = .Strong }); @export(_fltused, .{ .name = "_fltused", .linkage = .Strong });
} }
@ -47,6 +51,90 @@ fn wasm_start() callconv(.C) void {
_ = main(0, undefined); _ = main(0, undefined);
} }
fn strcpy(dest: [*:0]u8, src: [*:0]const u8) callconv(.C) [*:0]u8 {
var i: usize = 0;
while (src[i] != 0) : (i += 1) {
dest[i] = src[i];
}
dest[i] = 0;
return dest;
}
test "strcpy" {
var s1: [9:0]u8 = undefined;
s1[0] = 0;
_ = strcpy(&s1, "foobarbaz");
std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.spanZ(&s1));
}
fn strncpy(dest: [*:0]u8, src: [*:0]const u8, n: usize) callconv(.C) [*:0]u8 {
var i: usize = 0;
while (i < n and src[i] != 0) : (i += 1) {
dest[i] = src[i];
}
while (i < n) : (i += 1) {
dest[i] = 0;
}
return dest;
}
test "strncpy" {
var s1: [9:0]u8 = undefined;
s1[0] = 0;
_ = strncpy(&s1, "foobarbaz", 9);
std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.spanZ(&s1));
}
fn strcat(dest: [*:0]u8, src: [*:0]const u8) callconv(.C) [*:0]u8 {
var dest_end: usize = 0;
while (dest[dest_end] != 0) : (dest_end += 1) {}
var i: usize = 0;
while (src[i] != 0) : (i += 1) {
dest[dest_end + i] = src[i];
}
dest[dest_end + i] = 0;
return dest;
}
test "strcat" {
var s1: [9:0]u8 = undefined;
s1[0] = 0;
_ = strcat(&s1, "foo");
_ = strcat(&s1, "bar");
_ = strcat(&s1, "baz");
std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.spanZ(&s1));
}
fn strncat(dest: [*:0]u8, src: [*:0]const u8, avail: usize) callconv(.C) [*:0]u8 {
var dest_end: usize = 0;
while (dest[dest_end] != 0) : (dest_end += 1) {}
var i: usize = 0;
while (i < avail and src[i] != 0) : (i += 1) {
dest[dest_end + i] = src[i];
}
dest[dest_end + i] = 0;
return dest;
}
test "strncat" {
var s1: [9:0]u8 = undefined;
s1[0] = 0;
_ = strncat(&s1, "foo1111", 3);
_ = strncat(&s1, "bar1111", 3);
_ = strncat(&s1, "baz1111", 3);
std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.spanZ(&s1));
}
fn strcmp(s1: [*:0]const u8, s2: [*:0]const u8) callconv(.C) c_int { fn strcmp(s1: [*:0]const u8, s2: [*:0]const u8) callconv(.C) c_int {
return std.cstr.cmp(s1, s2); return std.cstr.cmp(s1, s2);
} }
@ -92,7 +180,7 @@ pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn
while (true) {} while (true) {}
} }
export fn memset(dest: ?[*]u8, c: u8, n: usize) ?[*]u8 { export fn memset(dest: ?[*]u8, c: u8, n: usize) callconv(.C) ?[*]u8 {
@setRuntimeSafety(false); @setRuntimeSafety(false);
var index: usize = 0; var index: usize = 0;
@ -102,7 +190,13 @@ export fn memset(dest: ?[*]u8, c: u8, n: usize) ?[*]u8 {
return dest; return dest;
} }
export fn memcpy(noalias dest: ?[*]u8, noalias src: ?[*]const u8, n: usize) ?[*]u8 { export fn __memset(dest: ?[*]u8, c: u8, n: usize, dest_n: usize) callconv(.C) ?[*]u8 {
if (dest_n < n)
@panic("buffer overflow");
return memset(dest, c, n);
}
export fn memcpy(noalias dest: ?[*]u8, noalias src: ?[*]const u8, n: usize) callconv(.C) ?[*]u8 {
@setRuntimeSafety(false); @setRuntimeSafety(false);
var index: usize = 0; var index: usize = 0;
@ -112,7 +206,7 @@ export fn memcpy(noalias dest: ?[*]u8, noalias src: ?[*]const u8, n: usize) ?[*]
return dest; return dest;
} }
export fn memmove(dest: ?[*]u8, src: ?[*]const u8, n: usize) ?[*]u8 { export fn memmove(dest: ?[*]u8, src: ?[*]const u8, n: usize) callconv(.C) ?[*]u8 {
@setRuntimeSafety(false); @setRuntimeSafety(false);
if (@ptrToInt(dest) < @ptrToInt(src)) { if (@ptrToInt(dest) < @ptrToInt(src)) {
@ -131,7 +225,7 @@ export fn memmove(dest: ?[*]u8, src: ?[*]const u8, n: usize) ?[*]u8 {
return dest; return dest;
} }
export fn memcmp(vl: ?[*]const u8, vr: ?[*]const u8, n: usize) isize { export fn memcmp(vl: ?[*]const u8, vr: ?[*]const u8, n: usize) callconv(.C) isize {
@setRuntimeSafety(false); @setRuntimeSafety(false);
var index: usize = 0; var index: usize = 0;
@ -146,17 +240,17 @@ export fn memcmp(vl: ?[*]const u8, vr: ?[*]const u8, n: usize) isize {
} }
test "test_memcmp" { test "test_memcmp" {
const base_arr = []u8{ 1, 1, 1 }; const base_arr = &[_]u8{ 1, 1, 1 };
const arr1 = []u8{ 1, 1, 1 }; const arr1 = &[_]u8{ 1, 1, 1 };
const arr2 = []u8{ 1, 0, 1 }; const arr2 = &[_]u8{ 1, 0, 1 };
const arr3 = []u8{ 1, 2, 1 }; const arr3 = &[_]u8{ 1, 2, 1 };
std.testing.expect(memcmp(base_arr[0..].ptr, arr1[0..].ptr, base_arr.len) == 0); std.testing.expect(memcmp(base_arr[0..], arr1[0..], base_arr.len) == 0);
std.testing.expect(memcmp(base_arr[0..].ptr, arr2[0..].ptr, base_arr.len) > 0); std.testing.expect(memcmp(base_arr[0..], arr2[0..], base_arr.len) > 0);
std.testing.expect(memcmp(base_arr[0..].ptr, arr3[0..].ptr, base_arr.len) < 0); std.testing.expect(memcmp(base_arr[0..], arr3[0..], base_arr.len) < 0);
} }
export fn bcmp(vl: [*]allowzero const u8, vr: [*]allowzero const u8, n: usize) isize { export fn bcmp(vl: [*]allowzero const u8, vr: [*]allowzero const u8, n: usize) callconv(.C) isize {
@setRuntimeSafety(false); @setRuntimeSafety(false);
var index: usize = 0; var index: usize = 0;
@ -170,30 +264,21 @@ export fn bcmp(vl: [*]allowzero const u8, vr: [*]allowzero const u8, n: usize) i
} }
test "test_bcmp" { test "test_bcmp" {
const base_arr = []u8{ 1, 1, 1 }; const base_arr = &[_]u8{ 1, 1, 1 };
const arr1 = []u8{ 1, 1, 1 }; const arr1 = &[_]u8{ 1, 1, 1 };
const arr2 = []u8{ 1, 0, 1 }; const arr2 = &[_]u8{ 1, 0, 1 };
const arr3 = []u8{ 1, 2, 1 }; const arr3 = &[_]u8{ 1, 2, 1 };
std.testing.expect(bcmp(base_arr[0..].ptr, arr1[0..].ptr, base_arr.len) == 0); std.testing.expect(bcmp(base_arr[0..], arr1[0..], base_arr.len) == 0);
std.testing.expect(bcmp(base_arr[0..].ptr, arr2[0..].ptr, base_arr.len) != 0); std.testing.expect(bcmp(base_arr[0..], arr2[0..], base_arr.len) != 0);
std.testing.expect(bcmp(base_arr[0..].ptr, arr3[0..].ptr, base_arr.len) != 0); std.testing.expect(bcmp(base_arr[0..], arr3[0..], base_arr.len) != 0);
} }
comptime { comptime {
if (builtin.mode != builtin.Mode.ReleaseFast and
builtin.mode != builtin.Mode.ReleaseSmall and
builtin.os.tag != .windows)
{
@export(__stack_chk_fail, .{ .name = "__stack_chk_fail" });
}
if (builtin.os.tag == .linux) { if (builtin.os.tag == .linux) {
@export(clone, .{ .name = "clone" }); @export(clone, .{ .name = "clone" });
} }
} }
fn __stack_chk_fail() callconv(.C) noreturn {
@panic("stack smashing detected");
}
// TODO we should be able to put this directly in std/linux/x86_64.zig but // TODO we should be able to put this directly in std/linux/x86_64.zig but
// it causes a segfault in release mode. this is a workaround of calling it // it causes a segfault in release mode. this is a workaround of calling it
@ -416,7 +501,6 @@ fn clone() callconv(.Naked) void {
\\ # move syscall number into r0 \\ # move syscall number into r0
\\ li 0, 120 \\ li 0, 120
\\ sc \\ sc
\\ # check for syscall error \\ # check for syscall error
\\ bns+ 1f # jump to label 1 if no summary overflow. \\ bns+ 1f # jump to label 1 if no summary overflow.
\\ #else \\ #else
@ -424,10 +508,8 @@ fn clone() callconv(.Naked) void {
\\1: \\1:
\\ # compare sc result with 0 \\ # compare sc result with 0
\\ cmpwi cr7, 3, 0 \\ cmpwi cr7, 3, 0
\\ # if not 0, jump to end \\ # if not 0, jump to end
\\ bne cr7, 2f \\ bne cr7, 2f
\\ #else: we're the child \\ #else: we're the child
\\ #call funcptr: move arg (d) into r3 \\ #call funcptr: move arg (d) into r3
\\ mr 3, 31 \\ mr 3, 31
@ -438,13 +520,11 @@ fn clone() callconv(.Naked) void {
\\ # mov SYS_exit into r0 (the exit param is already in r3) \\ # mov SYS_exit into r0 (the exit param is already in r3)
\\ li 0, 1 \\ li 0, 1
\\ sc \\ sc
\\2: \\2:
\\ # restore stack \\ # restore stack
\\ lwz 30, 0(1) \\ lwz 30, 0(1)
\\ lwz 31, 4(1) \\ lwz 31, 4(1)
\\ addi 1, 1, 16 \\ addi 1, 1, 16
\\ blr \\ blr
); );
}, },

View File

@ -284,11 +284,6 @@ comptime {
@export(@import("compiler_rt/stack_probe.zig").__chkstk, .{ .name = "__chkstk", .linkage = strong_linkage }); @export(@import("compiler_rt/stack_probe.zig").__chkstk, .{ .name = "__chkstk", .linkage = strong_linkage });
} }
if (is_mingw) {
@export(__stack_chk_fail, .{ .name = "__stack_chk_fail", .linkage = strong_linkage });
@export(__stack_chk_guard, .{ .name = "__stack_chk_guard", .linkage = strong_linkage });
}
switch (builtin.arch) { switch (builtin.arch) {
.i386 => { .i386 => {
@export(@import("compiler_rt/divti3.zig").__divti3, .{ .name = "__divti3", .linkage = linkage }); @export(@import("compiler_rt/divti3.zig").__divti3, .{ .name = "__divti3", .linkage = linkage });
@ -311,9 +306,6 @@ comptime {
else => {}, else => {},
} }
} else { } else {
if (std.Target.current.isGnuLibC() and builtin.link_libc) {
@export(__stack_chk_guard, .{ .name = "__stack_chk_guard", .linkage = linkage });
}
@export(@import("compiler_rt/divti3.zig").__divti3, .{ .name = "__divti3", .linkage = linkage }); @export(@import("compiler_rt/divti3.zig").__divti3, .{ .name = "__divti3", .linkage = linkage });
@export(@import("compiler_rt/modti3.zig").__modti3, .{ .name = "__modti3", .linkage = linkage }); @export(@import("compiler_rt/modti3.zig").__modti3, .{ .name = "__modti3", .linkage = linkage });
@export(@import("compiler_rt/multi3.zig").__multi3, .{ .name = "__multi3", .linkage = linkage }); @export(@import("compiler_rt/multi3.zig").__multi3, .{ .name = "__multi3", .linkage = linkage });
@ -337,14 +329,3 @@ pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn
unreachable; unreachable;
} }
} }
fn __stack_chk_fail() callconv(.C) noreturn {
@panic("stack smashing detected");
}
var __stack_chk_guard: usize = blk: {
var buf = [1]u8{0} ** @sizeOf(usize);
buf[@sizeOf(usize) - 1] = 255;
buf[@sizeOf(usize) - 2] = '\n';
break :blk @bitCast(usize, buf);
};

141
lib/std/special/ssp.zig Normal file
View File

@ -0,0 +1,141 @@
// SPDX-License-Identifier: MIT
// Copyright (c) 2015-2020 Zig Contributors
// This file is part of [zig](https://ziglang.org/), which is MIT licensed.
// The MIT license requires this copyright notice to be included in all copies
// and substantial portions of the software.
//
// Small Zig reimplementation of gcc's libssp.
//
// This library implements most of the builtins required by the stack smashing
// protection as implemented by gcc&clang.
const std = @import("std");
const builtin = std.builtin;
// Missing exports:
// - __gets_chk
// - __mempcpy_chk
// - __snprintf_chk
// - __sprintf_chk
// - __stpcpy_chk
// - __vsnprintf_chk
// - __vsprintf_chk
extern fn strncpy(dest: [*:0]u8, src: [*:0]const u8, n: usize) callconv(.C) [*:0]u8;
extern fn memset(dest: ?[*]u8, c: u8, n: usize) callconv(.C) ?[*]u8;
extern fn memcpy(noalias dest: ?[*]u8, noalias src: ?[*]const u8, n: usize) callconv(.C) ?[*]u8;
extern fn memmove(dest: ?[*]u8, src: ?[*]const u8, n: usize) callconv(.C) ?[*]u8;
// Avoid dragging in the runtime safety mechanisms into this .o file.
pub fn panic(msg: []const u8, error_return_trace: ?*builtin.StackTrace) noreturn {
@setCold(true);
if (@hasDecl(std.os, "abort"))
std.os.abort();
while (true) {}
}
export fn __stack_chk_fail() callconv(.C) noreturn {
@panic("stack smashing detected");
}
export fn __chk_fail() callconv(.C) noreturn {
@panic("buffer overflow detected");
}
// Emitted when targeting some architectures (eg. i386)
// XXX: This symbol should be hidden
export fn __stack_chk_fail_local() callconv(.C) noreturn {
__stack_chk_fail();
}
// XXX: Initialize the canary with random data
export var __stack_chk_guard: usize = blk: {
var buf = [1]u8{0} ** @sizeOf(usize);
buf[@sizeOf(usize) - 1] = 255;
buf[@sizeOf(usize) - 2] = '\n';
break :blk @bitCast(usize, buf);
};
export fn __strcpy_chk(dest: [*:0]u8, src: [*:0]const u8, dest_n: usize) callconv(.C) [*:0]u8 {
@setRuntimeSafety(false);
var i: usize = 0;
while (i < dest_n and src[i] != 0) : (i += 1) {
dest[i] = src[i];
}
if (i == dest_n) __chk_fail();
dest[i] = 0;
return dest;
}
export fn __strncpy_chk(dest: [*:0]u8, src: [*:0]const u8, n: usize, dest_n: usize) callconv(.C) [*:0]u8 {
if (dest_n < n) __chk_fail();
return strncpy(dest, src, n);
}
export fn __strcat_chk(dest: [*:0]u8, src: [*:0]const u8, dest_n: usize) callconv(.C) [*:0]u8 {
@setRuntimeSafety(false);
var avail = dest_n;
var dest_end: usize = 0;
while (avail > 0 and dest[dest_end] != 0) : (dest_end += 1) {
avail -= 1;
}
if (avail < 1) __chk_fail();
var i: usize = 0;
while (avail > 0 and src[i] != 0) : (i += 1) {
dest[dest_end + i] = src[i];
avail -= 1;
}
if (avail < 1) __chk_fail();
dest[dest_end + i] = 0;
return dest;
}
export fn __strncat_chk(dest: [*:0]u8, src: [*:0]const u8, n: usize, dest_n: usize) callconv(.C) [*:0]u8 {
@setRuntimeSafety(false);
var avail = dest_n;
var dest_end: usize = 0;
while (avail > 0 and dest[dest_end] != 0) : (dest_end += 1) {
avail -= 1;
}
if (avail < 1) __chk_fail();
var i: usize = 0;
while (avail > 0 and i < n and src[i] != 0) : (i += 1) {
dest[dest_end + i] = src[i];
avail -= 1;
}
if (avail < 1) __chk_fail();
dest[dest_end + i] = 0;
return dest;
}
export fn __memcpy_chk(noalias dest: ?[*]u8, noalias src: ?[*]const u8, n: usize, dest_n: usize) callconv(.C) ?[*]u8 {
if (dest_n < n) __chk_fail();
return memcpy(dest, src, n);
}
export fn __memmove_chk(dest: ?[*]u8, src: ?[*]const u8, n: usize, dest_n: usize) callconv(.C) ?[*]u8 {
if (dest_n < n) __chk_fail();
return memmove(dest, src, n);
}
export fn __memset_chk(dest: ?[*]u8, c: u8, n: usize, dest_n: usize) callconv(.C) ?[*]u8 {
if (dest_n < n) __chk_fail();
return memset(dest, c, n);
}

View File

@ -84,6 +84,9 @@ libcxxabi_static_lib: ?CRTFile = null,
/// Populated when we build the libunwind static library. A Job to build this is placed in the queue /// Populated when we build the libunwind static library. A Job to build this is placed in the queue
/// and resolved before calling linker.flush(). /// and resolved before calling linker.flush().
libunwind_static_lib: ?CRTFile = null, libunwind_static_lib: ?CRTFile = null,
/// Populated when we build the libssp static library. A Job to build this is placed in the queue
/// and resolved before calling linker.flush().
libssp_static_lib: ?CRTFile = null,
/// Populated when we build the libc static library. A Job to build this is placed in the queue /// Populated when we build the libc static library. A Job to build this is placed in the queue
/// and resolved before calling linker.flush(). /// and resolved before calling linker.flush().
libc_static_lib: ?CRTFile = null, libc_static_lib: ?CRTFile = null,
@ -160,6 +163,7 @@ const Job = union(enum) {
libunwind: void, libunwind: void,
libcxx: void, libcxx: void,
libcxxabi: void, libcxxabi: void,
libssp: void,
/// needed when producing a dynamic library or executable /// needed when producing a dynamic library or executable
libcompiler_rt: void, libcompiler_rt: void,
/// needed when not linking libc and using LLVM for code generation because it generates /// needed when not linking libc and using LLVM for code generation because it generates
@ -927,6 +931,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation {
(comp.getTarget().isWasm() and comp.bin_file.options.output_mode != .Obj); (comp.getTarget().isWasm() and comp.bin_file.options.output_mode != .Obj);
if (needs_compiler_rt_and_c and build_options.is_stage1) { if (needs_compiler_rt_and_c and build_options.is_stage1) {
try comp.work_queue.writeItem(.{ .libcompiler_rt = {} }); try comp.work_queue.writeItem(.{ .libcompiler_rt = {} });
// MinGW provides no libssp, use our own implementation.
if (comp.getTarget().isMinGW()) {
try comp.work_queue.writeItem(.{ .libssp = {} });
}
if (!comp.bin_file.options.link_libc) { if (!comp.bin_file.options.link_libc) {
try comp.work_queue.writeItem(.{ .zig_libc = {} }); try comp.work_queue.writeItem(.{ .zig_libc = {} });
} }
@ -978,6 +986,9 @@ pub fn destroy(self: *Compilation) void {
if (self.compiler_rt_static_lib) |*crt_file| { if (self.compiler_rt_static_lib) |*crt_file| {
crt_file.deinit(gpa); crt_file.deinit(gpa);
} }
if (self.libssp_static_lib) |*crt_file| {
crt_file.deinit(gpa);
}
if (self.libc_static_lib) |*crt_file| { if (self.libc_static_lib) |*crt_file| {
crt_file.deinit(gpa); crt_file.deinit(gpa);
} }
@ -1329,6 +1340,12 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor
fatal("unable to build compiler_rt: {}", .{@errorName(err)}); fatal("unable to build compiler_rt: {}", .{@errorName(err)});
}; };
}, },
.libssp => {
self.buildStaticLibFromZig("ssp.zig", &self.libssp_static_lib) catch |err| {
// TODO Expose this as a normal compile error rather than crashing here.
fatal("unable to build libssp: {}", .{@errorName(err)});
};
},
.zig_libc => { .zig_libc => {
self.buildStaticLibFromZig("c.zig", &self.libc_static_lib) catch |err| { self.buildStaticLibFromZig("c.zig", &self.libc_static_lib) catch |err| {
// TODO Expose this as a normal compile error rather than crashing here. // TODO Expose this as a normal compile error rather than crashing here.

View File

@ -1117,11 +1117,15 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void {
try argv.append(comp.libunwind_static_lib.?.full_object_path); try argv.append(comp.libunwind_static_lib.?.full_object_path);
} }
// compiler-rt and libc // compiler-rt, libc and libssp
if (is_exe_or_dyn_lib and !self.base.options.is_compiler_rt_or_libc) { if (is_exe_or_dyn_lib and !self.base.options.is_compiler_rt_or_libc) {
if (!self.base.options.link_libc) { if (!self.base.options.link_libc) {
try argv.append(comp.libc_static_lib.?.full_object_path); try argv.append(comp.libc_static_lib.?.full_object_path);
} }
// MinGW doesn't provide libssp symbols
if (target.abi.isGnu()) {
try argv.append(comp.libssp_static_lib.?.full_object_path);
}
// MSVC compiler_rt is missing some stuff, so we build it unconditionally but // MSVC compiler_rt is missing some stuff, so we build it unconditionally but
// and rely on weak linkage to allow MSVC compiler_rt functions to override ours. // and rely on weak linkage to allow MSVC compiler_rt functions to override ours.
try argv.append(comp.compiler_rt_static_lib.?.full_object_path); try argv.append(comp.compiler_rt_static_lib.?.full_object_path);