linux.rb (3209B)
1 # frozen_string_literal: true 2 3 require 'fileutils' 4 require 'set' 5 6 linux_rb_so = File.join __dir__, 'linux_rb.so' 7 linux_rb_c = File.join __dir__, 'linux_rb.c' 8 if File.exist?(linux_rb_c) && (!File.exist?(linux_rb_so) || 9 File.mtime(linux_rb_so) < File.mtime(linux_rb_c)) 10 require 'rbconfig' 11 cfg = RbConfig::CONFIG 12 cmd = "#{cfg['DLDSHARED']} #{cfg['CCDLFLAGS']} #{cfg['CFLAGS']} #{cfg['LDFLAGS']}". 13 split(/ +/) 14 cmd += %W(-I#{cfg['rubyarchhdrdir']} -I#{cfg['rubyhdrdir']} -g -o #{linux_rb_so} 15 #{linux_rb_c}) 16 system *cmd or fail 'Failed to compile linux_rb' 17 end 18 require linux_rb_so 19 20 module Linux 21 extend self 22 def clonens &blk 23 uid = Process.uid 24 gid = Process.gid 25 26 clone CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | \ 27 CLONE_NEWNET do 28 uid_gid_map uid, gid 29 mount '/', '/', nil, MS_REC | MS_PRIVATE 30 mount 'none', '/proc', 'proc', MS_NOSUID | MS_NODEV | MS_RDONLY 31 32 FileUtils.mkdir_p 'tmp' 33 mount 'tmp', '/tmp', nil, MS_BIND 34 mount nil, '/tmp', nil, MS_BIND | MS_REMOUNT | MS_NOSUID | MS_NODEV 35 36 # Make home unwriteable 37 cwd = File.expand_path('.') 38 mount cwd, cwd, nil, MS_BIND rescue nil 39 mount nil, cwd, nil, MS_BIND | MS_REMOUNT | MS_NOSUID | MS_NODEV 40 make_ro [cwd, '/tmp'] 41 42 # Try to automatically reap children. This does some magic so normal wait 43 # still works while still reaping most children. A single zombie 44 # conhost.exe seem to remain after executing wine, but it's much better 45 # that the 4 zombie processes I see without this. 46 Signal.trap 'CHLD', 'IGNORE' 47 blk.call 48 rescue Exception 49 puts $!.full_message 50 exit! 1 51 end 52 end 53 54 def uid_gid_map uid, gid, pid = 'self' 55 File.write "/proc/#{pid}/setgroups", 'deny' 56 File.write "/proc/#{pid}/gid_map", "#{gid} #{gid} 1\n" 57 File.write "/proc/#{pid}/uid_map", "#{uid} #{uid} 1\n" 58 end 59 60 def make_ro except 61 # read the full file in advance, because we will change the mounts that can 62 # change the file, and I have no freakin idea what happens in that case. 63 # Also mount points can be hidden by later mounts, ignore them as trying to 64 # remount them gives an EINVAL 65 points = {} 66 File.foreach('/proc/self/mountinfo').each do |l| 67 fields = l.split ' ' 68 opts = fields[5].split ',' 69 70 point = fields[4] 71 point_slash = point.end_with?('/') ? point : point + '/' 72 points.reject! {|p,ro| p.start_with? point_slash } 73 points[point] = opts.member? 'ro' 74 end 75 76 points.each do |point,ro| 77 next if except.include? point 78 # skip already RO mounts, depending on the sandbox sometimes they can't be 79 # remounted as RO, but that's not a problem... 80 next if ro 81 82 begin 83 mount nil, point, nil, MS_BIND | MS_REMOUNT | MS_RDONLY 84 rescue Errno::EPERM, Errno::EACCES 85 # /sys usually gives EPERM when trying to remount, ignore it... 86 $stderr.puts " \e[31;1m*\e[0m Failed to make #{point.inspect} ro" 87 end 88 end 89 end 90 91 def clonens_wait &blk 92 pid = clonens &blk 93 Process.wait pid 94 fail "Subprocess failed: #{$?}" unless $?.success? 95 end 96 end