* [gentoo-portage-dev] [PATCH] Support escaping network-sandbox through SOCKSv5 proxy
@ 2015-01-25 11:29 Michał Górny
2015-01-25 14:01 ` Michał Górny
0 siblings, 1 reply; 2+ messages in thread
From: Michał Górny @ 2015-01-25 11:29 UTC (permalink / raw
To: gentoo-portage-dev; +Cc: Michał Górny
Add a minimal SOCKSv5-over-UNIX-socket proxy to Portage, and start it
whenever ebuilds are started with network-sandbox enabled. Pass the
socket address in PORTAGE_SOCKS5_PROXY and DISTCC_SOCKS_PROXY variables.
The proxy can be used to escape the network sandbox whenever network
access is really desired, e.g. in distcc.
The proxy currently supports IPv4 only, and does not report bound
address (reports 0.0.0.0:0). No authentication is supported (UNIX
sockets provide a security layer).
---
bin/save-ebuild-env.sh | 5 +-
bin/socks5-server.py | 171 +++++++++++++++++++++
.../package/ebuild/_config/special_env_vars.py | 2 +-
pym/portage/package/ebuild/doebuild.py | 7 +
pym/portage/util/socks5.py | 45 ++++++
5 files changed, 227 insertions(+), 3 deletions(-)
create mode 100644 bin/socks5-server.py
create mode 100644 pym/portage/util/socks5.py
diff --git a/bin/save-ebuild-env.sh b/bin/save-ebuild-env.sh
index c6bffb5..477ed28 100644
--- a/bin/save-ebuild-env.sh
+++ b/bin/save-ebuild-env.sh
@@ -92,7 +92,7 @@ __save_ebuild_env() {
# portage config variables and variables set directly by portage
unset ACCEPT_LICENSE BAD BRACKET BUILD_PREFIX COLS \
- DISTCC_DIR DISTDIR DOC_SYMLINKS_DIR \
+ DISTCC_DIR DISTCC_SOCKS5_PROXY DISTDIR DOC_SYMLINKS_DIR \
EBUILD_FORCE_TEST EBUILD_MASTER_PID \
ECLASS_DEPTH ENDCOL FAKEROOTKEY \
GOOD HILITE HOME \
@@ -105,7 +105,8 @@ __save_ebuild_env() {
PORTAGE_DOHTML_WARN_ON_SKIPPED_FILES \
PORTAGE_NONFATAL PORTAGE_QUIET \
PORTAGE_SANDBOX_DENY PORTAGE_SANDBOX_PREDICT \
- PORTAGE_SANDBOX_READ PORTAGE_SANDBOX_WRITE PREROOTPATH \
+ PORTAGE_SANDBOX_READ PORTAGE_SANDBOX_WRITE \
+ PORTAGE_SOCKS5_PROXY PREROOTPATH \
QA_INTERCEPTORS \
RC_DEFAULT_INDENT RC_DOT_PATTERN RC_ENDCOL RC_INDENTATION \
ROOT ROOTPATH RPMDIR TEMP TMP TMPDIR USE_EXPAND \
diff --git a/bin/socks5-server.py b/bin/socks5-server.py
new file mode 100644
index 0000000..4795dcc
--- /dev/null
+++ b/bin/socks5-server.py
@@ -0,0 +1,171 @@
+#!/usr/bin/env python
+# SOCKSv5 proxy server for network-sandbox
+# Copyright 2015 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+import asyncore
+import errno
+import socket
+import struct
+import sys
+
+
+class ProxyConnection(asyncore.dispatcher_with_send):
+ _proxy_conn = None
+
+ def __init__(self, host, port, proxy_conn):
+ self._proxy_conn = proxy_conn
+ asyncore.dispatcher_with_send.__init__(self)
+ # TODO: how to support IPv6? ugly fail-then-reinit?
+ self.create_socket(socket.AF_INET, socket.SOCK_STREAM)
+ self.connect((host, port))
+
+ def handle_read(self):
+ buf = self.recv(4096)
+ self._proxy_conn.send(buf)
+
+ def handle_connect(self):
+ self._proxy_conn.send_connected()
+
+ def handle_close(self):
+ self._proxy_conn.close()
+
+ def handle_error(self):
+ e, v, tb = sys.exc_info()
+ if e is OSError:
+ self._proxy_conn.send_failure(v.errno)
+ self.close()
+ else:
+ raise
+
+
+class ProxyHandler(asyncore.dispatcher_with_send):
+ _my_buf = b''
+ _my_conn = None
+ _my_state = 0
+
+ def handle_read(self):
+ rd = self.recv(4096)
+ if not rd:
+ return
+
+ self._my_buf += rd
+ if self._my_state == 0: # waiting for hello
+ if len(self._my_buf) >= 3:
+ vers, method_no = struct.unpack('!BB', self._my_buf[:2])
+ if vers != 0x05:
+ self.close()
+ return
+ if len(self._my_buf) >= 2 + method_no:
+ for method in self._my_buf[2:2+method_no]:
+ if method == 0x00:
+ break
+ else:
+ # no supported method
+ method = 0xFF
+
+ repl = struct.pack('!BB', 0x05, method)
+ self.send(repl)
+ if method == 0xFF:
+ self.close()
+ return
+ else:
+ self._my_buf = self._my_buf[2+method_no:]
+ self._my_state = 1
+
+ if self._my_state == 1: # waiting for request
+ if len(self._my_buf) >= 5:
+ vers, cmd, rsv, atyp = struct.unpack('!BBBB', self._my_buf[:4])
+ if vers != 0x05 or rsv != 0x00:
+ self.close()
+ return
+
+ rpl = 0x00
+ addr_len = 0
+ if cmd != 0x01: # CONNECT
+ rpl = 0x07 # command not supported
+ elif atyp == 0x01: # IPv4
+ addr_len = 4
+ elif atyp == 0x03: # domain name
+ addr_len, = struct.unpack('!B', self._my_buf[4:5])
+ addr_len += 1 # length field
+# elif atyp == 0x04: # IPv6
+# addr_len = 16
+ else:
+ rpl = 0x08 # address type not supported
+
+ # terminate early
+ if rpl != 0x00:
+ repl = struct.pack('!BBBBLH', 0x05, rpl, 0x00, 0x01,
+ 0x00000000, 0x0000)
+ self.send(repl)
+ self.close()
+ return
+
+ if len(self._my_buf) >= 6 + addr_len:
+ if atyp == 0x01:
+ addr = socket.inet_ntoa(self._my_buf[5:5+addr_len])
+ elif atyp == 0x03:
+ addr = self._my_buf[5:4+addr_len]
+# elif atyp == 0x04:
+# addr = socket.inet_ntop(socket.AF_INET6, self._my_buf[5:5+addr_len])
+ port, = struct.unpack('!H', self._my_buf[4+addr_len:6+addr_len])
+
+ self._my_buf = self._my_buf[6+addr_len:]
+ self._my_state = 2
+ self._my_conn = ProxyConnection(addr, port, self)
+
+ if self._my_state == 2: # connecting
+ pass
+
+ if self._my_state == 3: # connected
+ self._my_conn.send(self._my_buf)
+ self._my_buf = b''
+
+ def handle_close(self):
+ if self._my_conn is not None:
+ self._my_conn.close()
+
+ def send_connected(self):
+ repl = struct.pack('!BBBBLH', 0x05, 0x00, 0x00, 0x01,
+ 0x00000000, 0x0000)
+ self.send(repl)
+ self._my_state = 3
+
+ def send_failure(self, err):
+ rpl = 0x01 # general error
+ if err in (errno.ENETUNREACH, errno.ENETDOWN):
+ rpl = 0x03 # network unreachable
+ elif err in (errno.EHOSTUNREACH, errno.EHOSTDOWN):
+ rpl = 0x04 # host unreachable
+ elif err in (errno.ECONNREFUSED, errno.ETIMEDOUT):
+ rpl = 0x05 # connection refused
+
+ repl = struct.pack('!BBBBLH', 0x05, rpl, 0x00, 0x01,
+ 0x00000000, 0x0000)
+ self.send(repl)
+ self.close()
+
+
+class ProxyServer(asyncore.dispatcher):
+ def __init__(self, socket_path):
+ asyncore.dispatcher.__init__(self)
+ self.create_socket(socket.AF_UNIX, socket.SOCK_STREAM)
+ self.set_reuse_addr()
+ self.bind(socket_path)
+ self.listen(5)
+
+ def handle_accepted(self, sock, addr):
+ h = ProxyHandler(sock)
+
+
+if __name__ == '__main__':
+ if len(sys.argv) != 2:
+ print('Usage: %s <socket-path>' % sys.argv[0])
+ sys.exit(1)
+
+ try:
+ s = ProxyServer(sys.argv[1])
+ asyncore.loop()
+ except KeyboardInterrupt:
+ sys.exit(0)
diff --git a/pym/portage/package/ebuild/_config/special_env_vars.py b/pym/portage/package/ebuild/_config/special_env_vars.py
index 6bb3c95..905d5e7 100644
--- a/pym/portage/package/ebuild/_config/special_env_vars.py
+++ b/pym/portage/package/ebuild/_config/special_env_vars.py
@@ -71,7 +71,7 @@ environ_whitelist += [
"PORTAGE_PYM_PATH", "PORTAGE_PYTHON",
"PORTAGE_PYTHONPATH", "PORTAGE_QUIET",
"PORTAGE_REPO_NAME", "PORTAGE_REPOSITORIES", "PORTAGE_RESTRICT",
- "PORTAGE_SIGPIPE_STATUS",
+ "PORTAGE_SIGPIPE_STATUS", "PORTAGE_SOCKS5_PROXY",
"PORTAGE_TMPDIR", "PORTAGE_UPDATE_ENV", "PORTAGE_USERNAME",
"PORTAGE_VERBOSE", "PORTAGE_WORKDIR_MODE", "PORTAGE_XATTR_EXCLUDE",
"PORTDIR", "PORTDIR_OVERLAY", "PREROOTPATH",
diff --git a/pym/portage/package/ebuild/doebuild.py b/pym/portage/package/ebuild/doebuild.py
index 791b5c3..0d71f01 100644
--- a/pym/portage/package/ebuild/doebuild.py
+++ b/pym/portage/package/ebuild/doebuild.py
@@ -68,6 +68,7 @@ from portage.util import apply_recursive_permissions, \
writemsg, writemsg_stdout, write_atomic
from portage.util.cpuinfo import get_cpu_count
from portage.util.lafilefixer import rewrite_lafile
+from portage.util.socks5 import get_socks5_proxy
from portage.versions import _pkgsplit
from _emerge.BinpkgEnvExtractor import BinpkgEnvExtractor
from _emerge.EbuildBuildDir import EbuildBuildDir
@@ -1487,6 +1488,12 @@ def spawn(mystring, mysettings, debug=False, free=False, droppriv=False,
keywords['unshare_net'] = not networked
keywords['unshare_ipc'] = not ipc
+ if not networked:
+ # Provide a SOCKS5-over-UNIX-socket proxy to escape sandbox
+ proxy = get_socks5_proxy(mysettings)
+ mysettings['PORTAGE_SOCKS5_PROXY'] = proxy
+ mysettings['DISTCC_SOCKS_PROXY'] = proxy
+
# TODO: Enable fakeroot to be used together with droppriv. The
# fake ownership/permissions will have to be converted to real
# permissions in the merge phase.
diff --git a/pym/portage/util/socks5.py b/pym/portage/util/socks5.py
new file mode 100644
index 0000000..c8b3d6a
--- /dev/null
+++ b/pym/portage/util/socks5.py
@@ -0,0 +1,45 @@
+# SOCKSv5 proxy manager for network-sandbox
+# Copyright 2015 Gentoo Foundation
+# Distributed under the terms of the GNU General Public License v2
+
+import os
+import signal
+
+from portage import _python_interpreter
+from portage.data import portage_gid, portage_uid, userpriv_groups
+from portage.process import atexit_register, spawn
+
+
+class ProxyManager(object):
+ socket_path = None
+ _pids = None
+
+ def start(self, settings):
+ self.socket_path = os.path.join(settings['PORTAGE_TMPDIR'],
+ '.portage.%d.net.sock' % os.getpid())
+ server_bin = os.path.join(settings['PORTAGE_BIN_PATH'], 'socks5-server.py')
+ self._pids = spawn([_python_interpreter, server_bin, self.socket_path],
+ returnpid=True, uid=portage_uid, gid=portage_gid,
+ groups=userpriv_groups, umask=0o077)
+
+ atexit_register(self.stop)
+
+ def stop(self):
+ self.socket_path = None
+
+ for p in self._pids:
+ os.kill(p, signal.SIGINT)
+ os.waitpid(p, 0)
+
+ def __bool__(self):
+ return self.socket_path is not None
+
+
+running_proxy = ProxyManager()
+
+
+def get_socks5_proxy(settings):
+ if not running_proxy:
+ running_proxy.start(settings)
+
+ return running_proxy.socket_path
--
2.2.2
^ permalink raw reply related [flat|nested] 2+ messages in thread
* Re: [gentoo-portage-dev] [PATCH] Support escaping network-sandbox through SOCKSv5 proxy
2015-01-25 11:29 [gentoo-portage-dev] [PATCH] Support escaping network-sandbox through SOCKSv5 proxy Michał Górny
@ 2015-01-25 14:01 ` Michał Górny
0 siblings, 0 replies; 2+ messages in thread
From: Michał Górny @ 2015-01-25 14:01 UTC (permalink / raw
To: gentoo-portage-dev
[-- Attachment #1: Type: text/plain, Size: 752 bytes --]
Dnia 2015-01-25, o godz. 12:29:54
Michał Górny <mgorny@gentoo.org> napisał(a):
> Add a minimal SOCKSv5-over-UNIX-socket proxy to Portage, and start it
> whenever ebuilds are started with network-sandbox enabled. Pass the
> socket address in PORTAGE_SOCKS5_PROXY and DISTCC_SOCKS_PROXY variables.
> The proxy can be used to escape the network sandbox whenever network
> access is really desired, e.g. in distcc.
>
> The proxy currently supports IPv4 only, and does not report bound
> address (reports 0.0.0.0:0). No authentication is supported (UNIX
> sockets provide a security layer).
Resubmitted with a number of fixes as:
[PATCH v2] Support escaping network-sandbox through SOCKSv5 proxy
--
Best regards,
Michał Górny
[-- Attachment #2: OpenPGP digital signature --]
[-- Type: application/pgp-signature, Size: 949 bytes --]
^ permalink raw reply [flat|nested] 2+ messages in thread
end of thread, other threads:[~2015-01-25 14:01 UTC | newest]
Thread overview: 2+ messages (download: mbox.gz follow: Atom feed
-- links below jump to the message on this page --
2015-01-25 11:29 [gentoo-portage-dev] [PATCH] Support escaping network-sandbox through SOCKSv5 proxy Michał Górny
2015-01-25 14:01 ` Michał Górny
This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox