diff --git a/docs/docs.lua b/docs/docs.lua index a412ddd2..cade463b 100644 --- a/docs/docs.lua +++ b/docs/docs.lua @@ -4461,6 +4461,7 @@ local doc = { }, notes = { 'This is not a libuv function and is not supported on Windows.', + 'When dropping privileges from root, calling `setuid()` alone is not sufficient — supplementary group IDs are not affected by `setuid()` or `setgid()` and must be dropped separately. Failure to do so is a security vulnerability (CERT POS36-C). `uv.setuid()` rejects root-to-non-root transitions until both primary group privileges and supplementary groups have already been dropped. The correct order is: `uv.setgroups({})` (or `uv.initgroups(user, gid)`), then `uv.setgid(gid)`, then `uv.setuid(uid)` (must be last, as it is irreversible).', }, }, { @@ -4469,6 +4470,36 @@ local doc = { params = { { name = 'id', type = 'integer' }, }, + notes = { + 'This is not a libuv function and is not supported on Windows.', + 'When dropping privileges, supplementary groups must be dropped before calling `setgid()` and `setuid()`. See the security warning in `uv.setuid()` for the correct privilege-dropping sequence.', + }, + }, + { + name = 'getgroups', + desc = 'Returns a table containing the list of supplementary group IDs for the process.', + returns = 'table', + notes = { + 'This is not a libuv function and is not supported on Windows.', + }, + }, + { + name = 'setgroups', + desc = 'Sets the supplementary group IDs for the process. Pass an empty table `{}` to drop all supplementary groups. Requires appropriate privileges (typically root).', + params = { + { name = 'groups', type = 'table' }, + }, + notes = { + 'This is not a libuv function and is not supported on Windows.', + }, + }, + { + name = 'initgroups', + desc = 'Initializes the supplementary group access list. Sets the supplementary group IDs based on the group database (e.g., `/etc/group`) for the given `user`, plus the specified base `group` ID. Requires appropriate privileges (typically root).', + params = { + { name = 'user', type = 'string' }, + { name = 'group', type = 'integer' }, + }, notes = { 'This is not a libuv function and is not supported on Windows.', }, diff --git a/docs/docs.md b/docs/docs.md index a7eb0587..cc509d38 100644 --- a/docs/docs.md +++ b/docs/docs.md @@ -649,7 +649,8 @@ See [Reference counting][]. Gets or sets the size of the send buffer that the operating system uses for the socket. -If `size` is omitted (or `0`), this will return the current send buffer size; otherwise, this will use `size` to set the new send buffer size. +If `size` is omitted (or `0`), this will return the current send buffer size; +otherwise, this will use `size` to set the new send buffer size. This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP handles on Windows. @@ -672,7 +673,8 @@ original set value. Gets or sets the size of the receive buffer that the operating system uses for the socket. -If `size` is omitted (or `0`), this will return the current send buffer size; otherwise, this will use `size` to set the new send buffer size. +If `size` is omitted (or `0`), this will return the current send buffer size; +otherwise, this will use `size` to set the new send buffer size. This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP handles on Windows. @@ -711,8 +713,9 @@ control of the file descriptor so any change to it may lead to malfunction. **Parameters:** - `handle`: `userdata` for sub-type of `uv_handle_t` -Returns the name of the struct for a given handle (e.g. `"pipe"` for `uv_pipe_t`) -and the libuv enum integer for the handle's type (`uv_handle_type`). +Returns the name of the struct for a given handle (e.g. `"pipe"` for +`uv_pipe_t`) and the libuv enum integer for the handle's type +(`uv_handle_type`). **Returns:** `string`, `integer` @@ -852,7 +855,8 @@ Get the timer repeat value. **Parameters:** - `timer`: `uv_timer_t userdata` -Get the timer due value or 0 if it has expired. The time is relative to `uv.now()`. +Get the timer due value or 0 if it has expired. The time is relative to +`uv.now()`. **Returns:** `integer` @@ -1125,9 +1129,9 @@ The socket is set to non-blocking mode. Starts polling the file descriptor. `events` are: `"r"`, `"w"`, `"rw"`, `"d"`, `"rd"`, `"wd"`, `"rwd"`, `"p"`, `"rp"`, `"wp"`, `"rwp"`, `"dp"`, `"rdp"`, `"wdp"`, or `"rwdp"` where `r` is `READABLE`, `w` is `WRITABLE`, `d` is -`DISCONNECT`, and `p` is `PRIORITIZED`. As soon as an event is detected -the callback will be called with status set to 0, and the detected events set on -the events field. +`DISCONNECT`, and `p` is `PRIORITIZED`. As soon as an event is detected the +callback will be called with status set to 0, and the detected events set on the +events field. The user should not close the socket while the handle is active. If the user does that anyway, the callback may be called reporting an error status, but this @@ -1233,7 +1237,8 @@ See [Constants][] for supported `signame` input and output values. - `callback`: `callable` - `signame`: `string` -Same functionality as `uv.signal_start()` but the signal handler is reset the moment the signal is received. +Same functionality as `uv.signal_start()` but the signal handler is reset the +moment the signal is received. See [Constants][] for supported `signame` input and output values. @@ -1279,16 +1284,14 @@ a better job on Windows than it does on Unix. **Parameters:** - `path`: `string` - `options`: `table` - - `args`: `string[]` or `nil` - Command line arguments as a list of strings. The first - string should *not* be the path to the program, since that is already + - `args`: `string[]` or `nil` Command line arguments as a list of strings. The + first string should *not* be the path to the program, since that is already provided via `path`. On Windows, this uses CreateProcess which concatenates - the arguments into a string. This can cause some strange errors - (see `options.verbatim` below for Windows). - - `stdio`: `table` or `nil` - Set the file descriptors that will be made available to - the child process. The convention is that the first entries are stdin, stdout, - and stderr. + the arguments into a string. This can cause some strange errors (see + `options.verbatim` below for Windows). + - `stdio`: `table` or `nil` Set the file descriptors that will be made + available to the child process. The convention is that the first entries are + stdin, stdout, and stderr. The entries can take many shapes. - If `integer`, then the child process inherits that same zero-indexed @@ -1297,31 +1300,28 @@ a better job on Windows than it does on Unix. or inherited stream depending if the stream has a valid fd. - If `nil`, means to ignore that fd in the child process. - **Note**: On Windows, file descriptors after the third are - available to the child process only if the child processes uses the MSVCRT - runtime. - - `[1, 2, 3, ..., n]`: `integer` or `userdata` for sub-type of `uv_stream_t` or `nil` - - `env`: `string[]` or `nil` - Set environment variables for the new process. + **Note**: On Windows, file descriptors after the third are available to the + child process only if the child processes uses the MSVCRT runtime. + - `[1, 2, 3, ..., n]`: `integer` or `userdata` for sub-type of `uv_stream_t` + or `nil` + - `env`: `string[]` or `nil` Set environment variables for the new process. Each entry should be a string in the form of `NAME=VALUE`. - - `cwd`: `string` or `nil` Set the current working directory for the sub-process. + - `cwd`: `string` or `nil` Set the current working directory for the + sub-process. - `uid`: `string` or `nil` Set the child process' user id. - `gid`: `string` or `nil` Set the child process' group id. - - `verbatim`: `boolean` or `nil` - If true, do not wrap any arguments in quotes, or - perform any other escaping, when converting the argument list into a command - line string. This option is only meaningful on Windows systems. On Unix it is - silently ignored. - - `detached`: `boolean` or `nil` - If true, spawn the child process in a detached state - - this will make it a process group leader, and will effectively enable the - child to keep running after the parent exits. Note that the child process - will still keep the parent's event loop alive unless the parent process calls - `uv.unref()` on the child's process handle. - - `hide`: `boolean` or `nil` - If true, hide the subprocess console window that would - normally be created. This option is only meaningful on Windows systems. On + - `verbatim`: `boolean` or `nil` If true, do not wrap any arguments in quotes, + or perform any other escaping, when converting the argument list into + a command line string. This option is only meaningful on Windows systems. On Unix it is silently ignored. + - `detached`: `boolean` or `nil` If true, spawn the child process in + a detached state - this will make it a process group leader, and will + effectively enable the child to keep running after the parent exits. Note + that the child process will still keep the parent's event loop alive unless + the parent process calls `uv.unref()` on the child's process handle. + - `hide`: `boolean` or `nil` If true, hide the subprocess console window that + would normally be created. This option is only meaningful on Windows + systems. On Unix it is silently ignored. - `on_exit`: `callable` - `code`: `integer` - `signal`: `integer` @@ -1382,7 +1382,8 @@ end) When the child process exits, `on_exit` is called with an exit code and signal. -**Returns:** `uv_process_t userdata` or `nil`, `integer` or `string`, `uv.error_name` or `nil` +**Returns:** `uv_process_t userdata` or `nil`, `integer` or `string`, +`uv.error_name` or `nil` ### `uv.process_kill(process, [signame])` @@ -1593,7 +1594,8 @@ Will return number of bytes written (can be less than the supplied buffer size). - `data`: `buffer` - `send_handle`: `userdata` for sub-type of `uv_stream_t` -Like `uv.write2()`, but with the properties of `uv.try_write()`. Not supported on Windows, where it returns `UV_EAGAIN`. +Like `uv.write2()`, but with the properties of `uv.try_write()`. Not supported +on Windows, where it returns `UV_EAGAIN`. Will return number of bytes written (can be less than the supplied buffer size). @@ -1687,7 +1689,8 @@ Open an existing file descriptor or SOCKET as a TCP handle. **Returns:** `0` or `fail` -**Note**: The passed file descriptor or SOCKET is not checked for its type, but it's required that it represents a valid stream socket. +**Note**: The passed file descriptor or SOCKET is not checked for its type, but +it's required that it represents a valid stream socket. ### `uv.tcp_nodelay(tcp, enable)` @@ -1712,7 +1715,9 @@ Enable / disable Nagle's algorithm. - `intvl`: `integer` or `nil` - `cnt`: `integer` or `nil` -Enable / disable TCP keep-alive. `delay` is the initial delay in seconds, `intvl` is the time in seconds between individual keep-alive probes, and `cnt` is the number of probes to send before assuming the connection is dead. +Enable / disable TCP keep-alive. `delay` is the initial delay in seconds, +`intvl` is the time in seconds between individual keep-alive probes, and `cnt` +is the number of probes to send before assuming the connection is dead. ignored when enable is `false`. **Returns:** `0` or `fail` @@ -1847,14 +1852,21 @@ and `uv.tcp_close_reset()` calls is not allowed. - `flags2`: `table` or `nil` - `nonblock`: `boolean` (default: `false`) -Create a pair of connected sockets with the specified properties. The resulting handles can be passed to `uv.tcp_open`, used with `uv.spawn`, or for any other purpose. +Create a pair of connected sockets with the specified properties. The resulting +handles can be passed to `uv.tcp_open`, used with `uv.spawn`, or for any other +purpose. See [Constants][] for supported `socktype` input values. -When `protocol` is set to 0 or nil, it will be automatically chosen based on the socket's domain and type. When `protocol` is specified as a string, it will be looked up using the `getprotobyname(3)` function (examples: `"ip"`, `"icmp"`, `"tcp"`, `"udp"`, etc). +When `protocol` is set to 0 or nil, it will be automatically chosen based on the +socket's domain and type. When `protocol` is specified as a string, it will be +looked up using the `getprotobyname(3)` function (examples: `"ip"`, `"icmp"`, +`"tcp"`, `"udp"`, etc). Flags: - - `nonblock`: Opens the specified socket handle for `OVERLAPPED` or `FIONBIO`/`O_NONBLOCK` I/O usage. This is recommended for handles that will be used by libuv, and not usually recommended otherwise. + - `nonblock`: Opens the specified socket handle for `OVERLAPPED` or + `FIONBIO`/`O_NONBLOCK` I/O usage. This is recommended for handles that will + be used by libuv, and not usually recommended otherwise. Equivalent to `socketpair(2)` with a domain of `AF_UNIX`. @@ -1883,7 +1895,8 @@ end) > [`uv_handle_t`][] and [`uv_stream_t`][] functions also apply. -Pipe handles provide an abstraction over local domain sockets on Unix and named pipes on Windows. +Pipe handles provide an abstraction over local domain sockets on Unix and named +pipes on Windows. ```lua local pipe = uv.new_pipe(false) @@ -2041,10 +2054,14 @@ where `r` is `READABLE` and `w` is `WRITABLE`. This function is blocking. - `write_flags`: `table` or `nil` - `nonblock`: `boolean` (default: `false`) -Create a pair of connected pipe handles. Data may be written to the `write` fd and read from the `read` fd. The resulting handles can be passed to `pipe_open`, used with `spawn`, or for any other purpose. +Create a pair of connected pipe handles. Data may be written to the `write` fd +and read from the `read` fd. The resulting handles can be passed to `pipe_open`, +used with `spawn`, or for any other purpose. Flags: - - `nonblock`: Opens the specified socket handle for `OVERLAPPED` or `FIONBIO`/`O_NONBLOCK` I/O usage. This is recommended for handles that will be used by libuv, and not usually recommended otherwise. + - `nonblock`: Opens the specified socket handle for `OVERLAPPED` or + `FIONBIO`/`O_NONBLOCK` I/O usage. This is recommended for handles that will + be used by libuv, and not usually recommended otherwise. Equivalent to `pipe(2)` with the `O_CLOEXEC` flag set. @@ -2087,7 +2104,8 @@ Bind the pipe to a file path (Unix) or a name (Windows). - If `type(flags)` is `nil`, it use default value `0`. - Returns `EINVAL` for unsupported flags without performing the bind operation. -Supports Linux abstract namespace sockets. namelen must include the leading '\0' byte but not the trailing nul byte. +Supports Linux abstract namespace sockets. namelen must include the leading '\0' +byte but not the trailing nul byte. **Returns:** `0` or `fail` @@ -2116,7 +2134,8 @@ Connect to the Unix domain socket or the named pipe. - If `type(flags)` is `nil`, it use default value `0`. - Returns `EINVAL` for unsupported flags without performing the bind operation. -Supports Linux abstract namespace sockets. namelen must include the leading nul byte but not the trailing nul byte. +Supports Linux abstract namespace sockets. namelen must include the leading nul +byte but not the trailing nul byte. **Returns:** `uv_connect_t userdata` or `fail` @@ -2167,7 +2186,8 @@ ttyname_r(3), open it, and use it if the passed file descriptor refers to a TTY. This lets libuv put the tty in non-blocking mode without affecting other processes that share the tty. -This function is not thread safe on systems that don’t support ioctl TIOCGPTN or TIOCPTYGNAME, for instance OpenBSD and Solaris. +This function is not thread safe on systems that don’t support ioctl TIOCGPTN or +TIOCPTYGNAME, for instance OpenBSD and Solaris. **Returns:** `uv_tty_t userdata` or `fail` @@ -2328,30 +2348,29 @@ it's required that it represents a valid datagram socket. - `reuseport`: `boolean` or `nil` Bind the UDP handle to an IP address and port. Any `flags` are set with a table -with fields `reuseaddr`, `ipv6only`, `linux_recverr`, `reuseport` equal to `true` or `false`. +with fields `reuseaddr`, `ipv6only`, `linux_recverr`, `reuseport` equal to +`true` or `false`. - `reuseaddr`: Indicates if SO_REUSEADDR will be set when binding the handle. - This sets the SO_REUSEPORT socket flag on the BSDs (except for - DragonFlyBSD), OS X, and other platforms where SO_REUSEPORTs don't - have the capability of load balancing, as the opposite of what - `reuseport` would do. On other Unix platforms, it sets the - SO_REUSEADDR flag. What that means is that multiple threads or - processes can bind to the same address without error (provided - they all set the flag) but only the last one to bind will receive - any traffic, in effect "stealing" the port from the previous listener. + This sets the SO_REUSEPORT socket flag on the BSDs (except for DragonFlyBSD), + OS X, and other platforms where SO_REUSEPORTs don't have the capability of + load balancing, as the opposite of what `reuseport` would do. On other Unix + platforms, it sets the SO_REUSEADDR flag. What that means is that multiple + threads or processes can bind to the same address without error (provided they + all set the flag) but only the last one to bind will receive any traffic, in + effect "stealing" the port from the previous listener. - `ipv6only`: Disables dual stack mode. -- `linux_recverr`: Indicates if IP_RECVERR/IPV6_RECVERR will be set when binding the handle. - This sets IP_RECVERR for IPv4 and IPV6_RECVERR for IPv6 UDP sockets on - Linux. This stops the Linux kernel from suppressing some ICMP error messages - and enables full ICMP error reporting for faster failover. - This flag is no-op on platforms other than Linux. +- `linux_recverr`: Indicates if IP_RECVERR/IPV6_RECVERR will be set when binding + the handle. This sets IP_RECVERR for IPv4 and IPV6_RECVERR for IPv6 UDP + sockets on Linux. This stops the Linux kernel from suppressing some ICMP error + messages and enables full ICMP error reporting for faster failover. This flag + is no-op on platforms other than Linux. - `reuseport`: Indicates if SO_REUSEPORT will be set when binding the handle. - This sets the SO_REUSEPORT socket option on supported platforms. - Unlike `reuseaddr`, this flag will make multiple threads or - processes that are binding to the same address and port "share" - the port, which means incoming datagrams are distributed across - the receiving sockets among threads or processes. - This flag is available only on Linux 3.9+, DragonFlyBSD 3.6+, + This sets the SO_REUSEPORT socket option on supported platforms. Unlike + `reuseaddr`, this flag will make multiple threads or processes that are + binding to the same address and port "share" the port, which means incoming + datagrams are distributed across the receiving sockets among threads or + processes. This flag is available only on Linux 3.9+, DragonFlyBSD 3.6+, FreeBSD 12.0+, Solaris 11.4, and AIX 7.2.5+ for now. **Returns:** `0` or `fail` @@ -2403,9 +2422,11 @@ the string `"leave"` or `"join"`. **Returns:** `0` or `fail` -### `uv.udp_set_source_membership(udp, multicast_addr, [interface_addr], source_addr, membership)` +### `uv.udp_set_source_membership(udp, multicast_addr, [interface_addr], +source_addr, membership)` -> method form `udp:set_source_membership(multicast_addr, interface_addr, source_addr, membership)` +> method form `udp:set_source_membership(multicast_addr, interface_addr, +> source_addr, membership)` **Parameters:** - `udp`: `uv_udp_t userdata` @@ -2414,9 +2435,10 @@ the string `"leave"` or `"join"`. - `source_addr`: `string` - `membership`: `string` -Set membership for a source-specific multicast group. `multicast_addr` is multicast -address to set membership for. `interface_addr` is interface address. `source_addr` -is source address. `membership` can be the string `"leave"` or `"join"`. +Set membership for a source-specific multicast group. `multicast_addr` is +multicast address to set membership for. `interface_addr` is interface address. +`source_addr` is source address. `membership` can be the string `"leave"` or +`"join"`. **Returns:** `0` or `fail` @@ -2744,10 +2766,14 @@ Get the path being monitored by the handle. [File system operations]: #file-system-operations -Most file system functions can operate synchronously or asynchronously. When a synchronous version is called (by omitting a callback), the function will +Most file system functions can operate synchronously or asynchronously. When +a synchronous version is called (by omitting a callback), the function will immediately return the results of the FS call. When an asynchronous version is -called (by providing a callback), the function will immediately return a -`uv_fs_t userdata` and asynchronously execute its callback; if an error is encountered, the first and only argument passed to the callback will be the `err` error string; if the operation completes successfully, the first argument will be `nil` and the remaining arguments will be the results of the FS call. +called (by providing a callback), the function will immediately return +a `uv_fs_t userdata` and asynchronously execute its callback; if an error is +encountered, the first and only argument passed to the callback will be the +`err` error string; if the operation completes successfully, the first argument +will be `nil` and the remaining arguments will be the results of the FS call. Synchronous and asynchronous versions of `readFile` (with naive error handling) are implemented below as an example: @@ -2835,9 +2861,11 @@ not supported. Equivalent to `preadv(2)`. Returns any data. An empty string indicates EOF. -If `offset` is nil or omitted, it will default to `-1`, which indicates 'use and update the current file offset.' +If `offset` is nil or omitted, it will default to `-1`, which indicates 'use and +update the current file offset.' -**Note:** When `offset` is >= 0, the current file offset will not be updated by the read. +**Note:** When `offset` is >= 0, the current file offset will not be updated by +the read. **Returns (sync version):** `string` or `fail` @@ -2869,9 +2897,11 @@ Equivalent to `unlink(2)`. Equivalent to `pwritev(2)`. Returns the number of bytes written. -If `offset` is nil or omitted, it will default to `-1`, which indicates 'use and update the current file offset.' +If `offset` is nil or omitted, it will default to `-1`, which indicates 'use and +update the current file offset.' -**Note:** When `offset` is >= 0, the current file offset will not be updated by the write. +**Note:** When `offset` is >= 0, the current file offset will not be updated by +the write. **Returns (sync version):** `integer` or `fail` @@ -3237,7 +3267,8 @@ Limited equivalent to `sendfile(2)`. Returns the number of bytes written. **Parameters:** - `path`: `string` -- `mode`: `string` (a combination of the `'r'`, `'w'` and `'x'` characters denoting the symbolic mode as per `chmod(1)`) +- `mode`: `string` (a combination of the `'r'`, `'w'` and `'x'` characters + denoting the symbolic mode as per `chmod(1)`) - `callback`: `callable` or `nil` (async if provided, sync if `nil`) - `err`: `nil` or `string` - `permission`: `boolean` or `nil` @@ -3294,10 +3325,12 @@ Equivalent to `utime(2)`. See [Constants][] for supported FS Modification Time constants. -Passing `"now"` or `uv.constants.FS_UTIME_NOW` as the atime or mtime sets the timestamp to the +Passing `"now"` or `uv.constants.FS_UTIME_NOW` as the atime or mtime sets the +timestamp to the current time. -Passing `nil`, `"omit"`, or `uv.constants.FS_UTIME_OMIT` as the atime or mtime leaves the timestamp +Passing `nil`, `"omit"`, or `uv.constants.FS_UTIME_OMIT` as the atime or mtime +leaves the timestamp untouched. **Returns (sync version):** `boolean` or `fail` @@ -3318,10 +3351,12 @@ Equivalent to `futimes(3)`. See [Constants][] for supported FS Modification Time constants. -Passing `"now"` or `uv.constants.FS_UTIME_NOW` as the atime or mtime sets the timestamp to the +Passing `"now"` or `uv.constants.FS_UTIME_NOW` as the atime or mtime sets the +timestamp to the current time. -Passing `nil`, `"omit"`, or `uv.constants.FS_UTIME_OMIT` as the atime or mtime leaves the timestamp +Passing `nil`, `"omit"`, or `uv.constants.FS_UTIME_OMIT` as the atime or mtime +leaves the timestamp untouched. **Returns (sync version):** `boolean` or `fail` @@ -3342,11 +3377,11 @@ Equivalent to `lutimes(3)`. See [Constants][] for supported FS Modification Time constants. -Passing `"now"` or `uv.constants.FS_UTIME_NOW` as the atime or mtime sets the timestamp to the -current time. +Passing `"now"` or `uv.constants.FS_UTIME_NOW` as the atime or mtime sets the +timestamp to the current time. -Passing `nil`, `"omit"`, or `uv.constants.FS_UTIME_OMIT` as the atime or mtime leaves the timestamp -untouched. +Passing `nil`, `"omit"`, or `uv.constants.FS_UTIME_OMIT` as the atime or mtime +leaves the timestamp untouched. **Returns (sync version):** `boolean` or `fail` @@ -3379,7 +3414,8 @@ Equivalent to `link(2)`. - `err`: `nil` or `string` - `success`: `boolean` or `nil` -Equivalent to `symlink(2)`. If the `flags` parameter is omitted, then the 3rd parameter will be treated as the `callback`. +Equivalent to `symlink(2)`. If the `flags` parameter is omitted, then the 3rd +parameter will be treated as the `callback`. **Returns (sync version):** `boolean` or `fail` @@ -3474,7 +3510,8 @@ Equivalent to `lchown(2)`. - `err`: `nil` or `string` - `success`: `boolean` or `nil` -Copies a file from path to new_path. If the `flags` parameter is omitted, then the 3rd parameter will be treated as the `callback`. +Copies a file from path to new_path. If the `flags` parameter is omitted, then +the 3rd parameter will be treated as the `callback`. **Returns (sync version):** `boolean` or `fail` @@ -3601,8 +3638,8 @@ work:queue(1, 2) - `...`: `threadargs` returned from `work_callback` Creates and initializes a new `luv_work_ctx_t` (not `uv_work_t`). -`work_callback` is a Lua function or a string containing Lua code or bytecode dumped from a function. -Returns the Lua userdata wrapping it. +`work_callback` is a Lua function or a string containing Lua code or bytecode +dumped from a function. Returns the Lua userdata wrapping it. **Returns:** `luv_work_ctx_t userdata` @@ -3712,9 +3749,10 @@ Libuv provides cross-platform implementations for multiple threading and Creates and initializes a `luv_thread_t` (not `uv_thread_t`). Returns the Lua userdata wrapping it and asynchronously executes `entry`, which can be either -a Lua function or a string containing Lua code or bytecode dumped from a function. Additional arguments `...` -are passed to the `entry` function and an optional `options` table may be -provided. Currently accepted `option` fields are `stack_size`. +a Lua function or a string containing Lua code or bytecode dumped from +a function. Additional arguments `...` are passed to the `entry` function and an +optional `options` table may be provided. Currently accepted `option` fields are +`stack_size`. **Returns:** `luv_thread_t userdata` or `fail` @@ -3905,7 +3943,8 @@ The initial value must be a non-negative integer. **Returns:** `luv_sem_t userdata` or `fail` -**Note**: A semaphore must be shared between threads, any `uv.sem_wait()` on a single thread that blocks will deadlock. +**Note**: A semaphore must be shared between threads, any `uv.sem_wait()` on +a single thread that blocks will deadlock. ### `uv.sem_post(sem)` @@ -3941,10 +3980,11 @@ the call is interrupted by a signal. **Parameters:** - `sem`: `luv_sem_t userdata` -The same as `uv.sem_wait()` but returns immediately if the semaphore is not available. +The same as `uv.sem_wait()` but returns immediately if the semaphore is not +available. -If the semaphore's value was decremented then `true` is returned, otherwise the semaphore -has a value of zero and `false` is returned. +If the semaphore's value was decremented then `true` is returned, otherwise the +semaphore has a value of zero and `false` is returned. **Returns:** `boolean` @@ -4082,13 +4122,17 @@ with zeroes). ### `uv.available_parallelism()` -Returns an estimate of the default amount of parallelism a program should use. Always returns a non-zero value. +Returns an estimate of the default amount of parallelism a program should use. +Always returns a non-zero value. -On Linux, inspects the calling thread’s CPU affinity mask to determine if it has been pinned to specific CPUs. +On Linux, inspects the calling thread’s CPU affinity mask to determine if it has +been pinned to specific CPUs. -On Windows, the available parallelism may be underreported on systems with more than 64 logical CPUs. +On Windows, the available parallelism may be underreported on systems with more +than 64 logical CPUs. -On other platforms, reports the number of CPUs that the operating system considers to be online. +On other platforms, reports the number of CPUs that the operating system +considers to be online. **Returns:** `integer` @@ -4146,6 +4190,15 @@ Sets the user ID of the process with the integer `id`. **Note**: This is not a libuv function and is not supported on Windows. +**Note**: When dropping privileges from root, calling `setuid()` alone is not +sufficient — supplementary group IDs are not affected by `setuid()` or +`setgid()` and must be dropped separately. Failure to do so is a security +vulnerability (CERT POS36-C). `uv.setuid()` rejects root-to-non-root transitions +until both primary group privileges and supplementary groups have already been +dropped. The correct order is: `uv.setgroups({})` (or `uv.initgroups(user, +gid)`), then `uv.setgid(gid)`, then `uv.setuid(uid)` (must be last, as it is +irreversible). + ### `uv.setgid(id)` **Parameters:** @@ -4157,6 +4210,44 @@ Sets the group ID of the process with the integer `id`. **Note**: This is not a libuv function and is not supported on Windows. +**Note**: When dropping privileges, supplementary groups must be dropped before +calling `setgid()` and `setuid()`. See the security warning in `uv.setuid()` for +the correct privilege-dropping sequence. + +### `uv.getgroups()` + +Returns a table containing the list of supplementary group IDs for the process. + +**Returns:** `table` + +**Note**: This is not a libuv function and is not supported on Windows. + +### `uv.setgroups(groups)` + +**Parameters:** +- `groups`: `table` + +Sets the supplementary group IDs for the process. Pass an empty table `{}` to +drop all supplementary groups. Requires appropriate privileges (typically root). + +**Returns:** Nothing. + +**Note**: This is not a libuv function and is not supported on Windows. + +### `uv.initgroups(user, group)` + +**Parameters:** +- `user`: `string` +- `group`: `integer` + +Initializes the supplementary group access list. Sets the supplementary group +IDs based on the group database (e.g., `/etc/group`) for the given `user`, plus +the specified base `group` ID. Requires appropriate privileges (typically root). + +**Returns:** Nothing. + +**Note**: This is not a libuv function and is not supported on Windows. + ### `uv.hrtime()` Returns a current high-resolution time in nanoseconds as a number. This is @@ -4429,7 +4520,11 @@ low on entropy. **Parameters:** - `errcode`: `integer` -Returns the libuv error message and error name (both in string form, see [`err` and `name` in Error Handling](#error-handling)) equivalent to the given platform dependent error code: POSIX error codes on Unix (the ones stored in errno), and Win32 error codes on Windows (those returned by GetLastError() or WSAGetLastError()). +Returns the libuv error message and error name (both in string form, see [`err` +and `name` in Error Handling](#error-handling)) equivalent to the given platform +dependent error code: POSIX error codes on Unix (the ones stored in errno), and +Win32 error codes on Windows (those returned by GetLastError() or +WSAGetLastError()). **Returns:** `string, string` or `nil` @@ -4464,20 +4559,25 @@ metrics counters. ## String manipulation functions -These string utilities are needed internally for dealing with Windows, and are exported to allow clients to work uniformly with this data when the libuv API is not complete. +These string utilities are needed internally for dealing with Windows, and are +exported to allow clients to work uniformly with this data when the libuv API is +not complete. **Notes**: 1. New in luv version 1.49.0. -2. See [the WTF-8 spec](https://simonsapin.github.io/wtf-8/) for information about WTF-8. -3. Luv uses Lua-style strings, which means that all inputs and return values (UTF-8 or UTF-16 strings) do not include a NUL terminator. +2. See [the WTF-8 spec](https://simonsapin.github.io/wtf-8/) for information + about WTF-8. +3. Luv uses Lua-style strings, which means that all inputs and return values + (UTF-8 or UTF-16 strings) do not include a NUL terminator. ### `uv.utf16_length_as_wtf8(utf16)` **Parameters:** - `utf16`: `string` -Get the length (in bytes) of a UTF-16 (or UCS-2) string `utf16` value after converting it to WTF-8. +Get the length (in bytes) of a UTF-16 (or UCS-2) string `utf16` value after +converting it to WTF-8. **Returns:** `integer` @@ -4486,7 +4586,9 @@ Get the length (in bytes) of a UTF-16 (or UCS-2) string `utf16` value after conv **Parameters:** - `utf16`: `string` -Convert UTF-16 (or UCS-2) string `utf16` to WTF-8 string. The endianness of the UTF-16 (or UCS-2) string is assumed to be the same as the native endianness of the platform. +Convert UTF-16 (or UCS-2) string `utf16` to WTF-8 string. The endianness of the +UTF-16 (or UCS-2) string is assumed to be the same as the native endianness of +the platform. **Returns:** `string` @@ -4495,7 +4597,9 @@ Convert UTF-16 (or UCS-2) string `utf16` to WTF-8 string. The endianness of the **Parameters:** - `wtf8`: `string` -Get the length (in UTF-16 code units) of a WTF-8 `wtf8` value after converting it to UTF-16 (or UCS-2). Note: The number of bytes needed for a UTF-16 (or UCS-2) string is ` * 2`. +Get the length (in UTF-16 code units) of a WTF-8 `wtf8` value after converting +it to UTF-16 (or UCS-2). Note: The number of bytes needed for a UTF-16 (or +UCS-2) string is ` * 2`. **Returns:** `integer` @@ -4504,7 +4608,9 @@ Get the length (in UTF-16 code units) of a WTF-8 `wtf8` value after converting i **Parameters:** - `wtf8`: `string` -Convert WTF-8 string in `wtf8` to UTF-16 (or UCS-2) string. The endianness of the UTF-16 (or UCS-2) string will be the same as the native endianness of the platform. +Convert WTF-8 string in `wtf8` to UTF-16 (or UCS-2) string. The endianness of +the UTF-16 (or UCS-2) string will be the same as the native endianness of the +platform. **Returns:** `string` diff --git a/docs/meta.lua b/docs/meta.lua index ec6bf3a8..8c079222 100644 --- a/docs/meta.lua +++ b/docs/meta.lua @@ -633,7 +633,9 @@ function uv_handle_t:has_ref() end --- Gets or sets the size of the send buffer that the operating system uses for the --- socket. --- ---- If `size` is omitted (or `0`), this will return the current send buffer size; otherwise, this will use `size` to set the new send buffer size. +--- If `size` is omitted (or `0`), this will return the current send +--- buffer size; otherwise, this will use `size` to set the new send +--- buffer size. --- --- This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP --- handles on Windows. @@ -650,7 +652,9 @@ function uv.send_buffer_size(handle, size) end --- Gets or sets the size of the send buffer that the operating system uses for the --- socket. --- ---- If `size` is omitted (or `0`), this will return the current send buffer size; otherwise, this will use `size` to set the new send buffer size. +--- If `size` is omitted (or `0`), this will return the current send +--- buffer size; otherwise, this will use `size` to set the new send +--- buffer size. --- --- This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP --- handles on Windows. @@ -666,7 +670,9 @@ function uv_handle_t:send_buffer_size(size) end --- Gets or sets the size of the receive buffer that the operating system uses for --- the socket. --- ---- If `size` is omitted (or `0`), this will return the current send buffer size; otherwise, this will use `size` to set the new send buffer size. +--- If `size` is omitted (or `0`), this will return the current send +--- buffer size; otherwise, this will use `size` to set the new send +--- buffer size. --- --- This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP --- handles on Windows. @@ -683,7 +689,9 @@ function uv.recv_buffer_size(handle, size) end --- Gets or sets the size of the receive buffer that the operating system uses for --- the socket. --- ---- If `size` is omitted (or `0`), this will return the current send buffer size; otherwise, this will use `size` to set the new send buffer size. +--- If `size` is omitted (or `0`), this will return the current send +--- buffer size; otherwise, this will use `size` to set the new send +--- buffer size. --- --- This function works for TCP, pipe and UDP handles on Unix and for TCP and UDP --- handles on Windows. @@ -1299,7 +1307,8 @@ function uv.signal_start(signal, signame, callback) end --- @return uv.error_name? err_name function uv_signal_t:start(signame, callback) end ---- Same functionality as `uv.signal_start()` but the signal handler is reset the moment the signal is received. +--- Same functionality as `uv.signal_start()` but the signal handler is +--- reset the moment the signal is received. --- --- See [Constants][] for supported `signame` input and output values. --- @param signal uv.uv_signal_t @@ -1310,7 +1319,8 @@ function uv_signal_t:start(signame, callback) end --- @return uv.error_name? err_name function uv.signal_start_oneshot(signal, signame, callback) end ---- Same functionality as `uv.signal_start()` but the signal handler is reset the moment the signal is received. +--- Same functionality as `uv.signal_start()` but the signal handler is +--- reset the moment the signal is received. --- --- See [Constants][] for supported `signame` input and output values. --- @param signame string|integer @@ -1744,7 +1754,8 @@ function uv.try_write(stream, data) end --- @return uv.error_name? err_name function uv_stream_t:try_write(data) end ---- Like `uv.write2()`, but with the properties of `uv.try_write()`. Not supported on Windows, where it returns `UV_EAGAIN`. +--- Like `uv.write2()`, but with the properties of `uv.try_write()`. Not +--- supported on Windows, where it returns `UV_EAGAIN`. --- --- Will return number of bytes written (can be less than the supplied buffer size). --- @param stream uv.uv_stream_t @@ -1755,7 +1766,8 @@ function uv_stream_t:try_write(data) end --- @return uv.error_name? err_name function uv.try_write2(stream, data, send_handle) end ---- Like `uv.write2()`, but with the properties of `uv.try_write()`. Not supported on Windows, where it returns `UV_EAGAIN`. +--- Like `uv.write2()`, but with the properties of `uv.try_write()`. Not +--- supported on Windows, where it returns `UV_EAGAIN`. --- --- Will return number of bytes written (can be less than the supplied buffer size). --- @param data uv.buffer @@ -1850,7 +1862,8 @@ function uv.new_tcp(flags) end --- Open an existing file descriptor or SOCKET as a TCP handle. --- **Note**: ---- The passed file descriptor or SOCKET is not checked for its type, but it's required that it represents a valid stream socket. +--- The passed file descriptor or SOCKET is not checked for its type, +--- but it's required that it represents a valid stream socket. --- @param tcp uv.uv_tcp_t --- @param sock integer --- @return 0? success @@ -1860,7 +1873,8 @@ function uv.tcp_open(tcp, sock) end --- Open an existing file descriptor or SOCKET as a TCP handle. --- **Note**: ---- The passed file descriptor or SOCKET is not checked for its type, but it's required that it represents a valid stream socket. +--- The passed file descriptor or SOCKET is not checked for its type, +--- but it's required that it represents a valid stream socket. --- @param sock integer --- @return 0? success --- @return string? err @@ -1882,7 +1896,10 @@ function uv.tcp_nodelay(tcp, enable) end --- @return uv.error_name? err_name function uv_tcp_t:nodelay(enable) end ---- Enable / disable TCP keep-alive. `delay` is the initial delay in seconds, `intvl` is the time in seconds between individual keep-alive probes, and `cnt` is the number of probes to send before assuming the connection is dead. +--- Enable / disable TCP keep-alive. `delay` is the initial delay in +--- seconds, `intvl` is the time in seconds between individual +--- keep-alive probes, and `cnt` is the number of probes to send before +--- assuming the connection is dead. --- ignored when enable is `false`. --- **Note**: --- `intvl` and `cnt` are only supported with Libuv >= 1.52.0. @@ -1896,7 +1913,10 @@ function uv_tcp_t:nodelay(enable) end --- @return uv.error_name? err_name function uv.tcp_keepalive(tcp, enable, delay, intvl, cnt) end ---- Enable / disable TCP keep-alive. `delay` is the initial delay in seconds, `intvl` is the time in seconds between individual keep-alive probes, and `cnt` is the number of probes to send before assuming the connection is dead. +--- Enable / disable TCP keep-alive. `delay` is the initial delay in +--- seconds, `intvl` is the time in seconds between individual +--- keep-alive probes, and `cnt` is the number of probes to send before +--- assuming the connection is dead. --- ignored when enable is `false`. --- **Note**: --- `intvl` and `cnt` are only supported with Libuv >= 1.52.0. @@ -2070,14 +2090,21 @@ function uv.tcp_close_reset(tcp, callback) end --- @return uv.error_name? err_name function uv_tcp_t:close_reset(callback) end ---- Create a pair of connected sockets with the specified properties. The resulting handles can be passed to `uv.tcp_open`, used with `uv.spawn`, or for any other purpose. +--- Create a pair of connected sockets with the specified properties. +--- The resulting handles can be passed to `uv.tcp_open`, used with +--- `uv.spawn`, or for any other purpose. --- --- See [Constants][] for supported `socktype` input values. --- ---- When `protocol` is set to 0 or nil, it will be automatically chosen based on the socket's domain and type. When `protocol` is specified as a string, it will be looked up using the `getprotobyname(3)` function (examples: `"ip"`, `"icmp"`, `"tcp"`, `"udp"`, etc). +--- When `protocol` is set to 0 or nil, it will be automatically chosen +--- based on the socket's domain and type. When `protocol` is specified +--- as a string, it will be looked up using the `getprotobyname(3)` +--- function (examples: `"ip"`, `"icmp"`, `"tcp"`, `"udp"`, etc). --- --- Flags: ---- - `nonblock`: Opens the specified socket handle for `OVERLAPPED` or `FIONBIO`/`O_NONBLOCK` I/O usage. This is recommended for handles that will be used by libuv, and not usually recommended otherwise. +--- - `nonblock`: Opens the specified socket handle for `OVERLAPPED` or +--- `FIONBIO`/`O_NONBLOCK` I/O usage. This is recommended for handles +--- that will be used by libuv, and not usually recommended otherwise. --- --- Equivalent to `socketpair(2)` with a domain of `AF_UNIX`. --- Example @@ -2111,7 +2138,8 @@ function uv.socketpair(socktype, protocol, flags1, flags2) end --- --- > [`uv_handle_t`][] and [`uv_stream_t`][] functions also apply. --- ---- Pipe handles provide an abstraction over local domain sockets on Unix and named pipes on Windows. +--- Pipe handles provide an abstraction over local domain sockets on +--- Unix and named pipes on Windows. --- --- ```lua --- local pipe = uv.new_pipe(false) @@ -2296,10 +2324,14 @@ function uv_pipe_t:chmod(flags) end --- (file descriptor) --- @field write integer ---- Create a pair of connected pipe handles. Data may be written to the `write` fd and read from the `read` fd. The resulting handles can be passed to `pipe_open`, used with `spawn`, or for any other purpose. +--- Create a pair of connected pipe handles. Data may be written to the +--- `write` fd and read from the `read` fd. The resulting handles can be +--- passed to `pipe_open`, used with `spawn`, or for any other purpose. --- --- Flags: ---- - `nonblock`: Opens the specified socket handle for `OVERLAPPED` or `FIONBIO`/`O_NONBLOCK` I/O usage. This is recommended for handles that will be used by libuv, and not usually recommended otherwise. +--- - `nonblock`: Opens the specified socket handle for `OVERLAPPED` or +--- `FIONBIO`/`O_NONBLOCK` I/O usage. This is recommended for handles +--- that will be used by libuv, and not usually recommended otherwise. --- --- Equivalent to `pipe(2)` with the `O_CLOEXEC` flag set. --- Example @@ -2335,7 +2367,8 @@ function uv.pipe(read_flags, write_flags) end --- - If `type(flags)` is `nil`, it use default value `0`. --- - Returns `EINVAL` for unsupported flags without performing the bind operation. --- ---- Supports Linux abstract namespace sockets. namelen must include the leading '\0' byte but not the trailing nul byte. +--- Supports Linux abstract namespace sockets. namelen must include the +--- leading '\0' byte but not the trailing nul byte. --- **Note**: --- 1. Paths on Unix get truncated to sizeof(sockaddr_un.sun_path) bytes, --- typically between 92 and 108 bytes. @@ -2357,7 +2390,8 @@ function uv.pipe_bind2(pipe, name, flags) end --- - If `type(flags)` is `nil`, it use default value `0`. --- - Returns `EINVAL` for unsupported flags without performing the bind operation. --- ---- Supports Linux abstract namespace sockets. namelen must include the leading '\0' byte but not the trailing nul byte. +--- Supports Linux abstract namespace sockets. namelen must include the +--- leading '\0' byte but not the trailing nul byte. --- **Note**: --- 1. Paths on Unix get truncated to sizeof(sockaddr_un.sun_path) bytes, --- typically between 92 and 108 bytes. @@ -2378,7 +2412,8 @@ function uv_pipe_t:bind2(name, flags) end --- - If `type(flags)` is `nil`, it use default value `0`. --- - Returns `EINVAL` for unsupported flags without performing the bind operation. --- ---- Supports Linux abstract namespace sockets. namelen must include the leading nul byte but not the trailing nul byte. +--- Supports Linux abstract namespace sockets. namelen must include the +--- leading nul byte but not the trailing nul byte. --- **Note**: --- 1. Paths on Unix get truncated to sizeof(sockaddr_un.sun_path) bytes, --- typically between 92 and 108 bytes. @@ -2401,7 +2436,8 @@ function uv.pipe_connect2(pipe, name, flags, callback) end --- - If `type(flags)` is `nil`, it use default value `0`. --- - Returns `EINVAL` for unsupported flags without performing the bind operation. --- ---- Supports Linux abstract namespace sockets. namelen must include the leading nul byte but not the trailing nul byte. +--- Supports Linux abstract namespace sockets. namelen must include the +--- leading nul byte but not the trailing nul byte. --- **Note**: --- 1. Paths on Unix get truncated to sizeof(sockaddr_un.sun_path) bytes, --- typically between 92 and 108 bytes. @@ -2451,7 +2487,8 @@ local uv_tty_t = {} --- This lets libuv put the tty in non-blocking mode without affecting other --- processes that share the tty. --- ---- This function is not thread safe on systems that don’t support ioctl TIOCGPTN or TIOCPTYGNAME, for instance OpenBSD and Solaris. +--- This function is not thread safe on systems that don’t support ioctl +--- TIOCGPTN or TIOCPTYGNAME, for instance OpenBSD and Solaris. --- **Note**: --- If reopening the TTY fails, libuv falls back to blocking writes. --- @param fd integer @@ -3190,10 +3227,16 @@ function uv_fs_poll_t:getpath() end --- # File system operations --- ---- Most file system functions can operate synchronously or asynchronously. When a synchronous version is called (by omitting a callback), the function will ---- immediately return the results of the FS call. When an asynchronous version is ---- called (by providing a callback), the function will immediately return a ---- `uv_fs_t userdata` and asynchronously execute its callback; if an error is encountered, the first and only argument passed to the callback will be the `err` error string; if the operation completes successfully, the first argument will be `nil` and the remaining arguments will be the results of the FS call. +--- Most file system functions can operate synchronously or +--- asynchronously. When a synchronous version is called (by omitting +--- a callback), the function will immediately return the results of the +--- FS call. When an asynchronous version is called (by providing +--- a callback), the function will immediately return a `uv_fs_t +--- userdata` and asynchronously execute its callback; if an error is +--- encountered, the first and only argument passed to the callback will +--- be the `err` error string; if the operation completes successfully, +--- the first argument will be `nil` and the remaining arguments will be +--- the results of the FS call. --- --- Synchronous and asynchronous versions of `readFile` (with naive error handling) --- are implemented below as an example: @@ -4213,13 +4256,17 @@ function uv.getrusage() end --- @return uv.error_name? err_name function uv.getrusage_thread() end ---- Returns an estimate of the default amount of parallelism a program should use. Always returns a non-zero value. +--- Returns an estimate of the default amount of parallelism a program +--- should use. Always returns a non-zero value. --- ---- On Linux, inspects the calling thread’s CPU affinity mask to determine if it has been pinned to specific CPUs. +--- On Linux, inspects the calling thread’s CPU affinity mask to +--- determine if it has been pinned to specific CPUs. --- ---- On Windows, the available parallelism may be underreported on systems with more than 64 logical CPUs. +--- On Windows, the available parallelism may be underreported on +--- systems with more than 64 logical CPUs. --- ---- On other platforms, reports the number of CPUs that the operating system considers to be online. +--- On other platforms, reports the number of CPUs that the operating +--- system considers to be online. --- @return integer function uv.available_parallelism() end @@ -4235,7 +4282,8 @@ function uv.available_parallelism() end --- @field idle integer --- @field irq integer ---- Returns information about the CPU(s) on the system as a table of tables for each +--- Returns information about the CPU(s) on the system as a table of +--- tables for each --- CPU found. --- @return table? cpu_info --- @return string? err @@ -4268,15 +4316,53 @@ function uv.getgid() end --- Sets the user ID of the process with the integer `id`. --- **Note**: --- This is not a libuv function and is not supported on Windows. +--- **Note**: +--- When dropping privileges from root, calling `setuid()` alone is not +--- sufficient — supplementary group IDs are not affected by `setuid()` +--- or `setgid()` and must be dropped separately. Failure to do so is +--- a security vulnerability (CERT POS36-C). `uv.setuid()` rejects +--- root-to-non-root transitions until both primary group privileges and +--- supplementary groups have already been dropped. The correct order +--- is: `uv.setgroups({})` (or `uv.initgroups(user, gid)`), then +--- `uv.setgid(gid)`, then `uv.setuid(uid)` (must be last, as it is +--- irreversible). --- @param id integer function uv.setuid(id) end --- Sets the group ID of the process with the integer `id`. --- **Note**: --- This is not a libuv function and is not supported on Windows. +--- **Note**: +--- When dropping privileges, supplementary groups must be dropped +--- before calling `setgid()` and `setuid()`. See the security warning +--- in `uv.setuid()` for the correct privilege-dropping sequence. --- @param id integer function uv.setgid(id) end +--- Returns a table containing the list of supplementary group IDs for the process. +--- **Note**: +--- This is not a libuv function and is not supported on Windows. +--- @return table +function uv.getgroups() end + +--- Sets the supplementary group IDs for the process. Pass an empty +--- table `{}` to drop all supplementary groups. Requires appropriate +--- privileges (typically root). +--- **Note**: +--- This is not a libuv function and is not supported on Windows. +--- @param groups table +function uv.setgroups(groups) end + +--- Initializes the supplementary group access list. Sets the +--- supplementary group IDs based on the group database (e.g., +--- `/etc/group`) for the given `user`, plus the specified base `group` +--- ID. Requires appropriate privileges (typically root). +--- **Note**: +--- This is not a libuv function and is not supported on Windows. +--- @param user string +--- @param group integer +function uv.initgroups(user, group) end + --- Returns a current high-resolution time in nanoseconds as a number. This is --- relative to an arbitrary time in the past. It is not related to the time of day --- and therefore not subject to clock drift. The primary use is for measuring @@ -4507,7 +4593,11 @@ function uv.os_setpriority(pid, priority) end --- @overload fun(len: integer, flags: 0|{}?, callback: fun(err: string?, bytes: string?)): 0?, string?, uv.error_name? function uv.random(len, flags) end ---- Returns the libuv error message and error name (both in string form, see [`err` and `name` in Error Handling](#error-handling)) equivalent to the given platform dependent error code: POSIX error codes on Unix (the ones stored in errno), and Win32 error codes on Windows (those returned by GetLastError() or WSAGetLastError()). +--- Returns the libuv error message and error name (both in string form, +--- see [`err` and `name` in Error Handling](#error-handling)) +--- equivalent to the given platform dependent error code: POSIX error +--- codes on Unix (the ones stored in errno), and Win32 error codes on +--- Windows (those returned by GetLastError() or WSAGetLastError()). --- @param errcode integer --- @return string? message --- @return string? name @@ -4542,30 +4632,40 @@ function uv.metrics_info() end --- # String manipulation functions --- ---- These string utilities are needed internally for dealing with Windows, and are exported to allow clients to work uniformly with this data when the libuv API is not complete. +--- These string utilities are needed internally for dealing with +--- Windows, and are exported to allow clients to work uniformly with +--- this data when the libuv API is not complete. --- --- **Notes**: --- --- 1. New in luv version 1.49.0. --- 2. See [the WTF-8 spec](https://simonsapin.github.io/wtf-8/) for information about WTF-8. ---- 3. Luv uses Lua-style strings, which means that all inputs and return values (UTF-8 or UTF-16 strings) do not include a NUL terminator. +--- 3. Luv uses Lua-style strings, which means that all inputs and +--- return values (UTF-8 or UTF-16 strings) do not include a NUL +--- terminator. --- Get the length (in bytes) of a UTF-16 (or UCS-2) string `utf16` value after converting it to WTF-8. --- @param utf16 string --- @return integer function uv.utf16_length_as_wtf8(utf16) end ---- Convert UTF-16 (or UCS-2) string `utf16` to WTF-8 string. The endianness of the UTF-16 (or UCS-2) string is assumed to be the same as the native endianness of the platform. +--- Convert UTF-16 (or UCS-2) string `utf16` to WTF-8 string. The +--- endianness of the UTF-16 (or UCS-2) string is assumed to be the same +--- as the native endianness of the platform. --- @param utf16 string --- @return string function uv.utf16_to_wtf8(utf16) end ---- Get the length (in UTF-16 code units) of a WTF-8 `wtf8` value after converting it to UTF-16 (or UCS-2). Note: The number of bytes needed for a UTF-16 (or UCS-2) string is ` * 2`. +--- Get the length (in UTF-16 code units) of a WTF-8 `wtf8` value after +--- converting it to UTF-16 (or UCS-2). Note: The number of bytes needed +--- for a UTF-16 (or UCS-2) string is ` * 2`. --- @param wtf8 string --- @return integer function uv.wtf8_length_as_utf16(wtf8) end ---- Convert WTF-8 string in `wtf8` to UTF-16 (or UCS-2) string. The endianness of the UTF-16 (or UCS-2) string will be the same as the native endianness of the platform. +--- Convert WTF-8 string in `wtf8` to UTF-16 (or UCS-2) string. The +--- endianness of the UTF-16 (or UCS-2) string will be the same as the +--- native endianness of the platform. --- @param wtf8 string --- @return string function uv.wtf8_to_utf16(wtf8) end diff --git a/src/luv.c b/src/luv.c index 7ea1dcbb..b5702f1e 100644 --- a/src/luv.c +++ b/src/luv.c @@ -332,6 +332,9 @@ static const luaL_Reg luv_functions[] = { {"setuid", luv_setuid}, {"getgid", luv_getgid}, {"setgid", luv_setgid}, + {"getgroups", luv_getgroups}, + {"setgroups", luv_setgroups}, + {"initgroups", luv_initgroups}, #endif {"getrusage", luv_getrusage}, #if LUV_UV_VERSION_GEQ(1, 50, 0) diff --git a/src/misc.c b/src/misc.c index 5b648e1f..09d6d9b8 100644 --- a/src/misc.c +++ b/src/misc.c @@ -18,6 +18,8 @@ #include "luv.h" #ifdef _WIN32 #include +#else +#include #endif static int luv_guess_handle(lua_State* L) { @@ -475,7 +477,33 @@ static int luv_getgid(lua_State* L){ static int luv_setuid(lua_State* L){ int uid = luaL_checkinteger(L, 1); - int r = setuid(uid); + int r; + + /* POS36-C safety check: when dropping privileges from root to an + unprivileged user, supplementary groups must be dropped first. + See: https://wiki.sei.cmu.edu/confluence/display/c/POS36-C */ + if (geteuid() == 0 && uid != 0) { + if (getgid() == 0 || getegid() == 0) { + return luaL_error(L, + "Cannot setuid before dropping group privileges " + "(POS36-C security check). Call uv.setgid() before " + "dropping privileges with uv.setuid()."); + } + + int ngroups = getgroups(0, NULL); + if (ngroups == -1) { + return luaL_error(L, "Error getting number of groups"); + } + + if (ngroups > 0) { + return luaL_error(L, + "Cannot setuid while supplementary groups are still set " + "(POS36-C security check). Call uv.setgroups({}) or " + "uv.initgroups() before dropping privileges with uv.setuid()."); + } + } + + r = setuid(uid); if (-1 == r) { luaL_error(L, "Error setting UID"); } @@ -491,6 +519,86 @@ static int luv_setgid(lua_State* L){ return 0; } +static int luv_getgroups(lua_State* L) { + int ngroups, i; + gid_t* groups; + + /* First call to get the number of groups */ + ngroups = getgroups(0, NULL); + if (ngroups == -1) { + return luaL_error(L, "Error getting number of groups"); + } + + groups = NULL; + if (ngroups > 0) { + groups = (gid_t*)malloc(ngroups * sizeof(gid_t)); + } + if (!groups) { + return luaL_error(L, "Error allocating memory for groups"); + } + + ngroups = getgroups(ngroups, groups); + if (ngroups == -1) { + free(groups); + return luaL_error(L, "Error getting groups"); + } + + lua_createtable(L, ngroups, 0); + for (i = 0; i < ngroups; i++) { + lua_pushinteger(L, groups[i]); + lua_rawseti(L, -2, i + 1); + } + + free(groups); + return 1; +} + +static int luv_setgroups(lua_State* L) { + int ngroups, i; + gid_t* groups; + + luaL_checktype(L, 1, LUA_TTABLE); + ngroups = lua_rawlen(L, 1); + + if (ngroups < 1) { + return 0; + } + + groups = (gid_t*)malloc(ngroups * sizeof(gid_t)); + if (!groups && ngroups > 0) { + return luaL_error(L, "Error allocating memory for groups"); + } + + for (i = 0; i < ngroups; i++) { + lua_rawgeti(L, 1, i + 1); + if (!lua_isnumber(L, -1)) { + free(groups); + return luaL_argerror(L, 1, "groups table entries must be integers"); + } + groups[i] = (gid_t)lua_tointeger(L, -1); + lua_pop(L, 1); + } + + if (setgroups(ngroups, groups) == -1) { + free(groups); + return luaL_error(L, "Error setting groups"); + } + + free(groups); + return 0; +} + +static int luv_initgroups(lua_State* L) { + const char* user = luaL_checkstring(L, 1); + gid_t group = (gid_t)luaL_checkinteger(L, 2); + + if (initgroups(user, group) == -1) { + return luaL_error(L, "Error initializing groups for user '%s'", user); + } + + return 0; +} + #if LUV_UV_VERSION_GEQ(1, 8, 0) static int luv_print_all_handles(lua_State* L){ luv_ctx_t* ctx = luv_context(L); diff --git a/tests/test-misc.lua b/tests/test-misc.lua index 953f69ee..33b0741e 100644 --- a/tests/test-misc.lua +++ b/tests/test-misc.lua @@ -262,4 +262,70 @@ return require('lib/tap')(function (test) assert(uv.utf16_length_as_wtf8("") == 0) end, "1.49.0") + test("uv.getgroups", function(print, p, expect, uv) + if not uv.getgroups then + print("skipping: uv.getgroups not available (Windows?)") + return + end + local groups = uv.getgroups() + p("supplementary groups", groups) + assert(type(groups) == "table") + for i, gid in ipairs(groups) do + assert(type(gid) == "number", "group ID must be a number") + end + end) + + test("uv.setgroups", function(print, p, expect, uv) + if not uv.setgroups then + print("skipping: uv.setgroups not available (Windows?)") + return + end + -- setgroups requires root, so we just verify the function exists + -- and accepts a table argument + assert(type(uv.setgroups) == "function") + -- Only test actual setgroups if running as root + if uv.getuid and uv.getuid() == 0 then + local groups = uv.getgroups() + -- Round-trip: set the current groups back + uv.setgroups(groups) + local groups2 = uv.getgroups() + assert(#groups == #groups2, "group count mismatch after round-trip") + else + print("skipping setgroups round-trip: not running as root") + end + end) + + test("uv.initgroups", function(print, p, expect, uv) + if not uv.initgroups then + print("skipping: uv.initgroups not available (Windows?)") + return + end + -- initgroups requires root, so we just verify the function exists + assert(type(uv.initgroups) == "function") + end) + + test("uv.setuid POS36-C safety check", function(print, p, expect, uv) + if not uv.setuid or not uv.getuid then + print("skipping: uv.setuid or uv.getuid not available (Windows?)") + return + end + -- When not running as root, setuid to our own uid should succeed + -- (the POS36-C check only blocks root->non-root transitions) + local uid = uv.getuid() + if uid ~= 0 then + -- Non-root: setuid to own uid should work without issues + uv.setuid(uid) + print("non-root setuid to own uid succeeded (no POS36-C block expected)") + else + -- Root: setuid to non-root before dropping gid/groups should fail + local ok, err = pcall(uv.setuid, 65534) + if not ok then + assert(string.find(err, "POS36-C"), "expected POS36-C error, got: " .. tostring(err)) + print("POS36-C safety check correctly blocked unsafe setuid ordering") + else + error("expected POS36-C safety check to block root->non-root setuid") + end + end + end) + end) diff --git a/tests/test-process.lua b/tests/test-process.lua index 32e75de8..7263ae05 100644 --- a/tests/test-process.lua +++ b/tests/test-process.lua @@ -1,6 +1,24 @@ return require('lib/tap')(function (test) local isWindows = require('lib/utils').isWindows + local privilege_drop_child_code = string.dump(function () + local uv = require('luv') + local target_id = 1 + local groups + + assert(uv.getuid() == 0, "child must start as root") + + uv.setgroups({}) + uv.setgid(target_id) + uv.setuid(target_id) + + groups = uv.getgroups() + assert(uv.getuid() == target_id) + assert(uv.getgid() == target_id) + assert(#groups == 0) + + io.write(string.format("dropped:%d:%d:%d\n", uv.getuid(), uv.getgid(), #groups)) + end) test("test disable_stdio_inheritance", function (print, p, expect, uv) uv.disable_stdio_inheritance() @@ -146,4 +164,41 @@ return require('lib/tap')(function (test) handle:kill("sigterm") end, "1.19.0") + test("spawned child can drop privileges in POS36-C order", function (print, p, expect, uv) + local child, pid + local input, output + + if isWindows or not uv.getuid or uv.getuid() ~= 0 then + print("skipping: requires Unix root privileges") + return + end + + input = uv.new_pipe(false) + output = uv.new_pipe(false) + + child, pid = assert(uv.spawn(uv.exepath(), { + args = {"-"}, + stdio = {input, output, 2}, + }, expect(function (code, signal) + p("exit", {pid = pid, code = code, signal = signal}) + assert(code == 0) + assert(signal == 0) + uv.close(input) + uv.close(output) + uv.close(child) + end))) + + uv.read_start(output, expect(function (err, chunk) + assert(not err, err) + if chunk then + p(chunk) + assert(chunk == "dropped:1:1:0\n") + uv.read_stop(output) + end + end, 1)) + + uv.write(input, privilege_drop_child_code) + uv.shutdown(input) + end) + end)