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