Coverage Report

Created: 2026-04-12 06:41

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/radvd/privsep-linux.c
Line
Count
Source
1
/*
2
 *
3
 *   Authors:
4
 *    Jim Paris     <jim@jtan.com>
5
 *    Pedro Roque   <roque@di.fc.ul.pt>
6
 *    Lars Fenneberg    <lf@elemental.net>
7
 *
8
 *   This software is Copyright 1996,1997,2008 by the above mentioned author(s),
9
 *   All Rights Reserved.
10
 *
11
 *   The license which is distributed with this software in the file COPYRIGHT
12
 *   applies to this software. If your distribution is missing this file, you
13
 *   may request it from <reubenhwk@gmail.com>.
14
 *
15
 */
16
17
#include "config.h"
18
#include "includes.h"
19
#include "pathnames.h"
20
#include "radvd.h"
21
22
static int set_interface_var(const char *iface, const char *var, const char *name, uint32_t val);
23
static void privsep_read_loop(void);
24
25
/* For reading or writing, depending on process */
26
static int pfd = -1;
27
28
0
void privsep_set_write_fd(int fd) { pfd = fd; }
29
30
/* Command types */
31
enum privsep_type {
32
  SET_INTERFACE_LINKMTU,
33
  SET_INTERFACE_CURHLIM,
34
  SET_INTERFACE_REACHTIME,
35
  SET_INTERFACE_RETRANSTIMER,
36
};
37
38
/* Command sent over pipe is a fixed size binary structure. */
39
struct privsep_command {
40
  int type;
41
  char iface[IFNAMSIZ];
42
  uint32_t val;
43
};
44
45
/* Privileged read loop */
46
static void privsep_read_loop(void)
47
0
{
48
0
  while (1) {
49
0
    struct privsep_command cmd;
50
0
    int ret = readn(pfd, &cmd, sizeof(cmd));
51
0
    if (ret <= 0) {
52
      /* Error or EOF, give up */
53
0
      if (ret < 0) {
54
0
        flog(LOG_ERR, "Exiting, privsep_read_loop had readn error: %s", strerror(errno));
55
0
      } else {
56
0
        flog(LOG_ERR, "Exiting, privsep_read_loop had readn return 0 bytes");
57
0
      }
58
0
    }
59
0
    if (ret != sizeof(cmd)) {
60
      /* Short read, ignore */
61
0
      return;
62
0
    }
63
64
0
    cmd.iface[IFNAMSIZ - 1] = '\0';
65
66
0
    switch (cmd.type) {
67
68
0
    case SET_INTERFACE_LINKMTU:
69
0
      if (cmd.val < MIN_AdvLinkMTU || cmd.val > MAX_AdvLinkMTU) {
70
0
        flog(LOG_ERR, "(privsep) %s: LinkMTU (%u) is not within the defined bounds, ignoring", cmd.iface,
71
0
             cmd.val);
72
0
        break;
73
0
      }
74
0
      ret = set_interface_var(cmd.iface, PROC_SYS_IP6_LINKMTU, "LinkMTU", cmd.val);
75
0
      break;
76
77
0
    case SET_INTERFACE_CURHLIM:
78
0
      if (cmd.val < MIN_AdvCurHopLimit || cmd.val > MAX_AdvCurHopLimit) {
79
0
        flog(LOG_ERR, "(privsep) %s: CurHopLimit (%u) is not within the defined bounds, ignoring",
80
0
             cmd.iface, cmd.val);
81
0
        break;
82
0
      }
83
0
      ret = set_interface_var(cmd.iface, PROC_SYS_IP6_CURHLIM, "CurHopLimit", cmd.val);
84
0
      break;
85
86
0
    case SET_INTERFACE_REACHTIME:
87
0
      if (cmd.val < MIN_AdvReachableTime || cmd.val > MAX_AdvReachableTime) {
88
0
        flog(LOG_ERR, "(privsep) %s: BaseReachableTimer (%u) is not within the defined bounds, ignoring",
89
0
             cmd.iface, cmd.val);
90
0
        break;
91
0
      }
92
0
      ret = set_interface_var(cmd.iface, PROC_SYS_IP6_BASEREACHTIME_MS, "BaseReachableTimer (ms)", cmd.val);
93
0
      if (ret == 0)
94
0
        break;
95
0
      set_interface_var(cmd.iface, PROC_SYS_IP6_BASEREACHTIME, "BaseReachableTimer", cmd.val / 1000);
96
0
      break;
97
98
0
    case SET_INTERFACE_RETRANSTIMER:
99
0
      if (cmd.val < MIN_AdvRetransTimer || cmd.val > MAX_AdvRetransTimer) {
100
0
        flog(LOG_ERR, "(privsep) %s: RetransTimer (%u) is not within the defined bounds, ignoring",
101
0
             cmd.iface, cmd.val);
102
0
        break;
103
0
      }
104
0
      ret = set_interface_var(cmd.iface, PROC_SYS_IP6_RETRANSTIMER_MS, "RetransTimer (ms)", cmd.val);
105
0
      if (ret == 0)
106
0
        break;
107
0
      set_interface_var(cmd.iface, PROC_SYS_IP6_RETRANSTIMER, "RetransTimer",
108
0
            cmd.val / 1000 * USER_HZ); /* XXX user_hz */
109
0
      break;
110
111
0
    default:
112
      /* Bad command */
113
0
      break;
114
0
    }
115
0
  }
116
0
}
117
118
void privsep_init(int fd)
119
0
{
120
  /* This will be the privileged child */
121
0
  pfd = fd;
122
0
  privsep_read_loop();
123
0
  close(pfd);
124
0
  flog(LOG_ERR, "Exiting, privsep_read_loop is complete.");
125
0
}
126
127
/* Interface calls for the unprivileged process */
128
int privsep_interface_linkmtu(const char *iface, uint32_t mtu)
129
0
{
130
0
  struct privsep_command cmd;
131
0
  cmd.type = SET_INTERFACE_LINKMTU;
132
0
  memset(&cmd.iface, 0, sizeof(cmd.iface));
133
0
  strlcpy(cmd.iface, iface, sizeof(cmd.iface));
134
0
  cmd.val = mtu;
135
136
0
  if (writen(pfd, &cmd, sizeof(cmd)) != sizeof(cmd))
137
0
    return -1;
138
0
  return 0;
139
0
}
140
141
int privsep_interface_curhlim(const char *iface, uint32_t hlim)
142
0
{
143
0
  struct privsep_command cmd;
144
0
  cmd.type = SET_INTERFACE_CURHLIM;
145
0
  memset(&cmd.iface, 0, sizeof(cmd.iface));
146
0
  strlcpy(cmd.iface, iface, sizeof(cmd.iface));
147
0
  cmd.val = hlim;
148
0
  if (writen(pfd, &cmd, sizeof(cmd)) != sizeof(cmd))
149
0
    return -1;
150
0
  return 0;
151
0
}
152
153
int privsep_interface_reachtime(const char *iface, uint32_t rtime)
154
0
{
155
0
  struct privsep_command cmd;
156
0
  cmd.type = SET_INTERFACE_REACHTIME;
157
0
  memset(&cmd.iface, 0, sizeof(cmd.iface));
158
0
  strlcpy(cmd.iface, iface, sizeof(cmd.iface));
159
0
  cmd.val = rtime;
160
0
  if (writen(pfd, &cmd, sizeof(cmd)) != sizeof(cmd))
161
0
    return -1;
162
0
  return 0;
163
0
}
164
165
int privsep_interface_retranstimer(const char *iface, uint32_t rettimer)
166
0
{
167
0
  struct privsep_command cmd;
168
0
  cmd.type = SET_INTERFACE_RETRANSTIMER;
169
0
  memset(&cmd.iface, 0, sizeof(cmd.iface));
170
0
  strlcpy(cmd.iface, iface, sizeof(cmd.iface));
171
0
  cmd.val = rettimer;
172
0
  if (writen(pfd, &cmd, sizeof(cmd)) != sizeof(cmd))
173
0
    return -1;
174
0
  return 0;
175
0
}
176
177
/* note: also called from the root context */
178
static int set_interface_var(const char *iface, const char *var, const char *name, uint32_t val)
179
0
{
180
0
  int retval = -1;
181
0
  FILE *fp = 0;
182
0
  char *spath = strdupf(var, iface);
183
0
  int fd = -1 ;
184
185
  /* No path traversal */
186
  // TODO: if interface names contain '/' in future, this may break.
187
0
  if (!iface[0] || !strcmp(iface, ".") || !strcmp(iface, "..") || strchr(iface, '/'))
188
0
    goto errmsg;
189
190
  // Open the file for writing, ONLY if it already exists.
191
  // explicitly ensure that O_CREAT is *NOT* set.
192
0
  fd = open(spath, O_WRONLY & (~O_CREAT), 0);
193
0
  if (fd == -1) {
194
    // If the file does NOT exist, this is non-fatal; as we fallback to a
195
    // different filename at a higher level.
196
    // ensure we cleanup and return -1 to the higher level
197
0
    if (errno == ENOENT)
198
0
      goto cleanup;
199
    // If we got some other error, e.g. ENOACCESS
200
0
    goto errmsg;
201
0
  }
202
203
  // We know the file exists now, write to it.
204
0
  fp = fdopen(fd, "w");
205
0
  if (!fp)
206
0
    goto errmsg;
207
208
0
  if (fprintf(fp, "%u", val) > 0) {
209
0
    retval = 0;
210
0
  }
211
212
0
errmsg:
213
0
  if (name && retval != 0)
214
0
    flog(LOG_ERR, "failed to set %s (%u) for %s: %s", name, val, iface, strerror(errno));
215
0
cleanup:
216
217
0
  if (fp)
218
0
    fclose(fp);
219
0
  if (fd != -1)
220
0
    close(fd);
221
222
0
  free(spath);
223
224
0
  return retval;
225
0
}