Coverage Report

Created: 2026-08-31 06:47

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/dovecot/src/lib/fdpass.c
Line
Count
Source
1
/* Copyright (c) Dovecot authors, see top-level COPYING file */
2
3
/*
4
   fdpass.c - File descriptor passing between processes via UNIX sockets
5
6
   This isn't fully portable, but pretty much all UNIXes nowadays should
7
   support this. If you're having runtime problems with fd_read(), check the
8
   end of fd_read() and play with the if condition. If you're having problems
9
   with fd_send(), try defining BUGGY_CMSG_MACROS.
10
11
   If this file doesn't compile at all, you should check if this is supported
12
   in your system at all. It may require some extra #define to enable it.
13
   If not, you're pretty much out of luck. Cygwin didn't last I checked.
14
*/
15
16
#define _XPG4_2
17
18
#if defined(irix) || defined (__irix__) || defined(sgi) || defined (__sgi__)
19
#  define _XOPEN_SOURCE 4 /* for IRIX */
20
#endif
21
22
#if !defined(_AIX) && !defined(_XOPEN_SOURCE_EXTENDED)
23
#  define _XOPEN_SOURCE_EXTENDED /* for Tru64, breaks AIX */
24
#endif
25
26
#ifdef HAVE_CONFIG_H
27
#  include "lib.h"
28
#else
29
#  define i_assert(x)
30
#endif
31
32
#include <string.h>
33
#include <limits.h>
34
#include <sys/types.h>
35
36
#include <sys/socket.h>
37
#include <sys/un.h>
38
#include <sys/uio.h>
39
40
#include "fdpass.h"
41
42
#ifndef HAVE_CONFIG_H
43
struct const_iovec {
44
  const void *iov_base;
45
  size_t iov_len;
46
};
47
#endif
48
49
/* RFC 2292 defines CMSG_*() macros, but some operating systems don't have them
50
   so we'll define our own if they don't exist.
51
52
   CMSG_LEN(data) is used to calculate size of sizeof(struct cmsghdr) +
53
   sizeof(data) and padding between them.
54
55
   CMSG_SPACE(data) also calculates the padding needed after the data, in case
56
   multiple objects are sent.
57
58
   cmsghdr contains cmsg_len field and two integers. cmsg_len is sometimes
59
   defined as sockaddr_t and sometimes size_t, so it can be either 32bit or
60
   64bit. This padding is added by compiler in sizeof(struct cmsghdr).
61
62
   Padding required by CMSG_DATA() can vary. Usually it wants size_t or 32bit.
63
   With Solaris it's in _CMSG_DATA_ALIGNMENT (32bit), we assume others want
64
   size_t.
65
66
   We don't really need CMSG_SPACE() to be exactly correct, because currently
67
   we send only one object at a time. But anyway I'm trying to keep that
68
   correct in case it's sometimes needed..
69
*/
70
71
#ifdef BUGGY_CMSG_MACROS
72
/* Some OSes have broken CMSG macros in 64bit systems. The macros use 64bit
73
   alignment while kernel uses 32bit alignment. */
74
#  undef CMSG_SPACE
75
#  undef CMSG_LEN
76
#  undef CMSG_DATA
77
#  define CMSG_DATA(cmsg) ((char *)((cmsg) + 1))
78
#  define _CMSG_DATA_ALIGNMENT 4
79
#  define _CMSG_HDR_ALIGNMENT 4
80
#endif
81
82
#ifndef CMSG_SPACE
83
#  define MY_ALIGN(len, align) \
84
  (((len) + align - 1) & ~(align - 1))
85
86
/* Alignment between cmsghdr and data */
87
#  ifndef _CMSG_DATA_ALIGNMENT
88
#    define _CMSG_DATA_ALIGNMENT sizeof(size_t)
89
#  endif
90
/* Alignment between data and next cmsghdr */
91
#  ifndef _CMSG_HDR_ALIGNMENT
92
#    define _CMSG_HDR_ALIGNMENT sizeof(size_t)
93
#  endif
94
95
#  define CMSG_SPACE(len) \
96
  (MY_ALIGN(sizeof(struct cmsghdr), _CMSG_DATA_ALIGNMENT) + \
97
   MY_ALIGN(len, _CMSG_HDR_ALIGNMENT))
98
#  define CMSG_LEN(len) \
99
  (MY_ALIGN(sizeof(struct cmsghdr), _CMSG_DATA_ALIGNMENT) + (len))
