#!/usr/bin/env ruby # frozen_string_literal: true # # Syd: rock-solid unikernel # lib/examples/ruby/rbshell.rb: Remote shell using syd via rbsyd # # Copyright (c) 2023 Ali Polatel # SPDX-License-Identifier: GPL-3.0 require "fileutils" require "socket" require "tempfile" require "tmpdir" require "syd" SYD_CONFIG = <<~SYD # Sandboxing types. # Enable all except stat sandboxing. sandbox/read:on sandbox/stat:off sandbox/write:on sandbox/exec:on sandbox/net:on sandbox/pid:on sandbox/mem:on # Define a modest limit for PID sandboxing pid/max:64 # Define modest limits for Memory sandboxing mem/max:256M mem/vm_max:2G # Allow /dev/null allow/read+/dev/null allow/write+/dev/null # Allow reading dynamic libraries under system paths. allow/read+/lib*/** allow/read+/usr/**/lib*/** # Allow PTYs allow/read+/dev/ptmx allow/write+/dev/ptmx allow/read+/dev/pty/[0-9]* allow/write+/dev/pty/[0-9]* # Allow execution of binaries under system paths. allow/exec+/bin/* allow/exec+/usr/**/bin/* # Allow /proc but deny pid1=syd allow/read+/proc/*** allow/write+/proc/*** deny/read+/proc/1/*** deny/write+/proc/1/*** SYD def main port = ARGV[0] || "65432" begin Syd.check rescue StandardError => e warn "Not running under syd: #{e}" puts 'Run "syd -plib -pcontainer ./rbshell.rb"' exit 1 end puts "Initializing" temp_dir = Dir.mktmpdir("rbshell-tmp-") puts "Temporary directory created: #{temp_dir}" at_exit { FileUtils.remove_entry(temp_dir) } Dir.chdir(temp_dir) abs_path = File.absolute_path(temp_dir) cwd = File.realpath(abs_path) ENV["HOME"] = cwd Tempfile.open("syd_config") do |file| file.write(SYD_CONFIG) file.rewind Syd.load(file.fileno) puts "Load: ok" end Syd.allow_read_add("#{cwd}/***") puts "AllowReadAdd(#{cwd}/***): ok" Syd.allow_write_add("#{cwd}/**") puts "AllowWriteAdd(#{cwd}/**): ok" Syd.allow_net_bind_add("127.0.0.1!#{port}") puts "AllowNetBind(127.0.0.1!#{port}): ok" Syd.lock(Syd::LOCK_ON) puts "LockOn: ok" start_tcp_server(port) end def start_tcp_server(port) server = TCPServer.new("127.0.0.1", port) puts "Listening on localhost:#{port}" loop do Thread.start(server.accept) do |client| handle_client(client) end end ensure server&.close end def handle_client(client) # Use 'r+' for bidirectional communication and merge STDERR with STDOUT IO.popen("/bin/sh", "r+", err: %i[child out]) do |shell| Thread.new { IO.copy_stream(client, shell) } IO.copy_stream(shell, client) end rescue StandardError => e client.puts "Error running shell: #{e}" ensure client.close end main if __FILE__ == $PROGRAM_NAME