You cannot select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
97 lines
3.1 KiB
Ruby
97 lines
3.1 KiB
Ruby
# frozen_string_literal: true
|
|
|
|
require 'fileutils'
|
|
require 'set'
|
|
|
|
linux_rb_so = File.join __dir__, 'linux_rb.so'
|
|
linux_rb_c = File.join __dir__, 'linux_rb.c'
|
|
if File.exist?(linux_rb_c) && (!File.exist?(linux_rb_so) ||
|
|
File.mtime(linux_rb_so) < File.mtime(linux_rb_c))
|
|
require 'rbconfig'
|
|
cfg = RbConfig::CONFIG
|
|
cmd = "#{cfg['DLDSHARED']} #{cfg['CCDLFLAGS']} #{cfg['CFLAGS']} #{cfg['LDFLAGS']}".
|
|
split(/ +/)
|
|
cmd += %W(-I#{cfg['rubyarchhdrdir']} -I#{cfg['rubyhdrdir']} -g -o #{linux_rb_so}
|
|
#{linux_rb_c})
|
|
system *cmd or fail 'Failed to compile linux_rb'
|
|
end
|
|
require linux_rb_so
|
|
|
|
module Linux
|
|
extend self
|
|
def clonens &blk
|
|
uid = Process.uid
|
|
gid = Process.gid
|
|
|
|
clone CLONE_NEWUSER | CLONE_NEWPID | CLONE_NEWNS | CLONE_NEWIPC | \
|
|
CLONE_NEWNET do
|
|
uid_gid_map uid, gid
|
|
mount '/', '/', nil, MS_REC | MS_PRIVATE
|
|
mount 'none', '/proc', 'proc', MS_NOSUID | MS_NODEV | MS_RDONLY
|
|
|
|
FileUtils.mkdir_p 'tmp'
|
|
mount 'tmp', '/tmp', nil, MS_BIND
|
|
mount nil, '/tmp', nil, MS_BIND | MS_REMOUNT | MS_NOSUID | MS_NODEV
|
|
|
|
# Make home unwriteable
|
|
cwd = File.expand_path('.')
|
|
mount cwd, cwd, nil, MS_BIND rescue nil
|
|
mount nil, cwd, nil, MS_BIND | MS_REMOUNT | MS_NOSUID | MS_NODEV
|
|
make_ro [cwd, '/tmp']
|
|
|
|
# Try to automatically reap children. This does some magic so normal wait
|
|
# still works while still reaping most children. A single zombie
|
|
# conhost.exe seem to remain after executing wine, but it's much better
|
|
# that the 4 zombie processes I see without this.
|
|
Signal.trap 'CHLD', 'IGNORE'
|
|
blk.call
|
|
rescue Exception
|
|
puts $!.full_message
|
|
exit! 1
|
|
end
|
|
end
|
|
|
|
def uid_gid_map uid, gid, pid = 'self'
|
|
File.write "/proc/#{pid}/setgroups", 'deny'
|
|
File.write "/proc/#{pid}/gid_map", "#{gid} #{gid} 1\n"
|
|
File.write "/proc/#{pid}/uid_map", "#{uid} #{uid} 1\n"
|
|
end
|
|
|
|
def make_ro except
|
|
# read the full file in advance, because we will change the mounts that can
|
|
# change the file, and I have no freakin idea what happens in that case.
|
|
# Also mount points can be hidden by later mounts, ignore them as trying to
|
|
# remount them gives an EINVAL
|
|
points = {}
|
|
File.foreach('/proc/self/mountinfo').each do |l|
|
|
fields = l.split ' '
|
|
opts = fields[5].split ','
|
|
|
|
point = fields[4]
|
|
point_slash = point.end_with?('/') ? point : point + '/'
|
|
points.reject! {|p,ro| p.start_with? point_slash }
|
|
points[point] = opts.member? 'ro'
|
|
end
|
|
|
|
points.each do |point,ro|
|
|
next if except.include? point
|
|
# skip already RO mounts, depending on the sandbox sometimes they can't be
|
|
# remounted as RO, but that's not a problem...
|
|
next if ro
|
|
|
|
begin
|
|
mount nil, point, nil, MS_BIND | MS_REMOUNT | MS_RDONLY
|
|
rescue Errno::EPERM, Errno::EACCES
|
|
# /sys usually gives EPERM when trying to remount, ignore it...
|
|
$stderr.puts " \e[31;1m*\e[0m Failed to make #{point.inspect} ro"
|
|
end
|
|
end
|
|
end
|
|
|
|
def clonens_wait &blk
|
|
pid = clonens &blk
|
|
Process.wait pid
|
|
fail "Subprocess failed: #{$?}" unless $?.success?
|
|
end
|
|
end
|