authorgravatar for jari.vetoniemi@cloudef.pwJari Vetoniemi <jari.vetoniemi@cloudef.pw> 2024-03-20 18:09:36+09:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2024-08-24 05:32:53-07:00
log2d7c26cc66facc401b37c26cb17bbae1da00d170
treee46ecd9beaf22c0e80c24c1891377ef6822469ed
parent9374725088fa077b61d819ed36341b20f8f3bcf7

ElfDynLib: resolve lib from system paths

Implements the base that should usually work that is - Check LD_LIBRARY_PATH if the binary is no setuid setgid binary - Check /lib, /usr/lib, in that order The missing parts are: - DT_RPATH and DT_RUNPATH handling from the calling executable - Reading /etc/ld.so.cache For more details check man page of dlopen(3)

1 files changed, 64 insertions(+), 1 deletions(-)

lib/std/dynamic_library.zig+64-1
......@@ -147,9 +147,72 @@ pub const ElfDynLib = struct {
147147
148148 pub const Error = ElfDynLibError;
149149
150 fn openPath(path: []const u8) !std.fs.Dir {
151 if (path.len == 0) return error.NotDir;
152 var parts = std.mem.tokenizeScalar(u8, path, '/');
153 var parent = if (path[0] == '/') try std.fs.cwd().openDir("/", .{}) else std.fs.cwd();
154 while (parts.next()) |part| {
155 const child = try parent.openDir(part, .{});
156 parent.close();
157 parent = child;
158 }
159 return parent;
160 }
161
162 fn resolveFromSearchPath(search_path: []const u8, file_name: []const u8, delim: u8) ?posix.fd_t {
163 var paths = std.mem.tokenizeScalar(u8, search_path, delim);
164 while (paths.next()) |p| {
165 var dir = openPath(p) catch continue;
166 defer dir.close();
167 const fd = posix.openat(dir.fd, file_name, .{
168 .ACCMODE = .RDONLY,
169 .CLOEXEC = true,
170 }, 0) catch continue;
171 return fd;
172 }
173 return null;
174 }
175
176 fn resolveFromParent(dir_path: []const u8, file_name: []const u8) ?posix.fd_t {
177 var dir = std.fs.cwd().openDir(dir_path, .{}) catch return null;
178 defer dir.close();
179 return posix.openat(dir.fd, file_name, .{
180 .ACCMODE = .RDONLY,
181 .CLOEXEC = true,
182 }, 0) catch null;
183 }
184
185 // This implements enough to be able to load system libraries in general
186 // Places where it differs from dlopen:
187 // - DT_RPATH of the calling binary is not used as a search path
188 // - DT_RUNPATH of the calling binary is not used as a search path
189 // - /etc/ld.so.cache is not read
190 fn resolveFromName(path_or_name: []const u8) !posix.fd_t {
191 // If filename contains a slash ("/"), then it is interpreted as a (relative or absolute) pathname
192 if (std.mem.indexOfScalarPos(u8, path_or_name, 0, '/')) |_| {
193 return posix.open(path_or_name, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
194 }
195
196 // Only read LD_LIBRARY_PATH if the binary is not setuid/setgid
197 if (std.os.linux.geteuid() == std.os.linux.getuid() and
198 std.os.linux.getegid() == std.os.linux.getgid())
199 {
200 if (posix.getenvZ("LD_LIBRARY_PATH")) |ld_library_path| {
201 if (resolveFromSearchPath(ld_library_path, path_or_name, ':')) |fd| {
202 return fd;
203 }
204 }
205 }
206
207 // Lastly the directories /lib and /usr/lib are searched (in this exact order)
208 if (resolveFromParent("/lib", path_or_name)) |fd| return fd;
209 if (resolveFromParent("/usr/lib", path_or_name)) |fd| return fd;
210 return error.FileNotFound;
211 }
212
150213 /// Trusts the file. Malicious file will be able to execute arbitrary code.
151214 pub fn open(path: []const u8) Error!ElfDynLib {
152 const fd = try posix.open(path, .{ .ACCMODE = .RDONLY, .CLOEXEC = true }, 0);
215 const fd = try resolveFromName(path);
153216 defer posix.close(fd);
154217
155218 const stat = try posix.fstat(fd);