openSUSE / xinetd

xinetd is a secure replacement for inetd - openSUSE fork to contain all the patches we had
Other
26 stars 16 forks source link

xinetd stuck for about 2 minutes on initializing when running in an Alpine container (host: Kali Linux) #47

Open goudunz1 opened 2 days ago

goudunz1 commented 2 days ago

Dear developers,

I observed a very strange behavior after I compiled xinetd version 2.3.15.4 and hosted a test program in an Alpine docker container: xinetd was stuck and kept consuming 100% CPU for about 2 minutes before it was able to bind to the specified port.

After some debugging with strace and ltrace, I found that xinetd fell into a very long loop during initialization:

execve("/usr/sbin/xinetd", ["xinetd"], 0x7ffefd9dbc50 /* 6 vars */) = 0
arch_prctl(ARCH_SET_FS, 0x7f1920f81b28) = 0
set_tid_address(0x7f1920f81f90)         = 13
brk(NULL)                               = 0x5608f3fa1000
brk(0x5608f3fa3000)                     = 0x5608f3fa3000
mmap(0x5608f3fa1000, 4096, PROT_NONE, MAP_PRIVATE|MAP_FIXED|MAP_ANONYMOUS, -1, 0) = 0x5608f3fa1000
mprotect(0x7f1920f7e000, 4096, PROT_READ) = 0
mprotect(0x5608f2448000, 8192, PROT_READ) = 0
prlimit64(0, RLIMIT_NOFILE, NULL, {rlim_cur=1073741816, rlim_max=1073741816}) = 0
close(3)                                = -1 EBADF (Bad file descriptor)
close(4)                                = -1 EBADF (Bad file descriptor)
close(5)                                = -1 EBADF (Bad file descriptor)
close(6)                                = -1 EBADF (Bad file descriptor)
close(7)                                = -1 EBADF (Bad file descriptor)
close(8)                                = -1 EBADF (Bad file descriptor)
close(9)                                = -1 EBADF (Bad file descriptor)
close(10)                               = -1 EBADF (Bad file descriptor)
... ( and it kept growing )

I think the problem is that xinetd only checks whether the nofile limit is RLIM_INFINITY during file descriptor setting. However, on my platform the default nofile limit is set to 0x3ffffff8 (1073741816), despite RLIM_INFINITY being -1. xinetd tries to clean all the file descriptor from 3-0x3ffffff7 during initialization, which leads to a 'forever' loop.

[Wed 20:36][kali㉿~]$ ulimit -Hn
1073741816

src/init.c (line 152)

   if ( rl.rlim_max == RLIM_INFINITY ) 
      rl.rlim_max = MAX_FDS;

Running the container with --ulimit nofile=4096:4096 and there's no stuck. It seems like changing line 152 to rl.rlim_max > MAX_FDS could be a proper fix.

I'm not sure whether this issue counts as a bug because fixing it does changes xinetd's default behavior a little bit. I know the original intention is to use a larger nofile number other than MAX_FDS(4096), but when nofile limit is set to a large number other than RLIM_INFINITY, the initialization will become extremely slow and CPU consuming.

My host platform: Kali 6.6.9-1kali1 (2024-01-08) x86_64 GNU/Linux

My container: Alpine Linux 3.20.3

The dockerfile I used to compile xinetd 2.3.15.4:

FROM alpine:latest AS builder

ARG XINETD_VERSION=2.3.15.4

RUN apk add build-base autoconf automake libtool pkgconf git
RUN git clone -b ${XINETD_VERSION} https://github.com/openSUSE/xinetd
RUN cd xinetd && sh ./autogen.sh && ./configure && make

FROM alpine:latest

COPY --from=builder /xinetd/xinetd /usr/sbin
RUN mkdir -p /etc/xinetd.d/

# ... ( some other matters )

xinetd.conf I used:

service ctf
{
    disable     = no
    id          = xinetd
    socket_type = stream
    protocol    = tcp
    wait        = no
    user        = root
    type        = UNLISTED
    port        = 8888
    bind        = 0.0.0.0
    server      = /init.sh
    # safety options
    per_source  = 10 # the maximum instances of this service per source IP address
    rlimit_cpu  = 20 # the maximum number of CPU seconds that the service may use
    rlimit_as   = 100M # the Address Space resource limit for the service
}

xinetd command I used: xinetd -stayalive -dontfork

Hope to hear from you soon.

vapier commented 1 day ago

this is init.c:setup_file_descriptors(). we should see if closefrom() is available (via configure-time test) as that'll be way more efficient.

goudunz1 commented 1 day ago

this is init.c:setup_file_descriptors(). we should see if closefrom() is available (via configure-time test) as that'll be way more efficient.

Thanks for your time! I'll just run the container with a relatively small nofile limit for now.