27
27
#include <stddef.h>
28
28
#include <limits.h>
29
29
30
- #include <sys/socket.h>
31
30
#include <sys/types.h>
31
+ #include <sys/socket.h>
32
32
#include <sys/stat.h>
33
33
#include <sys/un.h>
34
34
#include <sys/wait.h>
35
+ #include <netdb.h>
35
36
#include <unistd.h>
36
37
#include <fcntl.h>
37
38
#include "qrexec.h"
@@ -274,6 +275,10 @@ static int find_file(
274
275
{
275
276
if (target_len >= buffer_size ) {
276
277
/* buffer too small */
278
+ LOG (ERROR , "Buffer size %zu too small for target length %zu" , buffer_size , target_len );
279
+ rc = -2 ;
280
+ } else if (target_len == sizeof ("/dev/tcp" )) {
281
+ LOG (ERROR , "/dev/tcp/ not followed by host" );
277
282
rc = -2 ;
278
283
} else {
279
284
memcpy (buffer , buf , target_len );
@@ -425,7 +430,7 @@ struct qrexec_parsed_command *parse_qubes_rpc_command(
425
430
426
431
/* Parse service name ("qubes.Service") */
427
432
428
- const char * const plus = memchr (start , '+' , descriptor_len );
433
+ char * const plus = memchr (start , '+' , descriptor_len );
429
434
size_t const name_len = plus != NULL ? (size_t )(plus - start ) : descriptor_len ;
430
435
if (name_len > NAME_MAX ) {
431
436
LOG (ERROR , "Service name too long to execute (length %zu)" , name_len );
@@ -445,6 +450,8 @@ struct qrexec_parsed_command *parse_qubes_rpc_command(
445
450
goto err ;
446
451
if (plus == NULL )
447
452
cmd -> service_descriptor [descriptor_len ] = '+' ;
453
+ else
454
+ cmd -> arg = cmd -> service_descriptor + (plus + 1 - start );
448
455
449
456
/* Parse source domain */
450
457
@@ -495,7 +502,7 @@ int execute_qubes_rpc_command(const char *cmdline, int *pid, int *stdin_fd,
495
502
}
496
503
497
504
int execute_parsed_qubes_rpc_command (
498
- const struct qrexec_parsed_command * cmd , int * pid , int * stdin_fd ,
505
+ struct qrexec_parsed_command * cmd , int * pid , int * stdin_fd ,
499
506
int * stdout_fd , int * stderr_fd , struct buffer * stdin_buffer ) {
500
507
if (cmd -> service_descriptor ) {
501
508
// Proper Qubes RPC call
@@ -516,9 +523,81 @@ int execute_parsed_qubes_rpc_command(
516
523
pid , stdin_fd , stdout_fd , stderr_fd );
517
524
}
518
525
}
526
+ static bool validate_port (const char * port ) {
527
+ #define MAXPORT "65535"
528
+ #define MAXPORTLEN (sizeof MAXPORT - 1)
529
+ if (* port < '1' || * port > '9' )
530
+ return false;
531
+ const char * p = port + 1 ;
532
+ for (; * p != '\0' ; ++ p ) {
533
+ if (* p < '0' || * p > '9' )
534
+ return false;
535
+ }
536
+ if (p - port > (ptrdiff_t )MAXPORTLEN )
537
+ return false;
538
+ if (p - port < (ptrdiff_t )MAXPORTLEN )
539
+ return true;
540
+ return memcmp (port , MAXPORT , MAXPORTLEN ) <= 0 ;
541
+ #undef MAXPORT
542
+ #undef MAXPORTLEN
543
+ }
544
+
545
+ static int qubes_tcp_connect (const char * host , const char * port )
546
+ {
547
+ // Work around a glibc bug: overly-large port numbers not rejected
548
+ if (!validate_port (port )) {
549
+ LOG (ERROR , "Invalid port number %s" , port );
550
+ return -1 ;
551
+ }
552
+ /* If there is ':' or '%' in the host, then this must be an IPv6 address, not IPv4. */
553
+ bool const must_be_ipv6_addr = strchr (host , ':' ) != NULL || strchr (host , '%' ) != NULL ;
554
+ LOG (DEBUG , "Connecting to %s%s%s:%s" ,
555
+ must_be_ipv6_addr ? "[" : "" ,
556
+ host ,
557
+ must_be_ipv6_addr ? "]" : "" ,
558
+ port );
559
+ struct addrinfo hints = {
560
+ .ai_flags = AI_NUMERICSERV | AI_NUMERICHOST ,
561
+ .ai_family = must_be_ipv6_addr ? AF_INET6 : AF_UNSPEC ,
562
+ .ai_socktype = SOCK_STREAM ,
563
+ .ai_protocol = IPPROTO_TCP ,
564
+ }, * addrs ;
565
+ int rc = getaddrinfo (host , port , & hints , & addrs );
566
+ if (rc != 0 ) {
567
+ /* data comes from symlink or from qrexec service argument, which has already
568
+ * been sanitized */
569
+ LOG (ERROR , "getaddrinfo(%s, %s) failed: %s" , host , port , gai_strerror (rc ));
570
+ return -1 ;
571
+ }
572
+ rc = -1 ;
573
+ assert (addrs != NULL && "getaddrinfo() returned zero addresses" );
574
+ assert (addrs -> ai_next == NULL &&
575
+ "getaddrinfo() returned multiple addresses despite AI_NUMERICHOST | AI_NUMERICSERV" );
576
+ int sockfd = socket (addrs -> ai_family ,
577
+ addrs -> ai_socktype | SOCK_CLOEXEC ,
578
+ addrs -> ai_protocol );
579
+ if (sockfd < 0 )
580
+ goto freeaddrs ;
581
+ {
582
+ int one = 1 ;
583
+ if (setsockopt (sockfd , SOL_SOCKET , SO_REUSEADDR , & one , sizeof one ) != 0 )
584
+ abort ();
585
+ }
586
+ int res = connect (sockfd , addrs -> ai_addr , addrs -> ai_addrlen );
587
+ if (res != 0 ) {
588
+ PERROR ("connect" );
589
+ close (sockfd );
590
+ } else {
591
+ rc = sockfd ;
592
+ LOG (DEBUG , "Connection succeeded" );
593
+ }
594
+ freeaddrs :
595
+ freeaddrinfo (addrs );
596
+ return rc ;
597
+ }
519
598
520
599
bool find_qrexec_service (
521
- const struct qrexec_parsed_command * cmd ,
600
+ struct qrexec_parsed_command * cmd ,
522
601
int * socket_fd , struct buffer * stdin_buffer ) {
523
602
assert (cmd -> service_descriptor );
524
603
@@ -565,6 +644,68 @@ bool find_qrexec_service(
565
644
566
645
* socket_fd = s ;
567
646
return true;
647
+ } else if (S_ISLNK (statbuf .st_mode )) {
648
+ /* TCP-based service */
649
+ assert (path_buffer .buflen >= (int )sizeof ("/dev/tcp" ) - 1 );
650
+ assert (memcmp (path_buffer .data , "/dev/tcp" , sizeof ("/dev/tcp" ) - 1 ) == 0 );
651
+ char * address = path_buffer .data + (sizeof ("/dev/tcp" ) - 1 );
652
+ char * host = NULL , * port = NULL ;
653
+ if (* address == '/' ) {
654
+ host = address + 1 ;
655
+ char * slash = strchr (host , '/' );
656
+ if (slash != NULL ) {
657
+ * slash = '\0' ;
658
+ port = slash + 1 ;
659
+ }
660
+ } else {
661
+ assert (* address == '\0' );
662
+ }
663
+ if (port == NULL ) {
664
+ if (cmd -> arg == NULL || * cmd -> arg == '\0' ) {
665
+ LOG (ERROR , "No or empty argument provided, cannot connect to %s" ,
666
+ path_buffer .data );
667
+ return false;
668
+ }
669
+ if (host == NULL ) {
670
+ /* Get both host and port from service arguments */
671
+ host = cmd -> arg ;
672
+ port = strrchr (cmd -> arg , '+' );
673
+ if (port == NULL ) {
674
+ LOG (ERROR , "No port provided, cannot connect to %s" , cmd -> arg );
675
+ return false;
676
+ }
677
+ * port = '\0' ;
678
+ for (char * p = host ; p < port ; ++ p ) {
679
+ if (* p == '_' ) {
680
+ LOG (ERROR , "Underscore not allowed in hostname %s" , host );
681
+ return false;
682
+ }
683
+ if (* p == '+' )
684
+ * p = ':' ;
685
+ }
686
+ port ++ ;
687
+ } else {
688
+ /* Get just port from service arguments */
689
+ port = cmd -> arg ;
690
+ }
691
+ } else {
692
+ if (cmd -> arg != NULL && * cmd -> arg != '\0' ) {
693
+ LOG (ERROR , "Unexpected argument %s to service %s" , cmd -> arg , path_buffer .data );
694
+ return false;
695
+ }
696
+ }
697
+
698
+ if (cmd -> send_service_descriptor ) {
699
+ /* send part after "QUBESRPC ", including trailing NUL */
700
+ const char * desc = cmd -> command + RPC_REQUEST_COMMAND_LEN + 1 ;
701
+ buffer_append (stdin_buffer , desc , strlen (desc ) + 1 );
702
+ }
703
+
704
+ int res = qubes_tcp_connect (host , port );
705
+ if (res == -1 )
706
+ return false;
707
+ * socket_fd = res ;
708
+ return true;
568
709
}
569
710
570
711
if (euidaccess (path_buffer .data , X_OK ) == 0 ) {
0 commit comments