@@ -302,6 +302,7 @@ extract(const std::string& filename, const extract_cb& cb)
302302 filename,
303303 archive_error_string (arc)));
304304 }
305+ auto tmp_base = tmp_path.lexically_normal ();
305306
306307 log_info (" extracting %s to %s" , filename.c_str (), tmp_path.c_str ());
307308 while (true ) {
@@ -332,7 +333,12 @@ extract(const std::string& filename, const extract_cb& cb)
332333 if (strcmp (format_name, " raw" ) == 0 && filter_count >= 2 ) {
333334 desired_pathname = fs::path (filename).filename ();
334335 }
335- auto entry_path = tmp_path / desired_pathname;
336+ auto entry_path = (tmp_path / desired_pathname).lexically_normal ();
337+ auto rel = entry_path.lexically_relative (tmp_base);
338+ if (rel.empty () || lnav::filesystem::contains_dotdot (rel)) {
339+ log_warning (" ignoring naughty path: %s" , entry_path_str);
340+ continue ;
341+ }
336342 auto * prog = cb (
337343 entry_path,
338344 archive_entry_size_is_set (entry) ? archive_entry_size (entry) : -1 );
@@ -341,6 +347,44 @@ extract(const std::string& filename, const extract_cb& cb)
341347
342348 archive_entry_set_perm (
343349 wentry, S_IRUSR | (S_ISDIR (entry_mode) ? S_IXUSR | S_IWUSR : 0 ));
350+ if (S_ISLNK (entry_mode)) {
351+ auto * target_path_str = archive_entry_symlink (wentry);
352+ if (target_path_str == nullptr ) {
353+ log_warning (" symlink is null: %s" , entry_path_str);
354+ continue ;
355+ }
356+ auto link_target = fs::path (target_path_str);
357+ if (link_target.is_absolute ()) {
358+ // Confine to tmp_path: strip root, rejoin under tmp_path,
359+ // then express as relative from the symlink's own directory.
360+ auto confined = (tmp_path / link_target.relative_path ())
361+ .lexically_normal ();
362+ auto rewritten
363+ = confined.lexically_relative (entry_path.parent_path ());
364+ if (rewritten.empty ()) {
365+ log_warning (
366+ " ignoring symlink with unrepresentable target: %s" ,
367+ entry_path_str);
368+ continue ;
369+ }
370+ archive_entry_set_symlink (wentry, rewritten.c_str ());
371+ } else {
372+ // Relative target: verify it lands inside tmp_base, but leave
373+ // the literal target alone so kernel resolution matches.
374+ auto resolved = (entry_path.parent_path () / link_target)
375+ .lexically_normal ();
376+ auto target_rel = resolved.lexically_relative (tmp_base);
377+ if (target_rel.empty ()
378+ || lnav::filesystem::contains_dotdot (target_rel))
379+ {
380+ log_warning (
381+ " ignoring naughty symlink '%s' with target '%s'" ,
382+ entry_path_str,
383+ target_path_str);
384+ continue ;
385+ }
386+ }
387+ }
344388 r = archive_write_header (ext, wentry);
345389 if (r < ARCHIVE_OK) {
346390 return Err (
0 commit comments