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
@@ -265,21 +266,17 @@ static int find_file(
265
266
if (res == 0 && S_ISLNK (statbuf -> st_mode )) {
266
267
if (buf != NULL ) {
267
268
ssize_t res = readlink (buffer , buf , buffer_size );
268
- if (res >= (ssize_t )(sizeof "/dev/tcp/" - 1 ) &&
269
- memcmp (buf , "/dev/tcp/" , (sizeof "/dev/tcp/" - 1 )) == 0 ) {
270
- if ((size_t )res >= buffer_size ) {
271
- errno = ERANGE ;
272
- rc = -2 ;
273
- } else {
274
- memcpy (buffer , buf , (size_t )res );
275
- buffer [res ] = '\0' ;
276
- rc = 0 ;
277
- }
278
- goto done ;
279
- } else if (res < 0 ) {
269
+ if (res < (ssize_t )(sizeof "/dev/tcp" ) ||
270
+ (size_t )res >= buffer_size ||
271
+ memcmp (buf , "/dev/tcp/" , sizeof "/dev/tcp" ) != 0 ) {
280
272
rc = -2 ;
281
- goto done ;
273
+ } else {
274
+ memcpy (buffer , buf , (size_t )res );
275
+ buffer [res ] = '\0' ;
276
+ rc = 0 ;
277
+ LOG (ERROR , "Symlink to /dev/tcp detected: value is %s" , buffer );
282
278
}
279
+ goto done ;
283
280
}
284
281
/* check if the symlink is valid */
285
282
res = stat (buffer , statbuf );
@@ -410,7 +407,7 @@ struct qrexec_parsed_command *parse_qubes_rpc_command(
410
407
/* Parse service name ("qubes.Service") */
411
408
412
409
const char * const plus = memchr (start , '+' , descriptor_len );
413
- size_t const name_len = plus != NULL ? (size_t )(plus - start ) : descriptor_len ;
410
+ size_t name_len = plus != NULL ? (size_t )(plus - start ) : descriptor_len ;
414
411
if (name_len > NAME_MAX ) {
415
412
LOG (ERROR , "Service name too long to execute (length %zu)" , name_len );
416
413
goto err ;
@@ -426,6 +423,7 @@ struct qrexec_parsed_command *parse_qubes_rpc_command(
426
423
}
427
424
memcpy (cmd -> service_name , start , name_len );
428
425
cmd -> service_name [name_len ] = '\0' ;
426
+ cmd -> arg = plus != NULL ? plus + 1 : NULL ;
429
427
430
428
/* If there is no service argument, add a trailing "+" to the descriptor */
431
429
size_t const descriptor_buffer_size = descriptor_len + 1 + (plus == NULL );
@@ -493,7 +491,7 @@ int execute_qubes_rpc_command(const char *cmdline, int *pid, int *stdin_fd,
493
491
}
494
492
495
493
int execute_parsed_qubes_rpc_command (
496
- const struct qrexec_parsed_command * cmd , int * pid , int * stdin_fd ,
494
+ struct qrexec_parsed_command * cmd , int * pid , int * stdin_fd ,
497
495
int * stdout_fd , int * stderr_fd , struct buffer * stdin_buffer ) {
498
496
if (cmd -> service_descriptor ) {
499
497
// Proper Qubes RPC call
@@ -506,8 +504,145 @@ int execute_parsed_qubes_rpc_command(
506
504
}
507
505
}
508
506
507
+ static int qubes_tcp_connect (const char * host , const char * port )
508
+ {
509
+ // Work around a glibc bug: overly-large port numbers not rejected
510
+ {
511
+ if (* port < '1' || * port > '9' )
512
+ goto invalid_port ;
513
+ const char * end = port ;
514
+ errno = 0 ;
515
+ long r = strtol (port , (char * * )& end , 10 );
516
+ if (r < 1 || r > 65535 || * end != 0 || errno )
517
+ goto invalid_port ;
518
+ }
519
+ /* If there is ':' or '%' in the host, then this must be an IPv6 address, not
520
+ * a hostname. */
521
+ bool const must_be_ipv6_addr = strchr (host , ':' ) != NULL || strchr (host , '%' ) != NULL ;
522
+ LOG (DEBUG , "Connecting to %s%s%s:%s" ,
523
+ must_be_ipv6_addr ? "[" : "" ,
524
+ host ,
525
+ must_be_ipv6_addr ? "]" : "" ,
526
+ port );
527
+ struct addrinfo hints = {
528
+ .ai_flags = AI_NUMERICSERV | (must_be_ipv6_addr ? AI_NUMERICHOST : 0 ),
529
+ .ai_family = must_be_ipv6_addr ? AF_INET6 : AF_UNSPEC ,
530
+ .ai_socktype = SOCK_STREAM ,
531
+ .ai_protocol = IPPROTO_TCP ,
532
+ }, * addrs ;
533
+ int rc = getaddrinfo (host , port , & hints , & addrs );
534
+ if (rc != 0 ) {
535
+ /* data comes from symlink or from qrexec service argument, which has already
536
+ * been sanitized */
537
+ LOG (ERROR , "getaddrinfo(%s, %s) failed: %s" , host , port , gai_strerror (rc ));
538
+ return -1 ;
539
+ }
540
+ rc = -1 ;
541
+ size_t addresses = 1 , used_addresses = 0 ;
542
+ assert (addrs != NULL && "getaddrinfo() returned zero addresses" );
543
+ for (struct addrinfo * p = addrs -> ai_next ; p != NULL ; p = p -> ai_next ) {
544
+ addresses ++ ;
545
+ }
546
+ struct pollfd * fds = calloc (addresses , sizeof (struct pollfd ));
547
+ if (fds == NULL ) {
548
+ LOG (ERROR , "Out of memory allocating %zu pollfds!" , addresses );
549
+ goto freeaddrs ;
550
+ }
551
+ for (struct addrinfo * p = addrs ; p != NULL ; p = p -> ai_next ) {
552
+ switch (p -> ai_family ) {
553
+ case AF_INET :
554
+ assert (p -> ai_addrlen >= sizeof (struct sockaddr_in ));
555
+ LOG (DEBUG , "Port number %d" , ntohs (((struct sockaddr_in * )p -> ai_addr )-> sin_port ));
556
+ break ;
557
+ case AF_INET6 :
558
+ assert (p -> ai_addrlen >= sizeof (struct sockaddr_in6 ));
559
+ LOG (DEBUG , "Port number %d" , ntohs (((struct sockaddr_in6 * )p -> ai_addr )-> sin6_port ));
560
+ break ;
561
+ default :
562
+ LOG (ERROR , "Unknown socket family" );
563
+ break ;
564
+ }
565
+ int sockfd = socket (p -> ai_family ,
566
+ p -> ai_socktype | SOCK_CLOEXEC | SOCK_NONBLOCK ,
567
+ p -> ai_protocol );
568
+ if (sockfd < 0 ) {
569
+ LOG (ERROR , "Cannot create socket, skipping" );
570
+ continue ;
571
+ }
572
+ int res = connect (sockfd , p -> ai_addr , p -> ai_addrlen );
573
+ if (res == 0 ) {
574
+ rc = sockfd ;
575
+ goto done ;
576
+ }
577
+ if (errno != EINPROGRESS ) {
578
+ PERROR ("connect" );
579
+ close (sockfd );
580
+ continue ;
581
+ }
582
+ fds [used_addresses ].fd = sockfd ;
583
+ fds [used_addresses ].events = POLLIN |POLLOUT |POLLPRI |POLLRDHUP ;
584
+ used_addresses ++ ;
585
+ }
586
+ /* FIXME: USE EPOLL!!!!!!! */
587
+ while (used_addresses != 0 ) {
588
+ for (size_t i = 0 ; i < used_addresses ; ++ i ) {
589
+ fds [i ].revents = 0 ;
590
+ }
591
+ int res = poll (fds , used_addresses , 1000 /* 1 second */ );
592
+ if (res > 0 ) {
593
+ struct pollfd * p = fds ;
594
+ for (size_t i = 0 ; i < used_addresses ; ++ i ) {
595
+ if (fds [i ].revents == 0 )
596
+ * p ++ = fds [i ];
597
+ else if (fds [i ].revents & ~(short )(POLLIN | POLLOUT )) {
598
+ LOG (ERROR , "FD %d (offset %zu) had events 0%s%s%s%s%s%s" , fds [i ].fd , i ,
599
+ (fds [i ].revents & POLLIN ) ? " | POLLIN" : "" ,
600
+ (fds [i ].revents & POLLOUT ) ? " | POLLOUT" : "" ,
601
+ (fds [i ].revents & POLLPRI ) ? " | POLLPRI" : "" ,
602
+ (fds [i ].revents & POLLERR ) ? " | POLLERR" : "" ,
603
+ (fds [i ].revents & POLLHUP ) ? " | POLLHUP" : "" ,
604
+ (fds [i ].revents & POLLNVAL ) ? " | POLLNVAL" : "" );
605
+ close (fds [i ].fd );
606
+ fds [i ].fd = -1 ;
607
+ } else {
608
+ rc = fds [i ].fd ;
609
+ fds [i ].fd = -1 ;
610
+ goto done ;
611
+ }
612
+ }
613
+ used_addresses = (size_t )(p - fds );
614
+ } else if (res == 0 ) {
615
+ LOG (ERROR , "TCP connection timeout" );
616
+ break ;
617
+ } else if (errno == EINTR || errno == EAGAIN ) {
618
+ continue ;
619
+ } else {
620
+ PERROR ("poll" );
621
+ break ;
622
+ }
623
+ }
624
+ done :
625
+ if (rc < 0 )
626
+ LOG (ERROR , "None of %zu TCP sockets could connect" , addresses );
627
+ else
628
+ LOG (ERROR , "Connection succeeded" );
629
+ /* Close all FDs but the chosen one */
630
+ for (size_t i = 0 ; i < used_addresses ; ++ i ) {
631
+ if (fds [i ].fd != -1 ) {
632
+ close (fds [i ].fd );
633
+ }
634
+ }
635
+ free (fds );
636
+ freeaddrs :
637
+ freeaddrinfo (addrs );
638
+ return rc ;
639
+ invalid_port :
640
+ LOG (ERROR , "Invalid port number %s" , port );
641
+ return -1 ;
642
+ }
643
+
509
644
static int execute_qrexec_service (
510
- const struct qrexec_parsed_command * cmd ,
645
+ struct qrexec_parsed_command * cmd ,
511
646
int * pid , int * stdin_fd , int * stdout_fd , int * stderr_fd ,
512
647
struct buffer * stdin_buffer ) {
513
648
@@ -533,10 +668,11 @@ static int execute_qrexec_service(
533
668
return -1 ;
534
669
}
535
670
671
+ const char * desc = cmd -> command + RPC_REQUEST_COMMAND_LEN + 1 ;
536
672
if (S_ISSOCK (statbuf .st_mode )) {
537
673
/* Socket-based service. */
538
674
int s ;
539
- if ((s = socket (AF_UNIX , SOCK_STREAM , 0 )) == -1 ) {
675
+ if ((s = socket (AF_UNIX , SOCK_STREAM | SOCK_CLOEXEC | SOCK_NONBLOCK , 0 )) == -1 ) {
540
676
PERROR ("socket" );
541
677
return -1 ;
542
678
}
@@ -550,14 +686,60 @@ static int execute_qrexec_service(
550
686
if (stderr_fd )
551
687
* stderr_fd = -1 ;
552
688
* pid = 0 ;
553
- set_nonblock (s );
554
689
555
690
if (cmd -> send_service_descriptor ) {
556
691
/* send part after "QUBESRPC ", including trailing NUL */
557
- const char * desc = cmd -> command + RPC_REQUEST_COMMAND_LEN + 1 ;
558
692
buffer_append (stdin_buffer , desc , strlen (desc ) + 1 );
559
693
}
560
694
return 0 ;
695
+ } else if (S_ISLNK (statbuf .st_mode )) {
696
+ if (stderr_fd )
697
+ * stderr_fd = -1 ;
698
+ * pid = 0 ;
699
+ /* TCP-based service */
700
+ assert (memcmp (service_full_path , "/dev/tcp/" , sizeof "/dev/tcp" ) == 0 );
701
+ char * address = service_full_path + sizeof "/dev/tcp" ;
702
+ char * slash = strchr (address , '/' );
703
+ char * host , * port ;
704
+ if (slash == NULL ) {
705
+ if (cmd -> arg == NULL || * cmd -> arg == ' ' ) {
706
+ LOG (ERROR , "No or empty argument provided, cannot connect to %s" ,
707
+ service_full_path );
708
+ return -1 ;
709
+ }
710
+ char * ptr = cmd -> service_descriptor + (cmd -> arg - desc );
711
+ if (* address == '\0' ) {
712
+ /* Get both host and port from service arguments */
713
+ host = ptr ;
714
+ port = strrchr (ptr , '+' );
715
+ if (port == NULL ) {
716
+ LOG (ERROR , "No host provided, cannot connect at %s" , service_full_path );
717
+ return -1 ;
718
+ }
719
+ * port = '\0' ;
720
+ for (char * p = host ; p < port ; ++ p ) {
721
+ if (* p == '_' )
722
+ * p = ':' ;
723
+ else if (* p == '+' )
724
+ * p = '%' ;
725
+ }
726
+ port ++ ;
727
+ } else {
728
+ /* Get just port from service arguments */
729
+ host = address ;
730
+ port = ptr ;
731
+ }
732
+ } else {
733
+ * slash = '\0' ;
734
+ host = address ;
735
+ port = slash + 1 ;
736
+ }
737
+ int res = qubes_tcp_connect (host , port );
738
+ if (res == -1 )
739
+ return -1 ;
740
+ * stdin_fd = * stdout_fd = res ;
741
+ cmd -> send_service_descriptor = false;
742
+ return 0 ;
561
743
}
562
744
563
745
if (euidaccess (service_full_path , X_OK ) == 0 ) {
0 commit comments