authorgravatar for bratishkaerik@landless-city.netEric Joldasov <bratishkaerik@landless-city.net> 2024-04-23 23:19:19+05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-04-23 17:22:04-07:00
log6de152ec7cb906362b85ed86a8cf6a22b909b4df
tree6d4c7795c90b07171700abbb4fe48a9f6411eed7
parentcc25f754780eaed699ddc468601a954b50846fb1

std.zig.system: fix ELF file search

* Adjust buffer length a bit. * Fix detecting if file is a script. Logic below was unreachable, because 99% of scripts failed "At least 255 bytes long" check and were detected as ELF files. It should be "At least 4" instead (minimum value of "ELF magic length" and "smallest possible interpreter path length"). * Fix parsing interpreter path, when text after shebang: 1. does not have newline, 2. has leading spaces and tabs, 3. separates interpreter and arguments by tab or NUL. * Remove empty error set from `defaultAbiAndDynamicLinker`. Signed-off-by: Eric Joldasov <bratishkaerik@landless-city.net>

1 files changed, 57 insertions(+), 16 deletions(-)

lib/std/zig/system.zig+57-16
......@@ -983,7 +983,7 @@ fn detectAbiAndDynamicLinker(
983983
984984 // Best case scenario: the executable is dynamically linked, and we can iterate
985985 // over our own shared objects and find a dynamic linker.
986 const elf_file = blk: {
986 const elf_file = elf_file: {
987987 // This block looks for a shebang line in /usr/bin/env,
988988 // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
989989 // doing the same logic recursively in case it finds another shebang line.
......@@ -995,9 +995,22 @@ fn detectAbiAndDynamicLinker(
995995 // Haiku does not have a /usr root directory.
996996 .haiku => "/bin/env",
997997 };
998 // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1)
999 var buffer: [258]u8 = undefined;
998
999 // According to `man 2 execve`:
1000 //
1001 // The kernel imposes a maximum length on the text
1002 // that follows the "#!" characters at the start of a script;
1003 // characters beyond the limit are ignored.
1004 // Before Linux 5.1, the limit is 127 characters.
1005 // Since Linux 5.1, the limit is 255 characters.
1006 //
1007 // Tests show that bash and zsh consider 255 as total limit,
1008 // *including* "#!" characters and ignoring newline.
1009 // For safety, we set max length as 255 + \n (1).
1010 var buffer: [255 + 1]u8 = undefined;
10001011 while (true) {
1012 // Interpreter path can be relative on Linux, but
1013 // for simplicity we are asserting it is an absolute path.
10011014 const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
10021015 error.NoSpaceLeft => unreachable,
10031016 error.NameTooLong => unreachable,
......@@ -1027,27 +1040,55 @@ fn detectAbiAndDynamicLinker(
10271040
10281041 else => |e| return e,
10291042 };
1030 errdefer file.close();
1031
1032 const len = preadAtLeast(file, &buffer, 0, buffer.len) catch |err| switch (err) {
1043 var is_elf_file = false;
1044 defer if (is_elf_file == false) file.close();
1045
1046 // Shortest working interpreter path is "#!/i" (4)
1047 // (interpreter is "/i", assuming all pathes are absolute, like in above comment).
1048 // ELF magic number length is also 4.
1049 //
1050 // If file is shorter than that, it is definitely not ELF file
1051 // nor file with "shebang" line.
1052 const min_len: usize = 4;
1053
1054 const len = preadAtLeast(file, &buffer, 0, min_len) catch |err| switch (err) {
10331055 error.UnexpectedEndOfFile,
10341056 error.UnableToReadElfFile,
1035 => break :blk file,
1057 => return defaultAbiAndDynamicLinker(cpu, os, query),
10361058
10371059 else => |e| return e,
10381060 };
1039 const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file;
1040 const line = buffer[0..newline];
1041 if (!mem.startsWith(u8, line, "#!")) break :blk file;
1042 var it = mem.tokenizeScalar(u8, line[2..], ' ');
1043 file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, query);
1044 file.close();
1061 const content = buffer[0..len];
1062
1063 if (mem.eql(u8, content[0..4], std.elf.MAGIC)) {
1064 // It is very likely ELF file!
1065 is_elf_file = true;
1066 break :elf_file file;
1067 } else if (mem.eql(u8, content[0..2], "#!")) {
1068 // We detected shebang, now parse entire line.
1069
1070 // Trim leading "#!", spaces and tabs.
1071 const trimmed_line = mem.trimLeft(u8, content[2..], &.{ ' ', '\t' });
1072
1073 // This line can have:
1074 // * Interpreter path only,
1075 // * Interpreter path and arguments, all separated by space, tab or NUL character.
1076 // And optionally newline at the end.
1077 const path_maybe_args = mem.trimRight(u8, trimmed_line, "\n");
1078
1079 // Separate path and args.
1080 const path_end = mem.indexOfAny(u8, path_maybe_args, &.{ ' ', '\t', 0 }) orelse path_maybe_args.len;
1081
1082 file_name = path_maybe_args[0..path_end];
1083 continue;
1084 } else {
1085 // Not a ELF file, not a shell script with "shebang line", invalid duck.
1086 return defaultAbiAndDynamicLinker(cpu, os, query);
1087 }
10451088 }
10461089 };
10471090 defer elf_file.close();
10481091
1049 // If Zig is statically linked, such as via distributed binary static builds, the above
1050 // trick (block self_exe) won't work. The next thing we fall back to is the same thing, but for elf_file.
10511092 // TODO: inline this function and combine the buffer we already read above to find
10521093 // the possible shebang line with the buffer we use for the ELF header.
10531094 return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) {
......@@ -1075,7 +1116,7 @@ fn detectAbiAndDynamicLinker(
10751116 };
10761117}
10771118
1078fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Query) !Target {
1119fn defaultAbiAndDynamicLinker(cpu: Target.Cpu, os: Target.Os, query: Target.Query) Target {
10791120 const abi = query.abi orelse Target.Abi.default(cpu.arch, os);
10801121 return .{
10811122 .cpu = cpu,