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(...@@ -983,7 +983,7 @@ fn detectAbiAndDynamicLinker(
983983
984 // Best case scenario: the executable is dynamically linked, and we can iterate984 // Best case scenario: the executable is dynamically linked, and we can iterate
985 // over our own shared objects and find a dynamic linker.985 // over our own shared objects and find a dynamic linker.
986 const elf_file = blk: {986 const elf_file = elf_file: {
987 // This block looks for a shebang line in /usr/bin/env,987 // This block looks for a shebang line in /usr/bin/env,
988 // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,988 // if it finds one, then instead of using /usr/bin/env as the ELF file to examine, it uses the file it references instead,
989 // doing the same logic recursively in case it finds another shebang line.989 // doing the same logic recursively in case it finds another shebang line.
...@@ -995,9 +995,22 @@ fn detectAbiAndDynamicLinker(...@@ -995,9 +995,22 @@ fn detectAbiAndDynamicLinker(
995 // Haiku does not have a /usr root directory.995 // Haiku does not have a /usr root directory.
996 .haiku => "/bin/env",996 .haiku => "/bin/env",
997 };997 };
998 // #! (2) + 255 (max length of shebang line since Linux 5.1) + \n (1)998
999 var buffer: [258]u8 = undefined;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;
1000 while (true) {1011 while (true) {
1012 // Interpreter path can be relative on Linux, but
1013 // for simplicity we are asserting it is an absolute path.
1001 const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {1014 const file = fs.openFileAbsolute(file_name, .{}) catch |err| switch (err) {
1002 error.NoSpaceLeft => unreachable,1015 error.NoSpaceLeft => unreachable,
1003 error.NameTooLong => unreachable,1016 error.NameTooLong => unreachable,
...@@ -1027,27 +1040,55 @@ fn detectAbiAndDynamicLinker(...@@ -1027,27 +1040,55 @@ fn detectAbiAndDynamicLinker(
10271040
1028 else => |e| return e,1041 else => |e| return e,
1029 };1042 };
1030 errdefer file.close();1043 var is_elf_file = false;
10311044 defer if (is_elf_file == false) file.close();
1032 const len = preadAtLeast(file, &buffer, 0, buffer.len) catch |err| switch (err) {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) {
1033 error.UnexpectedEndOfFile,1055 error.UnexpectedEndOfFile,
1034 error.UnableToReadElfFile,1056 error.UnableToReadElfFile,
1035 => break :blk file,1057 => return defaultAbiAndDynamicLinker(cpu, os, query),
10361058
1037 else => |e| return e,1059 else => |e| return e,
1038 };1060 };
1039 const newline = mem.indexOfScalar(u8, buffer[0..len], '\n') orelse break :blk file;1061 const content = buffer[0..len];
1040 const line = buffer[0..newline];1062
1041 if (!mem.startsWith(u8, line, "#!")) break :blk file;1063 if (mem.eql(u8, content[0..4], std.elf.MAGIC)) {
1042 var it = mem.tokenizeScalar(u8, line[2..], ' ');1064 // It is very likely ELF file!
1043 file_name = it.next() orelse return defaultAbiAndDynamicLinker(cpu, os, query);1065 is_elf_file = true;
1044 file.close();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 }
1045 }1088 }
1046 };1089 };
1047 defer elf_file.close();1090 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.
1051 // TODO: inline this function and combine the buffer we already read above to find1092 // TODO: inline this function and combine the buffer we already read above to find
1052 // the possible shebang line with the buffer we use for the ELF header.1093 // the possible shebang line with the buffer we use for the ELF header.
1053 return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) {1094 return abiAndDynamicLinkerFromFile(elf_file, cpu, os, ld_info_list, query) catch |err| switch (err) {
...@@ -1075,7 +1116,7 @@ fn detectAbiAndDynamicLinker(...@@ -1075,7 +1116,7 @@ fn detectAbiAndDynamicLinker(
1075 };1116 };
1076}1117}
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 {
1079 const abi = query.abi orelse Target.Abi.default(cpu.arch, os);1120 const abi = query.abi orelse Target.Abi.default(cpu.arch, os);
1080 return .{1121 return .{
1081 .cpu = cpu,1122 .cpu = cpu,