-
Notifications
You must be signed in to change notification settings - Fork 296
New issue
Have a question about this project? # for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “#”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? # to your account
Additional systemd service hardening #11
Conversation
Add ProtectSystem=full, NoNewPrivileges=true, PrivateDevices=true, and a restricted CapabilityBoundingSet. There's no reason to comment out any of these options - according to the systemd documentation at https://www.freedesktop.org/software/systemd/man/systemd.unit.html : > If systemd encounters an unknown option, it will write a warning log message but continue loading the unit. So it's fine to leave in options that older versions may not understand - they'll simply be ignored.
ProtectSystem=full | ||
NoNewPrivileges=true | ||
PrivateDevices=true | ||
CapabilityBoundingSet=CAP_CHOWN CAP_DAC_OVERRIDE CAP_IPC_LOCK CAP_KILL CAP_NET_BIND_SERVICE CAP_SETGID CAP_SETUID CAP_SYS_CHROOT |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This might lack some caps.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Any thoughts on what caps may be missing?
And what makes you think there may be some missing? Is there a problem that you see when you use this service unit?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'll test these.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
NoNewPrivileges prevents UID changes, and we need to do UID changes. I need to adapt this.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@cmouse I don't think NoNewPrivileges
prevents UID changes - it prevents increasing privileges once they've been lost. If, for example, dovecot starts as root, then drops privileges and changes UID to dovecot
, that would be fine. If, on the other hand, dovecot starts as root, then drops privileges and changes UID to dovecot
, then tries to regain dropped privileges, then NoNewPrivileges
would block that.
I'm curious - is there a problem that you're seeing? I'd be really interested in knowing what the problem may be.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have not yet properly tried it, but in some cases some dovecot processes need to change their EUID to some user, and then back to root, and again to some other user. I'll try this out with this systemd unit and see if it really works.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
After testing it, it was missing CAP_SYS_RESOURCE. Seems that NoNewPrivileges at least initially works with environment with multiple UIDs for users.
Sep 19 09:03:52 cypher dovecot[10585]: master: Error: setrlimit(RLIMIT_NPROC, 4390): Operation not permitted
Looks like you already made these changes, thanks! |
Yes forgot to close this. Streamlined these into other changes. Thank you for your contribution. |
This fixes a race condition where the http_client_host_shared_idle_timeout() function would get called with an already freed hshared argument. Specifically, the situation arises from the hshared idle timeout calling http_client_host_shared_free(), which removes the timeout and then proceeds to free the client queue. The client queue freeing code indirectly calls http_client_host_shared_check_idle(), which notices that there is no idle timeout and allocates one. The backtrace at the point of this new timeout allocation: frame #3: 0x00007f0c775897f0 libdovecot.so.0`timeout_add_to(...) ioloop.c:280 frame #4: 0x00007f0c7751a45f libdovecot.so.0`http_client_host_shared_check_idle(hshared=<unavailable>) at http-client-host.c:69 frame #5: 0x00007f0c7750de89 libdovecot.so.0`http_client_request_error(_req=<unavailable>, status=9000, error="") at http-client-request.c:1525 frame #6: 0x00007f0c77517f38 libdovecot.so.0`http_client_queue_fail_full(queue=0x000055e13cff0e10, status=9000, error="", all=<unavailable>) at http-client-queue.c:183 frame #7: 0x00007f0c77518baa libdovecot.so.0`http_client_queue_free(queue=0x000055e13cff0e10) at http-client-queue.c:141 frame #8: 0x00007f0c7751a8bc libdovecot.so.0`http_client_host_free_shared(_host=<unavailable>) at http-client-host.c:391 frame #9: 0x00007f0c7751ab4c libdovecot.so.0`http_client_host_shared_free(_hshared=0x00007ffdac109e48) at http-client-host.c:294 frame #10: 0x00007f0c7751ace8 libdovecot.so.0`http_client_host_shared_idle_timeout(hshared=<unavailable>) at http-client-host.c:40 frame #11: 0x00007f0c7758a1a4 libdovecot.so.0`io_loop_handle_timeouts at ioloop.c:682 frame #12: 0x00007f0c7758a089 libdovecot.so.0`io_loop_handle_timeouts(ioloop=0x000055e13cfc8d80) at ioloop.c:696 frame #13: 0x00007f0c7758befc libdovecot.so.0`io_loop_handler_run_internal(ioloop=0x000055e13cfc8d80) at ioloop-select.c:126 frame #14: 0x00007f0c7758a56d libdovecot.so.0`io_loop_handler_run(ioloop=<unavailable>) at ioloop.c:767 frame #15: 0x00007f0c7758a798 libdovecot.so.0`io_loop_run(ioloop=0x000055e13cfc8d80) at ioloop.c:740 frame #16: 0x00007f0c774f61eb libdovecot.so.0`master_service_run(service=0x000055e13cfc8c10, callback=<unavailable>) at master-service.c:782 frame #17: 0x000055e13b48e3a5 stats`main(argc=<unavailable>, argv=<unavailable>) at main.c:99 frame #18: 0x00007f0c771092e1 libc.so.6`__libc_start_main + 241 frame #19: 0x000055e13b48e41a stats`_start + 42
This fixes a race condition where the http_client_host_shared_idle_timeout() function would get called with an already freed hshared argument. Specifically, the situation arises from the hshared idle timeout calling http_client_host_shared_free(), which removes the timeout and then proceeds to free the client queue. The client queue freeing code indirectly calls http_client_host_shared_check_idle(), which notices that there is no idle timeout and allocates one. The backtrace at the point of this new timeout allocation: frame #3: 0x00007f0c775897f0 libdovecot.so.0`timeout_add_to(...) ioloop.c:280 frame #4: 0x00007f0c7751a45f libdovecot.so.0`http_client_host_shared_check_idle(hshared=<unavailable>) at http-client-host.c:69 frame #5: 0x00007f0c7750de89 libdovecot.so.0`http_client_request_error(_req=<unavailable>, status=9000, error="") at http-client-request.c:1525 frame #6: 0x00007f0c77517f38 libdovecot.so.0`http_client_queue_fail_full(queue=0x000055e13cff0e10, status=9000, error="", all=<unavailable>) at http-client-queue.c:183 frame #7: 0x00007f0c77518baa libdovecot.so.0`http_client_queue_free(queue=0x000055e13cff0e10) at http-client-queue.c:141 frame #8: 0x00007f0c7751a8bc libdovecot.so.0`http_client_host_free_shared(_host=<unavailable>) at http-client-host.c:391 frame #9: 0x00007f0c7751ab4c libdovecot.so.0`http_client_host_shared_free(_hshared=0x00007ffdac109e48) at http-client-host.c:294 frame #10: 0x00007f0c7751ace8 libdovecot.so.0`http_client_host_shared_idle_timeout(hshared=<unavailable>) at http-client-host.c:40 frame #11: 0x00007f0c7758a1a4 libdovecot.so.0`io_loop_handle_timeouts at ioloop.c:682 frame #12: 0x00007f0c7758a089 libdovecot.so.0`io_loop_handle_timeouts(ioloop=0x000055e13cfc8d80) at ioloop.c:696 frame #13: 0x00007f0c7758befc libdovecot.so.0`io_loop_handler_run_internal(ioloop=0x000055e13cfc8d80) at ioloop-select.c:126 frame #14: 0x00007f0c7758a56d libdovecot.so.0`io_loop_handler_run(ioloop=<unavailable>) at ioloop.c:767 frame #15: 0x00007f0c7758a798 libdovecot.so.0`io_loop_run(ioloop=0x000055e13cfc8d80) at ioloop.c:740 frame #16: 0x00007f0c774f61eb libdovecot.so.0`master_service_run(service=0x000055e13cfc8c10, callback=<unavailable>) at master-service.c:782 frame #17: 0x000055e13b48e3a5 stats`main(argc=<unavailable>, argv=<unavailable>) at main.c:99 frame #18: 0x00007f0c771092e1 libc.so.6`__libc_start_main + 241 frame #19: 0x000055e13b48e41a stats`_start + 42
This fixes a race condition where the http_client_host_shared_idle_timeout() function would get called with an already freed hshared argument. Specifically, the situation arises from the hshared idle timeout calling http_client_host_shared_free(), which removes the timeout and then proceeds to free the client queue. The client queue freeing code indirectly calls http_client_host_shared_check_idle(), which notices that there is no idle timeout and allocates one. The backtrace at the point of this new timeout allocation: frame #3: 0x00007f0c775897f0 libdovecot.so.0`timeout_add_to(...) ioloop.c:280 frame #4: 0x00007f0c7751a45f libdovecot.so.0`http_client_host_shared_check_idle(hshared=<unavailable>) at http-client-host.c:69 frame #5: 0x00007f0c7750de89 libdovecot.so.0`http_client_request_error(_req=<unavailable>, status=9000, error="") at http-client-request.c:1525 frame #6: 0x00007f0c77517f38 libdovecot.so.0`http_client_queue_fail_full(queue=0x000055e13cff0e10, status=9000, error="", all=<unavailable>) at http-client-queue.c:183 frame #7: 0x00007f0c77518baa libdovecot.so.0`http_client_queue_free(queue=0x000055e13cff0e10) at http-client-queue.c:141 frame #8: 0x00007f0c7751a8bc libdovecot.so.0`http_client_host_free_shared(_host=<unavailable>) at http-client-host.c:391 frame #9: 0x00007f0c7751ab4c libdovecot.so.0`http_client_host_shared_free(_hshared=0x00007ffdac109e48) at http-client-host.c:294 frame #10: 0x00007f0c7751ace8 libdovecot.so.0`http_client_host_shared_idle_timeout(hshared=<unavailable>) at http-client-host.c:40 frame #11: 0x00007f0c7758a1a4 libdovecot.so.0`io_loop_handle_timeouts at ioloop.c:682 frame #12: 0x00007f0c7758a089 libdovecot.so.0`io_loop_handle_timeouts(ioloop=0x000055e13cfc8d80) at ioloop.c:696 frame #13: 0x00007f0c7758befc libdovecot.so.0`io_loop_handler_run_internal(ioloop=0x000055e13cfc8d80) at ioloop-select.c:126 frame #14: 0x00007f0c7758a56d libdovecot.so.0`io_loop_handler_run(ioloop=<unavailable>) at ioloop.c:767 frame #15: 0x00007f0c7758a798 libdovecot.so.0`io_loop_run(ioloop=0x000055e13cfc8d80) at ioloop.c:740 frame #16: 0x00007f0c774f61eb libdovecot.so.0`master_service_run(service=0x000055e13cfc8c10, callback=<unavailable>) at master-service.c:782 frame #17: 0x000055e13b48e3a5 stats`main(argc=<unavailable>, argv=<unavailable>) at main.c:99 frame #18: 0x00007f0c771092e1 libc.so.6`__libc_start_main + 241 frame #19: 0x000055e13b48e41a stats`_start + 42
Add ProtectSystem=full, NoNewPrivileges=true, PrivateDevices=true, and a restricted CapabilityBoundingSet.
There's no reason to comment out any of these options - according to the systemd documentation at https://www.freedesktop.org/software/systemd/man/systemd.unit.html :
So it's fine to leave in options that older versions may not understand - they'll simply be ignored.