2828
2929#include "private-lib-core.h"
3030#include <unistd.h>
31+ #include <sys/types.h>
32+ #include <pwd.h>
33+ #include <grp.h>
34+
35+ #if defined(__linux__ )
36+ #include <sys/stat.h>
37+ #endif
3138
3239#if defined(__OpenBSD__ ) || defined(__NetBSD__ )
3340#include <sys/resource.h>
@@ -51,7 +58,7 @@ lws_spawn_sul_reap(struct lws_sorted_usec_list *sul)
5158 struct lws_spawn_piped * lsp = lws_container_of (sul ,
5259 struct lws_spawn_piped , sul_reap );
5360
54- lwsl_notice ("%s: reaping spawn after last stdpipe, tries left %d\n" ,
61+ lwsl_info ("%s: reaping spawn after last stdpipe, tries left %d\n" ,
5562 __func__ , lsp -> reap_retry_budget );
5663 if (!lws_spawn_reap (lsp ) && !lsp -> pipes_alive ) {
5764 if (-- lsp -> reap_retry_budget ) {
@@ -212,6 +219,21 @@ lws_spawn_reap(struct lws_spawn_piped *lsp)
212219
213220 lws_sul_cancel (& lsp -> sul );
214221
222+ #if defined(__linux__ )
223+ if (lsp -> cgroup_path [0 ]) {
224+ /*
225+ * The child has been reaped, we can remove the cgroup dir.
226+ * This will only work if the cgroup is empty, which it should
227+ * be now.
228+ */
229+ if (rmdir (lsp -> cgroup_path ))
230+ lwsl_warn ("%s: unable to rmdir cgroup %s, errno %d\n" ,
231+ __func__ , lsp -> cgroup_path , errno );
232+ else
233+ lwsl_info ("%s: reaped cgroup %s\n" , __func__ , lsp -> cgroup_path );
234+ }
235+ #endif
236+
215237 /*
216238 * All the stdwsi went down, nothing more is coming... it's over
217239 * Collect the final information and then reap the dead process
@@ -240,7 +262,7 @@ lws_spawn_reap(struct lws_spawn_piped *lsp)
240262 n = waitid (P_PID , (id_t )lsp -> child_pid , & temp .si , WEXITED | WNOHANG );
241263#endif
242264 temp .si .si_status &= 0xff ; /* we use b8 + for flags */
243- lwsl_info ("%s: waitd says %d, process exit %d\n" ,
265+ lwsl_warn ("%s: waitd says %d, process exit %d\n" ,
244266 __func__ , n , temp .si .si_status );
245267
246268 lsp -> child_pid = -1 ;
@@ -319,6 +341,56 @@ lws_spawn_piped_kill_child_process(struct lws_spawn_piped *lsp)
319341 return 0 ;
320342}
321343
344+ int
345+ lws_spawn_get_self_cgroup (char * cgroup , size_t max )
346+ {
347+ int fd = open ("/proc/self/cgroup" , O_RDONLY );
348+ ssize_t r ;
349+ char * p , s [256 ];
350+
351+ if (fd < 0 ) {
352+ lwsl_err ("%s: unable to open /proc/self/cgroup\n" , __func__ );
353+ return 1 ;
354+ }
355+
356+ r = read (fd , s , sizeof (s ) - 1 );
357+ close (fd );
358+ if (r < 0 ) {
359+ lwsl_err ("%s: unable to read from /proc/self/cgroup\n" , __func__ );
360+
361+ return 1 ;
362+ }
363+
364+ s [r ] = '\0' ;
365+ p = strchr (s , ':' );
366+
367+ if (!p ) {
368+ lwsl_err ("%s: unable to find first : '%s'\n" , __func__ , s );
369+ return 1 ;
370+ }
371+
372+ p = strchr (p + 1 , ':' );
373+ if (!p || (r - (p - s ) < 3 )) {
374+ lwsl_err ("%s: unable to find second : '%s'\n" , __func__ , s );
375+
376+ return 1 ;
377+ }
378+ p ++ ;
379+
380+ if (p [strlen (p ) - 1 ] == '\n' )
381+ p [strlen (p ) - 1 ] = '\0' ;
382+
383+ if ((size_t )(r - (p - s )) + 1 > max - 1 ) {
384+ lwsl_err ("%s: cgroup name too large : '%s'\n" , __func__ , s );
385+
386+ return 1 ;
387+ }
388+
389+ memcpy (cgroup , p , (size_t )(r - (p - s ) + 1 ));
390+
391+ return 0 ;
392+ }
393+
322394/*
323395 * Deals with spawning a subprocess and executing it securely with stdin/out/err
324396 * diverted into pipes
@@ -331,7 +403,7 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
331403 struct lws_context * context = i -> vh -> context ;
332404 struct lws_spawn_piped * lsp ;
333405 const char * wd ;
334- int n , m ;
406+ int n , m , do_cgroup = 0 ;
335407
336408 if (i -> protocol_name )
337409 pcol = lws_vhost_name_to_protocol (i -> vh , i -> protocol_name );
@@ -350,6 +422,13 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
350422 lsp -> info = * i ;
351423 lsp -> reap_retry_budget = 20 ;
352424
425+ #if defined(__linux__ )
426+ lsp -> cgroup_path [0 ] = '\0' ;
427+ #endif
428+
429+ if (i -> p_cgroup_ret )
430+ * i -> p_cgroup_ret = 1 ; /* Default to cgroup failed */
431+
353432 /*
354433 * Prepare the stdin / out / err pipes
355434 */
@@ -445,6 +524,55 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
445524 lsp -> stdwsi [LWS_STDIN ]-> desc .sockfd ,
446525 lsp -> stdwsi [LWS_STDOUT ]-> desc .sockfd ,
447526 lsp -> stdwsi [LWS_STDERR ]-> desc .sockfd );
527+
528+ #if defined(__linux__ )
529+ if (i -> cgroup_name_suffix && i -> cgroup_name_suffix [0 ]) {
530+ char self_cg [256 ];
531+
532+ if (lws_spawn_get_self_cgroup (self_cg , sizeof (self_cg ) - 1 ))
533+ lwsl_err ("%s: failed to get self cgroup\n" , __func__ );
534+ else {
535+ lws_snprintf (lsp -> cgroup_path , sizeof (lsp -> cgroup_path ),
536+ "/sys/fs/cgroup%s/%s" , self_cg , i -> cgroup_name_suffix );
537+
538+ if (mkdir (lsp -> cgroup_path , 0755 )) {
539+ lwsl_warn ("%s: failed to generate cgroup dir %s: errno %d\n" ,
540+ __func__ , lsp -> cgroup_path , errno );
541+ lsp -> cgroup_path [0 ] = '\0' ;
542+ } else {
543+ char pth [300 ];
544+ int cfd ;
545+
546+ lws_snprintf (pth , sizeof (pth ), "%s/cgroup.type" , lsp -> cgroup_path );
547+ cfd = lws_open (pth , LWS_O_WRONLY );
548+ if (cfd >= 0 ) {
549+ if (write (cfd , "threaded" , 8 ) != 8 )
550+ lwsl_warn ("%s: failed to write threaded\n" , __func__ );
551+
552+ close (cfd );
553+ }
554+
555+
556+ lwsl_info ("%s: created cgroup %s\n" , __func__ , lsp -> cgroup_path );
557+ lws_snprintf (pth , sizeof (pth ), "%s/pids.max" , lsp -> cgroup_path );
558+ cfd = lws_open (pth , LWS_O_WRONLY );
559+ if (cfd >= 0 ) {
560+ if (write (cfd , "max" , 3 ) != 3 )
561+ lwsl_warn ("%s: failed to write max\n" , __func__ );
562+
563+ close (cfd );
564+ }
565+
566+ do_cgroup = 1 ;
567+ }
568+ }
569+ }
570+
571+ if (i -> p_cgroup_ret )
572+ /* Report cgroup success to caller */
573+ * i -> p_cgroup_ret = !do_cgroup ;
574+
575+ #endif
448576
449577 /* we are ready with the redirection pipes... do the (v)fork */
450578#if defined(__sun ) || !defined(LWS_HAVE_VFORK ) || !defined(LWS_HAVE_EXECVPE )
@@ -510,6 +638,32 @@ lws_spawn_piped(const struct lws_spawn_piped_info *i)
510638 * process is OK. Stuff that happens after the execvpe() is OK.
511639 */
512640
641+ #if defined(__linux__ )
642+ if (lsp -> cgroup_path [0 ]) {
643+ char path [300 ], pid_str [20 ];
644+ int fd , len ;
645+
646+ /*
647+ * We are the new child process. We must move ourselves into
648+ * the cgroup created for us by the parent.
649+ */
650+ lws_snprintf (path , sizeof (path ) - 1 , "%s/cgroup.procs" , lsp -> cgroup_path );
651+ fd = open (path , O_WRONLY );
652+ if (fd >= 0 ) {
653+ len = lws_snprintf (pid_str , sizeof (pid_str ) - 1 , "%d" , (int )getpid ());
654+ if (write (fd , pid_str , (size_t )len ) != (ssize_t )len ) {
655+ /*
656+ * using lwsl_err here is unsafe in vfork()
657+ * child, just exit with a special code
658+ */
659+ _exit (121 );
660+ }
661+ close (fd );
662+ } else
663+ _exit (122 );
664+ }
665+ #endif
666+
513667 if (i -> chroot_path && chroot (i -> chroot_path )) {
514668 lwsl_err ("%s: child chroot %s failed, errno %d\n" ,
515669 __func__ , i -> chroot_path , errno );
@@ -603,6 +757,21 @@ lws_spawn_stdwsi_closed(struct lws_spawn_piped *lsp, struct lws *wsi)
603757{
604758 int n ;
605759
760+ /*
761+ * This is part of the normal cleanup path, check if the lsp has already
762+ * been destroyed by a timeout or other error path. If the stdwsi that
763+ * is closing has already been nulled out, we have already been through
764+ * destroy.
765+ */
766+ for (n = 0 ; n < 3 ; n ++ )
767+ if (lsp -> stdwsi [n ] == wsi )
768+ goto found ;
769+
770+ /* Not found, so must have been destroyed already */
771+ return ;
772+
773+ found :
774+
606775 assert (lsp );
607776 lsp -> pipes_alive -- ;
608777 lwsl_debug ("%s: pipes alive %d\n" , __func__ , lsp -> pipes_alive );
@@ -621,6 +790,82 @@ lws_spawn_get_stdfd(struct lws *wsi)
621790 return wsi -> lsp_channel ;
622791}
623792
793+ int
794+ lws_spawn_prepare_self_cgroup (const char * user , const char * group )
795+ {
796+ #if defined(__linux__ )
797+ uid_t uid = (uid_t )- 1 ;
798+ gid_t gid = (gid_t )- 1 ;
799+ char path [256 ], self_cgroup [256 ];
800+ int fd ;
801+
802+ if (lws_spawn_get_self_cgroup (self_cgroup , sizeof (self_cgroup ) - 1 )) {
803+ lwsl_err ("%s: unable to get self cgroup\n" , __func__ );
804+
805+ return 1 ;
806+ }
807+
808+ lws_snprintf (path , sizeof (path ), "/sys/fs/cgroup%s/cgroup.subtree_control" ,
809+ self_cgroup );
810+
811+ fd = lws_open (path , LWS_O_WRONLY );
812+ if (fd < 0 ) {
813+ /* May fail if user doesn't own the file, that's okay */
814+ lwsl_info ("%s: cannot open subtree_control: %s\n" ,
815+ __func__ , strerror (errno ));
816+ return 0 ; /* Still a success if dir exists */
817+ }
818+
819+ if (write (fd , "+cpu +memory +pids +io" , 22 ) != 22 )
820+ /* ignore, may be there already or fail due to perms */
821+ lwsl_debug ("%s: setting admin cgroup options failed\n" , __func__ );
822+ close (fd );
823+
824+ lws_snprintf (path , sizeof (path ), "/sys/fs/cgroup%s" , self_cgroup );
825+
826+ if (user ) {
827+ struct passwd * pwd ;
828+
829+ pwd = getpwnam (user );
830+ if (pwd )
831+ uid = pwd -> pw_uid ;
832+ else
833+ lwsl_warn ("%s: user '%s' not found\n" , __func__ , user );
834+ }
835+ if (group ) {
836+ struct group * grp ;
837+
838+ grp = getgrnam (group );
839+ if (grp )
840+ gid = grp -> gr_gid ;
841+ else
842+ lwsl_warn ("%s: group '%s' not found\n" , __func__ , group );
843+ }
844+
845+ lwsl_notice ("%s: switching %s to %d:%d\n" ,
846+ __func__ , path , uid , gid );
847+
848+ if (chown (path , uid , gid ) < 0 )
849+ lwsl_warn ("%s: failed to chown %s: %s\n" ,
850+ __func__ , path , strerror (errno ));
851+ /* 2. ALSO change ownership of the critical control files inside it */
852+ lws_snprintf (path , sizeof (path ), "/sys/fs/cgroup%s/cgroup.procs" , self_cgroup );
853+ if (chown (path , uid , gid ) < 0 )
854+ lwsl_warn ("%s: failed to chown %s: %s\n" ,
855+ __func__ , path , strerror (errno ));
856+
857+ lws_snprintf (path , sizeof (path ), "/sys/fs/cgroup%s/cgroup.subtree_control" , self_cgroup );
858+ if (chown (path , uid , gid ) < 0 )
859+ lwsl_warn ("%s: failed to chown %s: %s\n" ,
860+ __func__ , path , strerror (errno ));
861+
862+ lwsl_notice ("%s: lws cgroup parent configured\n" , __func__ );
863+
864+ return 0 ;
865+ #endif
866+ return 1 ; /* Not supported on this platform */
867+ }
868+
624869int
625870lws_spawn_get_fd_stdxxx (struct lws_spawn_piped * lsp , int std_idx )
626871{
0 commit comments