@@ -358,31 +358,136 @@ nc_sock_listen_inet(const char *address, uint16_t port)
358358 return -1 ;
359359}
360360
361- const char *
361+ /**
362+ * @brief Construct the full path to the UNIX socket.
363+ *
364+ * @param[in] filename Name of the socket file.
365+ * @param[out] path Constructed full path to the UNIX socket (must be freed by the caller).
366+ * @return 0 on success, 1 on error.
367+ */
368+ static int
369+ nc_session_unix_construct_socket_path (const char * filename , char * * path )
370+ {
371+ int rc = 0 , is_prefix , is_subdir , is_exact ;
372+ char * full_path = NULL , * real_base_dir = NULL , * last_slash = NULL , * sock_dir_path = NULL ;
373+ char * real_target_dir = NULL ;
374+ struct sockaddr_un sun ;
375+ size_t dir_len , base_len ;
376+ const char * dir = server_opts .unix_socket_dir ;
377+
378+ if (!dir ) {
379+ ERR (NULL , "Cannot construct UNIX socket path \"%s\""
380+ " (no base directory set, see nc_set_unix_socket_dir())." , filename );
381+ return 1 ;
382+ }
383+
384+ if (filename [0 ] == '/' ) {
385+ ERR (NULL , "Cannot construct UNIX socket path \"%s\" (absolute path not allowed)." , filename );
386+ return 1 ;
387+ }
388+
389+ /* construct the path to the UNIX socket */
390+ if (asprintf (& full_path , "%s/%s" , dir , filename ) == -1 ) {
391+ ERRMEM ;
392+ rc = 1 ;
393+ goto cleanup ;
394+ }
395+
396+ if (strlen (full_path ) > sizeof (sun .sun_path ) - 1 ) {
397+ ERR (NULL , "Socket path \"%s\" is too long." , full_path );
398+ goto cleanup ;
399+ }
400+
401+ /* ensure the socket path is within the base directory */
402+ if (!(real_base_dir = realpath (dir , NULL ))) {
403+ ERR (NULL , "realpath() failed for UNIX socket base directory \"%s\" (%s)." , dir , strerror (errno ));
404+ rc = 1 ;
405+ goto cleanup ;
406+ }
407+
408+ /* find the last slash in the constructed path */
409+ last_slash = strrchr (full_path , '/' );
410+ if (last_slash ) {
411+ /* extract the directory part of the socket path */
412+ dir_len = last_slash - full_path ;
413+ sock_dir_path = strndup (full_path , dir_len );
414+ NC_CHECK_ERRMEM_GOTO (!sock_dir_path , rc = 1 , cleanup );
415+
416+ if (!(real_target_dir = realpath (sock_dir_path , NULL ))) {
417+ ERR (NULL , "realpath() failed for UNIX socket path directory \"%s\" (%s)." , sock_dir_path ,
418+ strerror (errno ));
419+ rc = 1 ;
420+ goto cleanup ;
421+ }
422+ } else {
423+ /* should not happen as we always add dir/filename */
424+ real_target_dir = strdup (real_base_dir );
425+ NC_CHECK_ERRMEM_GOTO (!real_target_dir , rc = 1 , cleanup );
426+ }
427+
428+ base_len = strlen (real_base_dir );
429+
430+ /* check the relationship between both paths */
431+ is_prefix = (strncmp (real_base_dir , real_target_dir , base_len ) == 0 );
432+
433+ is_exact = (real_target_dir [base_len ] == '\0' );
434+
435+ is_subdir = (real_target_dir [base_len ] == '/' );
436+
437+ /* special case if base is '/' */
438+ if ((base_len == 1 ) && (real_base_dir [0 ] == '/' )) {
439+ is_subdir = 1 ;
440+ }
441+
442+ if (!is_prefix || (!is_exact && !is_subdir )) {
443+ ERR (NULL , "UNIX socket path \"%s\" escapes the base directory \"%s\"." , full_path , dir );
444+ rc = 1 ;
445+ goto cleanup ;
446+ }
447+
448+ /* transfer ownership */
449+ * path = full_path ;
450+ full_path = NULL ;
451+
452+ cleanup :
453+ free (real_base_dir );
454+ free (real_target_dir );
455+ free (sock_dir_path );
456+ free (full_path );
457+ return rc ;
458+ }
459+
460+ char *
362461nc_server_unix_get_socket_path (const struct nc_endpt * endpt )
363462{
364463 LY_ARRAY_COUNT_TYPE i ;
365- const char * path = NULL ;
464+ const char * filename = NULL ;
465+ char * path = NULL ;
366466
367467 /* check the endpoints options for type of socket path */
368468 if (endpt -> opts .unix -> path_type == NC_UNIX_SOCKET_PATH_FILE ) {
369469 /* UNIX socket endpoints always have only one bind, get its address */
370- path = endpt -> binds [0 ].address ;
470+ filename = endpt -> binds [0 ].address ;
371471 } else if (endpt -> opts .unix -> path_type == NC_UNIX_SOCKET_PATH_HIDDEN ) {
372- /* serach the mappings */
472+ /* search the mappings */
373473 LY_ARRAY_FOR (server_opts .unix_paths , i ) {
374474 if (!strcmp (server_opts .unix_paths [i ].endpt_name , endpt -> name )) {
375- path = server_opts .unix_paths [i ].path ;
475+ filename = server_opts .unix_paths [i ].path ;
376476 break ;
377477 }
378478 }
379- if (!path ) {
479+ if (!filename ) {
380480 ERR (NULL , "UNIX socket path mapping for endpoint \"%s\" not found." , endpt -> name );
381481 }
382482 } else {
383483 ERRINT ;
384484 }
385485
486+ /* construct the full socket path */
487+ if (nc_session_unix_construct_socket_path (filename , & path )) {
488+ return NULL ;
489+ }
490+
386491 return path ;
387492}
388493
@@ -543,7 +648,7 @@ nc_sock_accept_binds(struct nc_endpt *endpt, struct nc_bind *binds, uint16_t bin
543648 pthread_mutex_t * bind_lock , int timeout , char * * host , uint16_t * port , uint16_t * idx , int * sock )
544649{
545650 uint16_t i , j , pfd_count , client_port ;
546- char * client_address ;
651+ char * client_address , * sockpath = NULL ;
547652 struct pollfd * pfd ;
548653 struct sockaddr_storage saddr ;
549654 socklen_t saddr_len = sizeof (saddr );
@@ -648,7 +753,11 @@ nc_sock_accept_binds(struct nc_endpt *endpt, struct nc_bind *binds, uint16_t bin
648753 }
649754
650755 if (saddr .ss_family == AF_UNIX ) {
651- VRB (NULL , "Accepted a connection on %s." , endpt ? nc_server_unix_get_socket_path (endpt ) : "UNIX socket" );
756+ if (endpt ) {
757+ sockpath = nc_server_unix_get_socket_path (endpt );
758+ }
759+ VRB (NULL , "Accepted a connection on %s." , sockpath ? sockpath : "UNIX socket" );
760+ free (sockpath );
652761 } else {
653762 VRB (NULL , "Accepted a connection on %s:%u from %s:%u." , binds [i ].address , binds [i ].port , client_address , client_port );
654763 }
@@ -2335,7 +2444,7 @@ nc_ps_clear(struct nc_pollsession *ps, int all, void (*data_free)(void *))
23352444int
23362445nc_server_bind_and_listen (struct nc_endpt * endpt , struct nc_bind * bind )
23372446{
2338- const char * unix_path = NULL ;
2447+ char * unix_path = NULL ;
23392448 int sock = -1 , rc = 0 ;
23402449
23412450 /* start listening on the endpoint */
@@ -2378,6 +2487,7 @@ nc_server_bind_and_listen(struct nc_endpt *endpt, struct nc_bind *bind)
23782487 }
23792488
23802489cleanup :
2490+ free (unix_path );
23812491 return rc ;
23822492}
23832493
@@ -4382,3 +4492,21 @@ nc_server_get_unix_socket_path(const char *endpoint_name)
43824492 pthread_rwlock_unlock (& server_opts .config_lock );
43834493 return socket_path ;
43844494}
4495+
4496+ API int
4497+ nc_server_set_unix_socket_dir (const char * dir )
4498+ {
4499+ int rc = 0 ;
4500+
4501+ /* CONFIG WRITE LOCK */
4502+ pthread_rwlock_wrlock (& server_opts .config_lock );
4503+
4504+ free (server_opts .unix_socket_dir );
4505+ server_opts .unix_socket_dir = strdup (dir );
4506+ NC_CHECK_ERRMEM_GOTO (!server_opts .unix_socket_dir , rc = 1 , cleanup );
4507+
4508+ cleanup :
4509+ /* CONFIG WRITE UNLOCK */
4510+ pthread_rwlock_unlock (& server_opts .config_lock );
4511+ return rc ;
4512+ }
0 commit comments