1 | /* |
2 | * Copyright © 2012 Collabora, Ltd. |
3 | * |
4 | * Permission is hereby granted, free of charge, to any person obtaining |
5 | * a copy of this software and associated documentation files (the |
6 | * "Software"), to deal in the Software without restriction, including |
7 | * without limitation the rights to use, copy, modify, merge, publish, |
8 | * distribute, sublicense, and/or sell copies of the Software, and to |
9 | * permit persons to whom the Software is furnished to do so, subject to |
10 | * the following conditions: |
11 | * |
12 | * The above copyright notice and this permission notice (including the |
13 | * next paragraph) shall be included in all copies or substantial |
14 | * portions of the Software. |
15 | * |
16 | * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, |
17 | * EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF |
18 | * MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND |
19 | * NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS |
20 | * BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN |
21 | * ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN |
22 | * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE |
23 | * SOFTWARE. |
24 | */ |
25 | |
26 | #define _GNU_SOURCE |
27 | |
28 | #include "../config.h" |
29 | |
30 | #include <sys/types.h> |
31 | #include <sys/socket.h> |
32 | #include <unistd.h> |
33 | #include <fcntl.h> |
34 | #include <errno.h> |
35 | #include <string.h> |
36 | #include <sys/epoll.h> |
37 | #include <sys/mman.h> |
38 | #include <sys/un.h> |
39 | #ifdef HAVE_SYS_UCRED_H |
40 | #include <sys/ucred.h> |
41 | #endif |
42 | |
43 | #include "wayland-os.h" |
44 | |
45 | static int |
46 | set_cloexec_or_close(int fd) |
47 | { |
48 | long flags; |
49 | |
50 | if (fd == -1) |
51 | return -1; |
52 | |
53 | flags = fcntl(fd: fd, F_GETFD); |
54 | if (flags == -1) |
55 | goto err; |
56 | |
57 | if (fcntl(fd: fd, F_SETFD, flags | FD_CLOEXEC) == -1) |
58 | goto err; |
59 | |
60 | return fd; |
61 | |
62 | err: |
63 | close(fd: fd); |
64 | return -1; |
65 | } |
66 | |
67 | int |
68 | wl_os_socket_cloexec(int domain, int type, int protocol) |
69 | { |
70 | int fd; |
71 | |
72 | fd = socket(domain: domain, type: type | SOCK_CLOEXEC, protocol: protocol); |
73 | if (fd >= 0) |
74 | return fd; |
75 | if (errno != EINVAL) |
76 | return -1; |
77 | |
78 | fd = socket(domain: domain, type: type, protocol: protocol); |
79 | return set_cloexec_or_close(fd); |
80 | } |
81 | |
82 | #if defined(__FreeBSD__) |
83 | int |
84 | wl_os_socket_peercred(int sockfd, uid_t *uid, gid_t *gid, pid_t *pid) |
85 | { |
86 | socklen_t len; |
87 | struct xucred ucred; |
88 | |
89 | len = sizeof(ucred); |
90 | if (getsockopt(sockfd, SOL_LOCAL, LOCAL_PEERCRED, &ucred, &len) < 0 || |
91 | ucred.cr_version != XUCRED_VERSION) |
92 | return -1; |
93 | *uid = ucred.cr_uid; |
94 | *gid = ucred.cr_gid; |
95 | #if HAVE_XUCRED_CR_PID |
96 | /* Since https://cgit.freebsd.org/src/commit/?id=c5afec6e895a */ |
97 | *pid = ucred.cr_pid; |
98 | #else |
99 | *pid = 0; |
100 | #endif |
101 | return 0; |
102 | } |
103 | #elif defined(SO_PEERCRED) |
104 | int |
105 | wl_os_socket_peercred(int sockfd, uid_t *uid, gid_t *gid, pid_t *pid) |
106 | { |
107 | socklen_t len; |
108 | struct ucred ucred; |
109 | |
110 | len = sizeof(ucred); |
111 | if (getsockopt(fd: sockfd, SOL_SOCKET, SO_PEERCRED, optval: &ucred, optlen: &len) < 0) |
112 | return -1; |
113 | *uid = ucred.uid; |
114 | *gid = ucred.gid; |
115 | *pid = ucred.pid; |
116 | return 0; |
117 | } |
118 | #else |
119 | #error "Don't know how to read ucred on this platform" |
120 | #endif |
121 | |
122 | int |
123 | wl_os_dupfd_cloexec(int fd, int minfd) |
124 | { |
125 | int newfd; |
126 | |
127 | newfd = fcntl(fd: fd, F_DUPFD_CLOEXEC, minfd); |
128 | if (newfd >= 0) |
129 | return newfd; |
130 | if (errno != EINVAL) |
131 | return -1; |
132 | |
133 | newfd = fcntl(fd: fd, F_DUPFD, minfd); |
134 | return set_cloexec_or_close(newfd); |
135 | } |
136 | |
137 | static ssize_t |
138 | recvmsg_cloexec_fallback(int sockfd, struct msghdr *msg, int flags) |
139 | { |
140 | ssize_t len; |
141 | struct cmsghdr *cmsg; |
142 | unsigned char *data; |
143 | int *fd; |
144 | int *end; |
145 | |
146 | len = recvmsg(fd: sockfd, message: msg, flags: flags); |
147 | if (len == -1) |
148 | return -1; |
149 | |
150 | if (!msg->msg_control || msg->msg_controllen == 0) |
151 | return len; |
152 | |
153 | cmsg = CMSG_FIRSTHDR(msg); |
154 | for (; cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { |
155 | if (cmsg->cmsg_level != SOL_SOCKET || |
156 | cmsg->cmsg_type != SCM_RIGHTS) |
157 | continue; |
158 | |
159 | data = CMSG_DATA(cmsg); |
160 | end = (int *)(data + cmsg->cmsg_len - CMSG_LEN(0)); |
161 | for (fd = (int *)data; fd < end; ++fd) |
162 | *fd = set_cloexec_or_close(*fd); |
163 | } |
164 | |
165 | return len; |
166 | } |
167 | |
168 | ssize_t |
169 | wl_os_recvmsg_cloexec(int sockfd, struct msghdr *msg, int flags) |
170 | { |
171 | #if HAVE_BROKEN_MSG_CMSG_CLOEXEC |
172 | /* |
173 | * FreeBSD had a broken implementation of MSG_CMSG_CLOEXEC between 2015 |
174 | * and 2021, so we have to use the non-MSG_CMSG_CLOEXEC fallback |
175 | * directly when compiling against a version that does not include the |
176 | * fix (https://cgit.freebsd.org/src/commit/?id=6ceacebdf52211). |
177 | */ |
178 | #pragma message("Using fallback directly since MSG_CMSG_CLOEXEC is broken.") |
179 | #else |
180 | ssize_t len; |
181 | |
182 | len = recvmsg(fd: sockfd, message: msg, flags: flags | MSG_CMSG_CLOEXEC); |
183 | if (len >= 0) |
184 | return len; |
185 | if (errno != EINVAL) |
186 | return -1; |
187 | #endif |
188 | return recvmsg_cloexec_fallback(sockfd, msg, flags); |
189 | } |
190 | |
191 | int |
192 | wl_os_epoll_create_cloexec(void) |
193 | { |
194 | int fd; |
195 | |
196 | #ifdef EPOLL_CLOEXEC |
197 | fd = epoll_create1(EPOLL_CLOEXEC); |
198 | if (fd >= 0) |
199 | return fd; |
200 | if (errno != EINVAL) |
201 | return -1; |
202 | #endif |
203 | |
204 | fd = epoll_create(size: 1); |
205 | return set_cloexec_or_close(fd); |
206 | } |
207 | |
208 | int |
209 | wl_os_accept_cloexec(int sockfd, struct sockaddr *addr, socklen_t *addrlen) |
210 | { |
211 | int fd; |
212 | |
213 | #ifdef HAVE_ACCEPT4 |
214 | fd = accept4(fd: sockfd, addr: addr, addr_len: addrlen, SOCK_CLOEXEC); |
215 | if (fd >= 0) |
216 | return fd; |
217 | if (errno != ENOSYS) |
218 | return -1; |
219 | #endif |
220 | |
221 | fd = accept(fd: sockfd, addr: addr, addr_len: addrlen); |
222 | return set_cloexec_or_close(fd); |
223 | } |
224 | |
225 | /* |
226 | * Fallback function for operating systems that don't implement |
227 | * mremap(MREMAP_MAYMOVE). |
228 | */ |
229 | void * |
230 | wl_os_mremap_maymove(int fd, void *old_data, ssize_t *old_size, |
231 | ssize_t new_size, int prot, int flags) |
232 | { |
233 | void *result; |
234 | /* |
235 | * We could try mapping a new block immediately after the current one |
236 | * with MAP_FIXED, however that is not guaranteed to work and breaks |
237 | * on CHERI-enabled architectures since the data pointer will still |
238 | * have the bounds of the previous allocation. As this is not a |
239 | * performance-critical path, we always map a new region and copy the |
240 | * old data to the new region. |
241 | */ |
242 | result = mmap(NULL, len: new_size, prot: prot, flags: flags, fd: fd, offset: 0); |
243 | if (result != MAP_FAILED) { |
244 | /* Copy the data over and unmap the old mapping. */ |
245 | memcpy(dest: result, src: old_data, n: *old_size); |
246 | if (munmap(addr: old_data, len: *old_size) == 0) { |
247 | *old_size = 0; /* successfully unmapped old data. */ |
248 | } |
249 | } |
250 | return result; |
251 | } |
252 | |