100
#endif
101
102
#ifdef SCM_RIGHTS
103
104
ssize_t fd_send(int handle, int send_fd, const void *data, size_t size)
105
0
{
106
0
        struct msghdr msg;
107
0
        struct const_iovec iov;
108
0
        struct cmsghdr *cmsg;
109
0
  char buf[CMSG_SPACE(sizeof(int))];
110
111
  /* at least one byte is required to be sent with fd passing */
112
0
  i_assert(size > 0 && size < INT_MAX);
113
114
0
  memset(&msg, 0, sizeof(struct msghdr));
115
116
0
        iov.iov_base = data;
117
0
        iov.iov_len = size;
118
119
0
        msg.msg_iov = (void *)&iov;
120
0
  msg.msg_iovlen = 1;
121
122
0
  if (send_fd != -1) {
123
    /* set the control and controllen before CMSG_FIRSTHDR(). */
124
0
    memset(buf, 0, sizeof(buf));
125
0
    msg.msg_control = buf;
126
0
    msg.msg_controllen = sizeof(buf);
127
128
0
    cmsg = CMSG_FIRSTHDR(&msg);
129
0
    cmsg->cmsg_level = SOL_SOCKET;
130
0
    cmsg->cmsg_type = SCM_RIGHTS;
131
0
    cmsg->cmsg_len = CMSG_LEN(sizeof(int));
132
0
    memcpy(CMSG_DATA(cmsg), &send_fd, sizeof(send_fd));
133
134
    /* set the real length we want to use. Do it after all is
135
       set just in case CMSG macros required the extra padding
136
       in the end. */
137
0
    msg.msg_controllen = cmsg->cmsg_len;
138
0
  }
139
140
0
  return sendmsg(handle, &msg, 0);
141
0
}
142
143
#ifdef LINUX20
144
/* Linux 2.0.x doesn't set any cmsg fields. Note that this might make some
145
   attacks possible so don't do it unless you really have to. */
146
#  define CHECK_CMSG(cmsg) ((cmsg) != NULL)
147
#else
148
#  define CHECK_CMSG(cmsg) \
149
0
  ((cmsg) != NULL && \
150
0
   (size_t)(cmsg)->cmsg_len >= (size_t)CMSG_LEN(sizeof(int)) && \
151
0
   (cmsg)->cmsg_level == SOL_SOCKET && (cmsg)->cmsg_type == SCM_RIGHTS)
152
#endif
153
154
ssize_t fd_read(int handle, void *data, size_t size, int *fd)
155
0
{
156
0
  struct msghdr msg;
157
0
  struct iovec iov;
158
0
  struct cmsghdr *cmsg;
159
0
  ssize_t ret;
160
0
  char buf[CMSG_SPACE(sizeof(int))];
161
162
0
  i_assert(size > 0 && size < INT_MAX);
163
164
0
  memset(&msg, 0, sizeof (struct msghdr));
165
166
0
  iov.iov_base = data;
167
0
  iov.iov_len = size;
168
169
0
  msg.msg_iov = &iov;
170
0
  msg.msg_iovlen = 1;
171
172
0
  memset(buf, 0, sizeof(buf));
173
0
  msg.msg_control = buf;
174
0
  msg.msg_controllen = sizeof(buf);
175
176
0
  ret = recvmsg(handle, &msg, 0);
177
0
  if (ret <= 0) {
178
0
    *fd = -1;
179
0
    return ret;
180
0
  }
181
182
  /* at least one byte transferred - we should have the fd now.
183
     do extra checks to make sure it really is an fd that is being
184
     transferred to avoid potential DoS conditions. some systems don't
185
     set all these values correctly however so CHECK_CMSG() is somewhat
186
     system dependent */
187
0
  cmsg = CMSG_FIRSTHDR(&msg);
188
0
  if (!CHECK_CMSG(cmsg))
189
0
    *fd = -1;
190
0
  else
191
0
    memcpy(fd, CMSG_DATA(cmsg), sizeof(*fd));
192
0
  return ret;
193
0
}
194
195
#else
196
#  ifdef __GNUC__
197
#    warning SCM_RIGHTS not supported, privilege separation not possible
198
#  endif
199
ssize_t fd_send(int handle ATTR_UNUSED, int send_fd ATTR_UNUSED,
200
    const void *data ATTR_UNUSED, size_t size ATTR_UNUSED)
201
{
202
  errno = ENOSYS;
203
  return -1;
204
}
205
206
ssize_t fd_read(int handle ATTR_UNUSED, void *data ATTR_UNUSED,
207
    size_t size ATTR_UNUSED, int *fd ATTR_UNUSED)
208
{
209
  errno = ENOSYS;
210
  return -1;
211
}
212
#endif