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"
@@ -411,7 +412,7 @@ struct qrexec_parsed_command *parse_qubes_rpc_command(
411
412
/* Parse service name ("qubes.Service") */
412
413
413
414
const char * const plus = memchr (start , '+' , descriptor_len );
414
- size_t const name_len = plus != NULL ? (size_t )(plus - start ) : descriptor_len ;
415
+ size_t name_len = plus != NULL ? (size_t )(plus - start ) : descriptor_len ;
415
416
if (name_len > NAME_MAX ) {
416
417
LOG (ERROR , "Service name too long to execute (length %zu)" , name_len );
417
418
goto err ;
@@ -423,6 +424,7 @@ struct qrexec_parsed_command *parse_qubes_rpc_command(
423
424
cmd -> service_name = memdupnul (start , name_len );
424
425
if (!cmd -> service_name )
425
426
goto err ;
427
+ cmd -> arg = plus != NULL ? plus + 1 : NULL ;
426
428
427
429
/* If there is no service argument, add a trailing "+" to the descriptor */
428
430
cmd -> service_descriptor = memdupnul (start , descriptor_len + (plus == NULL ));
@@ -480,7 +482,7 @@ int execute_qubes_rpc_command(const char *cmdline, int *pid, int *stdin_fd,
480
482
}
481
483
482
484
int execute_parsed_qubes_rpc_command (
483
- const struct qrexec_parsed_command * cmd , int * pid , int * stdin_fd ,
485
+ struct qrexec_parsed_command * cmd , int * pid , int * stdin_fd ,
484
486
int * stdout_fd , int * stderr_fd , struct buffer * stdin_buffer ) {
485
487
if (cmd -> service_descriptor ) {
486
488
// Proper Qubes RPC call
@@ -501,9 +503,81 @@ int execute_parsed_qubes_rpc_command(
501
503
pid , stdin_fd , stdout_fd , stderr_fd );
502
504
}
503
505
}
506
+ static bool validate_port (const char * port ) {
507
+ #define MAXPORT "65535"
508
+ #define MAXPORTLEN (sizeof MAXPORT - 1)
509
+ if (* port < '1' || * port > '9' )
510
+ return false;
511
+ const char * p = port + 1 ;
512
+ for (; * p != 0 ; ++ p ) {
513
+ if (* p < '0' || * p > '9' )
514
+ return false;
515
+ }
516
+ if (p - port > (ptrdiff_t )MAXPORTLEN )
517
+ return false;
518
+ if (p - port < (ptrdiff_t )MAXPORTLEN )
519
+ return true;
520
+ return memcmp (port , MAXPORT , MAXPORTLEN ) <= 0 ;
521
+ #undef MAXPORT
522
+ #undef MAXPORTLEN
523
+ }
524
+
525
+ static int qubes_tcp_connect (const char * host , const char * port )
526
+ {
527
+ // Work around a glibc bug: overly-large port numbers not rejected
528
+ if (!validate_port (port )) {
529
+ LOG (ERROR , "Invalid port number %s" , port );
530
+ return -1 ;
531
+ }
532
+ /* If there is ':' or '%' in the host, then this must be an IPv6 address, not IPv4. */
533
+ bool const must_be_ipv6_addr = strchr (host , ':' ) != NULL || strchr (host , '%' ) != NULL ;
534
+ LOG (DEBUG , "Connecting to %s%s%s:%s" ,
535
+ must_be_ipv6_addr ? "[" : "" ,
536
+ host ,
537
+ must_be_ipv6_addr ? "]" : "" ,
538
+ port );
539
+ struct addrinfo hints = {
540
+ .ai_flags = AI_NUMERICSERV | AI_NUMERICHOST ,
541
+ .ai_family = must_be_ipv6_addr ? AF_INET6 : AF_UNSPEC ,
542
+ .ai_socktype = SOCK_STREAM ,
543
+ .ai_protocol = IPPROTO_TCP ,
544
+ }, * addrs ;
545
+ int rc = getaddrinfo (host , port , & hints , & addrs );
546
+ if (rc != 0 ) {
547
+ /* data comes from symlink or from qrexec service argument, which has already
548
+ * been sanitized */
549
+ LOG (ERROR , "getaddrinfo(%s, %s) failed: %s" , host , port , gai_strerror (rc ));
550
+ return -1 ;
551
+ }
552
+ rc = -1 ;
553
+ assert (addrs != NULL && "getaddrinfo() returned zero addresses" );
554
+ assert (addrs -> ai_next == NULL &&
555
+ "getaddrinfo() returned multiple addresses despite AI_NUMERICHOST | AI_NUMERICSERV" );
556
+ int sockfd = socket (addrs -> ai_family ,
557
+ addrs -> ai_socktype | SOCK_CLOEXEC ,
558
+ addrs -> ai_protocol );
559
+ if (sockfd < 0 )
560
+ goto freeaddrs ;
561
+ {
562
+ int one = 1 ;
563
+ if (setsockopt (sockfd , SOL_SOCKET , SO_REUSEADDR , & one , sizeof one ) != 0 )
564
+ abort ();
565
+ }
566
+ int res = connect (sockfd , addrs -> ai_addr , addrs -> ai_addrlen );
567
+ if (res != 0 ) {
568
+ PERROR ("connect" );
569
+ close (sockfd );
570
+ } else {
571
+ rc = sockfd ;
572
+ LOG (DEBUG , "Connection succeeded" );
573
+ }
574
+ freeaddrs :
575
+ freeaddrinfo (addrs );
576
+ return rc ;
577
+ }
504
578
505
579
bool find_qrexec_service (
506
- const struct qrexec_parsed_command * cmd ,
580
+ struct qrexec_parsed_command * cmd ,
507
581
int * socket_fd , struct buffer * stdin_buffer ) {
508
582
assert (cmd -> service_descriptor );
509
583
@@ -529,10 +603,11 @@ bool find_qrexec_service(
529
603
return false;
530
604
}
531
605
606
+ const char * desc = cmd -> command + RPC_REQUEST_COMMAND_LEN + 1 ;
532
607
if (S_ISSOCK (statbuf .st_mode )) {
533
608
/* Socket-based service. */
534
609
int s ;
535
- if ((s = socket (AF_UNIX , SOCK_STREAM , 0 )) == -1 ) {
610
+ if ((s = socket (AF_UNIX , SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK , 0 )) == -1 ) {
536
611
PERROR ("socket" );
537
612
return false;
538
613
}
@@ -544,12 +619,59 @@ bool find_qrexec_service(
544
619
545
620
if (cmd -> send_service_descriptor ) {
546
621
/* send part after "QUBESRPC ", including trailing NUL */
547
- const char * desc = cmd -> command + RPC_REQUEST_COMMAND_LEN + 1 ;
548
622
buffer_append (stdin_buffer , desc , strlen (desc ) + 1 );
549
623
}
550
624
551
625
* socket_fd = s ;
552
626
return true;
627
+ } else if (S_ISLNK (statbuf .st_mode )) {
628
+ /* TCP-based service */
629
+ assert (path_buffer .buflen >= (int )sizeof "/dev/tcp" );
630
+ assert (memcmp (path_buffer .data , "/dev/tcp/" , sizeof "/dev/tcp" ) == 0 );
631
+ char * address = path_buffer .data + sizeof "/dev/tcp" ;
632
+ char * slash = strchr (address , '/' );
633
+ char * host , * port ;
634
+ if (slash == NULL ) {
635
+ if (cmd -> arg == NULL || * cmd -> arg == ' ' ) {
636
+ LOG (ERROR , "No or empty argument provided, cannot connect to %s" ,
637
+ path_buffer .data );
638
+ return -1 ;
639
+ }
640
+ char * ptr = cmd -> service_descriptor + (cmd -> arg - desc );
641
+ if (* address == '\0' ) {
642
+ /* Get both host and port from service arguments */
643
+ host = ptr ;
644
+ port = strrchr (ptr , '+' );
645
+ if (port == NULL ) {
646
+ LOG (ERROR , "No host provided, cannot connect at %s" , path_buffer .data );
647
+ return -1 ;
648
+ }
649
+ * port = '\0' ;
650
+ for (char * p = host ; p < port ; ++ p ) {
651
+ if (* p == '_' ) {
652
+ LOG (ERROR , "Underscore not allowed in hostname %s" , host );
653
+ return -1 ;
654
+ }
655
+ if (* p == '+' )
656
+ * p = ':' ;
657
+ }
658
+ port ++ ;
659
+ } else {
660
+ /* Get just port from service arguments */
661
+ host = address ;
662
+ port = ptr ;
663
+ }
664
+ } else {
665
+ * slash = '\0' ;
666
+ host = address ;
667
+ port = slash + 1 ;
668
+ }
669
+ int res = qubes_tcp_connect (host , port );
670
+ if (res == -1 )
671
+ return false;
672
+ * socket_fd = res ;
673
+ cmd -> send_service_descriptor = false;
674
+ return true;
553
675
}
554
676
555
677
if (euidaccess (path_buffer .data , X_OK ) == 0 ) {
0 commit comments