server.scm 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332
  1. ;;; Repl server
  2. ;; Copyright (C) 2003,2010,2011,2014,2016,2019,2021 Free Software Foundation, Inc.
  3. ;; This library is free software; you can redistribute it and/or
  4. ;; modify it under the terms of the GNU Lesser General Public
  5. ;; License as published by the Free Software Foundation; either
  6. ;; version 3 of the License, or (at your option) any later version.
  7. ;;
  8. ;; This library is distributed in the hope that it will be useful,
  9. ;; but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. ;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  11. ;; Lesser General Public License for more details.
  12. ;;
  13. ;; You should have received a copy of the GNU Lesser General Public
  14. ;; License along with this library; if not, write to the Free Software
  15. ;; Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
  16. ;; 02110-1301 USA
  17. ;;; Code:
  18. (define-module (system repl server)
  19. #:use-module (system repl repl)
  20. #:use-module (ice-9 threads)
  21. #:use-module (ice-9 rdelim)
  22. #:use-module (ice-9 match)
  23. #:use-module (ice-9 iconv)
  24. #:use-module (rnrs bytevectors)
  25. #:use-module (ice-9 binary-ports)
  26. #:use-module (srfi srfi-1)
  27. #:use-module (srfi srfi-26) ; cut
  28. #:export (make-tcp-server-socket
  29. make-unix-domain-server-socket
  30. run-server
  31. spawn-server
  32. stop-server-and-clients!))
  33. ;; List of pairs of the form (SOCKET . FORCE-CLOSE), where SOCKET is a
  34. ;; socket port, and FORCE-CLOSE is a thunk that forcefully shuts down
  35. ;; the socket.
  36. (define *open-sockets* '())
  37. (define sockets-lock (make-mutex))
  38. ;; WARNING: it is unsafe to call 'close-socket!' from another thread.
  39. ;; Note: although not exported, this is used by (system repl coop-server)
  40. (define (close-socket! s)
  41. (with-mutex sockets-lock
  42. (set! *open-sockets* (assq-remove! *open-sockets* s)))
  43. ;; Close-port could block or raise an exception flushing buffered
  44. ;; output. Hmm.
  45. (close-port s))
  46. ;; Note: although not exported, this is used by (system repl coop-server)
  47. (define (add-open-socket! s force-close)
  48. (with-mutex sockets-lock
  49. (set! *open-sockets* (acons s force-close *open-sockets*))))
  50. (define (stop-server-and-clients!)
  51. (cond
  52. ((with-mutex sockets-lock
  53. (match *open-sockets*
  54. (() #f)
  55. (((s . force-close) . rest)
  56. (set! *open-sockets* rest)
  57. force-close)))
  58. => (lambda (force-close)
  59. (force-close)
  60. (stop-server-and-clients!)))))
  61. (define* (make-tcp-server-socket #:key
  62. (host #f)
  63. (addr (if host
  64. (inet-pton AF_INET host)
  65. INADDR_LOOPBACK))
  66. (port 37146))
  67. (let ((sock (socket PF_INET SOCK_STREAM 0)))
  68. (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
  69. (bind sock AF_INET addr port)
  70. sock))
  71. (define* (make-unix-domain-server-socket #:key (path "/tmp/guile-socket"))
  72. (let ((sock (socket PF_UNIX SOCK_STREAM 0)))
  73. (setsockopt sock SOL_SOCKET SO_REUSEADDR 1)
  74. (bind sock AF_UNIX path)
  75. sock))
  76. (define* (run-server #:optional (server-socket (make-tcp-server-socket)))
  77. (run-server* server-socket serve-client))
  78. ;; Note: although not exported, this is used by (system repl coop-server)
  79. (define (run-server* server-socket serve-client)
  80. ;; We use a pipe to notify the server when it should shut down.
  81. (define shutdown-pipes (pipe))
  82. (define shutdown-read-pipe (car shutdown-pipes))
  83. (define shutdown-write-pipe (cdr shutdown-pipes))
  84. ;; 'shutdown-server' is called by 'stop-server-and-clients!'.
  85. (define (shutdown-server)
  86. (display #\! shutdown-write-pipe)
  87. (force-output shutdown-write-pipe))
  88. (define monitored-ports
  89. (list server-socket
  90. shutdown-read-pipe))
  91. (define (accept-new-client)
  92. (let ((ready-ports (car (select monitored-ports '() '()))))
  93. ;; If we've been asked to shut down, return #f.
  94. (and (not (memq shutdown-read-pipe ready-ports))
  95. ;; If the socket turns out to actually not be ready, this
  96. ;; will return #f. ECONNABORTED etc are still possible of
  97. ;; course.
  98. (or (false-if-exception (accept server-socket)
  99. #:warning "Failed to accept client:")
  100. (accept-new-client)))))
  101. ;; Put the socket into non-blocking mode.
  102. (fcntl server-socket F_SETFL
  103. (logior O_NONBLOCK
  104. (fcntl server-socket F_GETFL)))
  105. (sigaction SIGPIPE SIG_IGN)
  106. (add-open-socket! server-socket shutdown-server)
  107. (listen server-socket 5)
  108. (let lp ()
  109. (match (accept-new-client)
  110. (#f
  111. ;; If client is false, we are shutting down.
  112. (close shutdown-write-pipe)
  113. (close shutdown-read-pipe)
  114. (close server-socket))
  115. ((client-socket . client-addr)
  116. (make-thread serve-client client-socket client-addr)
  117. (lp)))))
  118. (define* (spawn-server #:optional (server-socket (make-tcp-server-socket)))
  119. (make-thread run-server server-socket))
  120. (define (serve-client client addr)
  121. (let ((thread (current-thread)))
  122. ;; To shut down this thread and socket, cause it to unwind.
  123. (add-open-socket! client (lambda () (cancel-thread thread))))
  124. (guard-against-http-request client)
  125. (dynamic-wind
  126. (lambda () #f)
  127. (with-continuation-barrier
  128. (lambda ()
  129. (parameterize ((current-input-port client)
  130. (current-output-port client)
  131. (current-error-port client)
  132. (current-warning-port client))
  133. (with-fluids ((*repl-stack* '()))
  134. (start-repl)))))
  135. (lambda () (close-socket! client))))
  136. ;;;
  137. ;;; The following code adds protection to Guile's REPL servers against
  138. ;;; HTTP inter-protocol exploitation attacks, a scenario whereby an
  139. ;;; attacker can, via an HTML page, cause a web browser to send data to
  140. ;;; TCP servers listening on a loopback interface or private network.
  141. ;;; See <https://en.wikipedia.org/wiki/Inter-protocol_exploitation> and
  142. ;;; <https://www.jochentopf.com/hfpa/hfpa.pdf>, The HTML Form Protocol
  143. ;;; Attack (2001) by Tochen Topf <jochen@remote.org>.
  144. ;;;
  145. ;;; Here we add a procedure to 'before-read-hook' that looks for a possible
  146. ;;; HTTP request-line in the first line of input from the client socket. If
  147. ;;; present, the socket is drained and closed, and a loud warning is written
  148. ;;; to stderr (POSIX file descriptor 2).
  149. ;;;
  150. (define (with-temporary-port-encoding port encoding thunk)
  151. "Call THUNK in a dynamic environment in which the encoding of PORT is
  152. temporarily set to ENCODING."
  153. (let ((saved-encoding #f))
  154. (dynamic-wind
  155. (lambda ()
  156. (unless (port-closed? port)
  157. (set! saved-encoding (port-encoding port))
  158. (set-port-encoding! port encoding)))
  159. thunk
  160. (lambda ()
  161. (unless (port-closed? port)
  162. (set! encoding (port-encoding port))
  163. (set-port-encoding! port saved-encoding))))))
  164. (define (with-saved-port-line+column port thunk)
  165. "Save the line and column of PORT before entering THUNK, and restore
  166. their previous values upon normal or non-local exit from THUNK."
  167. (let ((saved-line #f) (saved-column #f))
  168. (dynamic-wind
  169. (lambda ()
  170. (unless (port-closed? port)
  171. (set! saved-line (port-line port))
  172. (set! saved-column (port-column port))))
  173. thunk
  174. (lambda ()
  175. (unless (port-closed? port)
  176. (set-port-line! port saved-line)
  177. (set-port-column! port saved-column))))))
  178. (define (drain-input-and-close socket)
  179. "Drain input from SOCKET using ISO-8859-1 encoding until it would block,
  180. and then close it. Return the drained input as a string."
  181. (dynamic-wind
  182. (lambda ()
  183. ;; Enable full buffering mode on the socket to allow
  184. ;; 'get-bytevector-some' to return non-trivial chunks.
  185. (setvbuf socket 'block))
  186. (lambda ()
  187. (let loop ((chunks '()))
  188. (let ((result (and (char-ready? socket)
  189. (get-bytevector-some socket))))
  190. (if (bytevector? result)
  191. (loop (cons (bytevector->string result "ISO-8859-1")
  192. chunks))
  193. (string-concatenate-reverse chunks)))))
  194. (lambda ()
  195. ;; Close the socket even in case of an exception.
  196. (close-port socket))))
  197. (define permissive-http-request-line?
  198. ;; This predicate is deliberately permissive
  199. ;; when checking the Request-URI component.
  200. (let ((cs (ucs-range->char-set #x20 #x7E))
  201. (rx (make-regexp
  202. (string-append
  203. "^(OPTIONS|GET|HEAD|POST|PUT|DELETE|TRACE|CONNECT) "
  204. "[^ ]+ "
  205. "HTTP/[0123456789]+.[0123456789]+$"))))
  206. (lambda (line)
  207. "Return true if LINE might plausibly be an HTTP request-line,
  208. otherwise return #f."
  209. ;; We cannot simplify this to a simple 'regexp-exec', because
  210. ;; 'regexp-exec' cannot cope with NUL bytes.
  211. (and (string-every cs line)
  212. (regexp-exec rx line)))))
  213. (define (check-for-http-request socket)
  214. "Check for a possible HTTP request in the initial input from SOCKET.
  215. If one is found, close the socket and print a report to STDERR (fdes 2).
  216. Otherwise, put back the bytes."
  217. ;; Temporarily set the port encoding to ISO-8859-1 to allow lossless
  218. ;; reading and unreading of the first line, regardless of what bytes
  219. ;; are present. Note that a valid HTTP request-line contains only
  220. ;; ASCII characters.
  221. (with-temporary-port-encoding socket "ISO-8859-1"
  222. (lambda ()
  223. ;; Save the port 'line' and 'column' counters and later restore
  224. ;; them, since unreading what we read is not sufficient to do so.
  225. (with-saved-port-line+column socket
  226. (lambda ()
  227. ;; Read up to (but not including) the first CR or LF.
  228. ;; Although HTTP mandates CRLF line endings, we are permissive
  229. ;; here to guard against the possibility that in some
  230. ;; environments CRLF might be converted to LF before it
  231. ;; reaches us.
  232. (match (read-delimited "\r\n" socket 'peek)
  233. ((? eof-object?)
  234. ;; We found EOF before any input. Nothing to do.
  235. 'done)
  236. ((? permissive-http-request-line? request-line)
  237. ;; The input from the socket began with a plausible HTTP
  238. ;; request-line, which is unlikely to be legitimate and may
  239. ;; indicate an possible break-in attempt.
  240. ;; First, set the current port parameters to a void-port,
  241. ;; to avoid sending any more data over the socket, to cause
  242. ;; the REPL reader to see EOF, and to swallow any remaining
  243. ;; output gracefully.
  244. (let ((void-port (%make-void-port "rw")))
  245. (current-input-port void-port)
  246. (current-output-port void-port)
  247. (current-error-port void-port)
  248. (current-warning-port void-port))
  249. ;; Read from the socket until we would block,
  250. ;; and then close it.
  251. (let ((drained-input (drain-input-and-close socket)))
  252. ;; Print a report to STDERR (POSIX file descriptor 2).
  253. ;; XXX Can we do better here?
  254. (call-with-port (dup->port 2 "w")
  255. (cut format <> "
  256. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@
  257. @@ POSSIBLE BREAK-IN ATTEMPT ON THE REPL SERVER @@
  258. @@ BY AN HTTP INTER-PROTOCOL EXPLOITATION ATTACK. See: @@
  259. @@ <https://en.wikipedia.org/wiki/Inter-protocol_exploitation> @@
  260. @@ Possible HTTP request received: ~S
  261. @@ The associated socket has been closed. @@
  262. @@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@@\n"
  263. (string-append request-line
  264. drained-input)))))
  265. (start-line
  266. ;; The HTTP request-line was not found, so
  267. ;; 'unread' the characters that we have read.
  268. (unread-string start-line socket))))))))
  269. (define (guard-against-http-request socket)
  270. "Arrange for the Guile REPL to check for an HTTP request in the
  271. initial input from SOCKET, in which case the socket will be closed.
  272. This guards against HTTP inter-protocol exploitation attacks, a scenario
  273. whereby an attacker can, via an HTML page, cause a web browser to send
  274. data to TCP servers listening on a loopback interface or private
  275. network."
  276. (%set-port-property! socket 'guard-against-http-request? #t))
  277. (define* (maybe-check-for-http-request
  278. #:optional (socket (current-input-port)))
  279. "Apply check-for-http-request to SOCKET if previously requested by
  280. guard-against-http-request. This procedure is intended to be added to
  281. before-read-hook."
  282. (when (%port-property socket 'guard-against-http-request?)
  283. (check-for-http-request socket)
  284. (unless (port-closed? socket)
  285. (%set-port-property! socket 'guard-against-http-request? #f))))
  286. ;; Install the hook.
  287. (add-hook! before-read-hook
  288. maybe-check-for-http-request)
  289. ;;; Local Variables:
  290. ;;; eval: (put 'with-temporary-port-encoding 'scheme-indent-function 2)
  291. ;;; eval: (put 'with-saved-port-line+column 'scheme-indent-function 1)
  292. ;;; End: