Coverage Report

Created: 2026-06-10 06:09

next uncovered line (L), next uncovered region (R), next uncovered branch (B)
/src/proftpd/modules/mod_rlimit.c
Line
Count
Source
1
/*
2
 * ProFTPD - FTP server daemon
3
 * Copyright (c) 2013-2026 The ProFTPD Project team
4
 *
5
 * This program is free software; you can redistribute it and/or modify
6
 * it under the terms of the GNU General Public License as published by
7
 * the Free Software Foundation; either version 2 of the License, or
8
 * (at your option) any later version.
9
 *
10
 * This program is distributed in the hope that it will be useful,
11
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13
 * GNU General Public License for more details.
14
 *
15
 * You should have received a copy of the GNU General Public License
16
 * along with this program; if not, see <https://www.gnu.org/licenses/>.
17
 *
18
 * As a special exemption, Public Flood Software/MacGyver aka Habeeb J. Dihu
19
 * and other respective copyright holders give permission to link this program
20
 * with OpenSSL, and distribute the resulting executable, without including
21
 * the source code for OpenSSL in the source distribution.
22
 */
23
24
/* Resource limit module */
25
26
#include "conf.h"
27
#include "privs.h"
28
29
#define MOD_RLIMIT_VERSION    "mod_rlimit/1.0"
30
31
/* On some platforms, including both sys/prctl.h and linux/prctl.h will cause
32
 * build errors similar to this one:
33
 *
34
 *  https://github.com/dvarrazzo/py-setproctitle/issues/44
35
 *
36
 * So try to work around this behavior by including sys/prctrl.h if available,
37
 * and if not, try linux/prctl.h.
38
 */
39
#if defined(HAVE_SYS_PRCTL_H)
40
# include <sys/prctl.h>
41
#elif defined(HAVE_LINUX_PRCTL_H)
42
# include <linux/prctl.h>
43
#endif
44
45
module rlimit_module;
46
47
0
#define DAEMON_SCOPE    3
48
0
#define SESSION_SCOPE   4
49
50
0
static int get_num_bytes(const char *nbytes_str, rlim_t *nbytes) {
51
0
  unsigned long inb;
52
0
  char units, junk;
53
0
  int res;
54
55
  /* Scan in the given argument, checking for the leading number-of-bytes
56
   * as well as a trailing G, M, K, or B (case-insensitive).  The junk
57
   * variable is catch arguments like "2g2" or "number-letter-whatever".
58
   *
59
   * NOTE: There is no portable way to scan in an ssize_t, so we do unsigned
60
   * long and cast it.  This probably places a 32-bit limit on rlimit values.
61
   */
62
0
  res = sscanf(nbytes_str, "%lu%c%c", &inb, &units, &junk);
63
0
  if (res == 2) {
64
0
    if (units != 'G' && units != 'g' &&
65
0
        units != 'M' && units != 'm' &&
66
0
        units != 'K' && units != 'k' &&
67
0
        units != 'B' && units != 'b') {
68
0
      errno = EINVAL;
69
0
      return -1;
70
0
    }
71
72
0
    *nbytes = inb;
73
74
    /* Calculate the actual bytes, multiplying by the given units.  Doing
75
     * it this way means that <math.h> and -lm aren't required.
76
     */
77
0
    if (units == 'G' ||
78
0
        units == 'g') {
79
0
      *nbytes *= (1024 * 1024 * 1024);
80
0
    }
81
82
0
    if (units == 'M' ||
83
0
        units == 'm') {
84
0
      *nbytes *= (1024 * 1024);
85
0
    }
86
87
0
    if (units == 'K' ||
88
0
        units == 'k') {
89
0
      *nbytes *= 1024;
90
0
    }
91
92
    /* Silently ignore units of 'B' and 'b', as they don't affect
93
     * the requested number of bytes anyway.
94
     */
95
96
0
    return 0;
97
0
  }
98
99
0
  if (res == 1) {
100
    /* No units given.  Return the number of bytes as is. */
101
0
    *nbytes = inb;
102
0
    return 0;
103
0
  }
104
105
  /* Default return value: the given argument was badly formatted.
106
   */
107
0
  errno = EINVAL;
108
0
  return -1;
109
0
}
110
111
/* usage: RLimitChroot on|off */
112
0
MODRET set_rlimitchroot(cmd_rec *cmd) {
113
0
  config_rec *c;
114
0
  int use_guard = 0;
115
116
0
  CHECK_ARGS(cmd, 1);
117
0
  CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
118
119
0
  use_guard = get_boolean(cmd, 1);
120
0
  if (use_guard == -1) {
121
0
    CONF_ERROR(cmd, "expected Boolean parameter");
122
0
  }
123
124
0
  c = add_config_param(cmd->argv[0], 1, NULL);
125
0
  c->argv[0] = palloc(c->pool, sizeof(int));
126
0
  *((int *) c->argv[0]) = use_guard;
127
128
0
  if (pr_module_exists("mod_ifsession.c")) {
129
    /* These are needed in case this directive is used with mod_ifsession
130
     * configuration.
131
     */
132
0
    c->flags |= CF_MULTI;
133
0
  }
134
135
0
  return PR_HANDLED(cmd);
136
0
}
137
138
/* usage: RLimitCPU ["daemon"|"session"] soft-limit [hard-limit] */
139
0
MODRET set_rlimitcpu(cmd_rec *cmd) {
140
0
#if defined(RLIMIT_CPU)
141
0
  config_rec *c = NULL;
142
0
  rlim_t current, max;
143
144
  /* Make sure the directive has between 1 and 3 parameters */
145
0
  if (cmd->argc-1 < 1 ||
146
0
      cmd->argc-1 > 3) {
147
0
    CONF_ERROR(cmd, "wrong number of parameters");
148
0
  }
149
150
  /* The context check for this directive depends on the first parameter.
151
   * For backwards compatibility, this parameter may be a number, or it
152
   * may be "daemon", "session", or "none".  If it happens to be
153
   * "daemon", then this directive should be in the CONF_ROOT context only.
154
   * Otherwise, it can appear in the full range of server contexts.
155
   */
156
157
0
  if (strcasecmp(cmd->argv[1], "daemon") == 0) {
158
0
    CHECK_CONF(cmd, CONF_ROOT);
159
160
0
  } else {
161
0
    CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
162
0
  }
163
164
0
  if (pr_rlimit_get_cpu(&current, &max) < 0 &&
165
0
      errno != ENOSYS) {
166
0
    pr_log_pri(PR_LOG_NOTICE, "unable to retrieve CPU resource limits: %s",
167
0
      strerror(errno));
168
0
  }
169
170
  /* Handle the newer format, which uses "daemon" or "session" or "none"
171
   * as the first parameter.
172
   */
173
0
  if (strcasecmp(cmd->argv[1], "daemon") == 0 ||
174
0
      strcasecmp(cmd->argv[1], "session") == 0) {
175
176
0
    if (strcasecmp(cmd->argv[2], "max") == 0 ||
177
0
        strcasecmp(cmd->argv[2], "unlimited") == 0) {
178
0
      current = RLIM_INFINITY;
179
180
0
    } else {
181
      /* Check that the non-max argument is a number, and error out if not. */
182
0
      char *ptr = NULL;
183
0
      unsigned long num = strtoul(cmd->argv[2], &ptr, 10);
184
185
0
      if (ptr && *ptr) {
186
0
        CONF_ERROR(cmd, "badly formatted argument");
187
0
      }
188
189
0
      current = num;
190
0
    }
191
192
    /* Handle the optional "hard limit" parameter, if present. */
193
0
    if (cmd->argc-1 == 3) {
194
0
      if (strcasecmp(cmd->argv[3], "max") == 0 ||
195
0
          strcasecmp(cmd->argv[3], "unlimited") == 0) {
196
0
        max = RLIM_INFINITY;
197
198
0
      } else {
199
        /* Check that the non-max argument is a number, and error out if not. */
200
0
        char *ptr = NULL;
201
0
        unsigned long num = strtoul(cmd->argv[3], &ptr, 10);
202
203
0
        if (ptr && *ptr) {
204
0
          CONF_ERROR(cmd, "badly formatted argument");
205
0
        }
206
207
0
        max = num;
208
0
      }
209
210
0
    } else {
211
      /* Assume that the hard limit should be the same as the soft limit. */
212
0
      max = current;
213
0
    }
214
215
0
    c = add_config_param(cmd->argv[0], 3, NULL, NULL, NULL);
216
0
    c->argv[0] = pstrdup(c->pool, cmd->argv[1]);
217
0
    c->argv[1] = palloc(c->pool, sizeof(rlim_t));
218
0
    *((rlim_t *) c->argv[1]) = current;
219
0
    c->argv[2] = palloc(c->pool, sizeof(rlim_t));
220
0
    *((rlim_t *) c->argv[2]) = max;
221
222
  /* Handle the older format, which will have a number as the first parameter.
223
   */
224
0
  } else {
225
0
    if (strcasecmp(cmd->argv[1], "max") == 0 ||
226
0
        strcasecmp(cmd->argv[1], "unlimited") == 0) {
227
0
      current = RLIM_INFINITY;
228
229
0
    } else {
230
      /* Check that the non-max argument is a number, and error out if not. */
231
0
      char *ptr = NULL;
232
0
      long num = strtol(cmd->argv[1], &ptr, 10);
233
234
0
      if (ptr && *ptr) {
235
0
        CONF_ERROR(cmd, "badly formatted argument");
236
0
      }
237
238
0
      current = num;
239
0
    }
240
241
    /* Handle the optional "hard limit" parameter, if present. */
242
0
    if (cmd->argc-1 == 2) {
243
0
      if (strcasecmp(cmd->argv[2], "max") == 0 ||
244
0
          strcasecmp(cmd->argv[2], "unlimited") == 0) {
245
0
        max = RLIM_INFINITY;
246
247
0
      } else {
248
        /* Check that the non-max argument is a number, and error out if not. */
249
0
        char *ptr = NULL;
250
0
        long num = strtol(cmd->argv[2], &ptr, 10);
251
252
0
        if (ptr && *ptr) {
253
0
          CONF_ERROR(cmd, "badly formatted argument");
254
0
        }
255
256
0
        max = num;
257
0
      }
258
259
0
    } else {
260
      /* Assume that the hard limit should be the same as the soft limit. */
261
0
      max = current;
262
0
    }
263
264
0
    c = add_config_param(cmd->argv[0], 2, NULL, NULL);
265
0
    c->argv[0] = palloc(c->pool, sizeof(rlim_t));
266
0
    *((rlim_t *) c->argv[0]) = current;
267
0
    c->argv[1] = palloc(c->pool, sizeof(rlim_t));
268
0
    *((rlim_t *) c->argv[1]) = max;
269
0
  }
270
271
0
  if (pr_module_exists("mod_ifsession.c")) {
272
    /* These are needed in case this directive is used with mod_ifsession
273
     * configuration.
274
     */
275
0
    c->flags |= CF_MULTI;
276
0
  }
277
278
0
  return PR_HANDLED(cmd);
279
#else
280
  CONF_ERROR(cmd, "RLimitCPU is not supported on this platform");
281
#endif
282
0
}
283
284
/* usage: RLimitMemory ["daemon"|"session"] soft-limit [hard-limit] */
285
0
MODRET set_rlimitmemory(cmd_rec *cmd) {
286
0
#if defined(RLIMIT_AS) || defined(RLIMIT_DATA) || defined(RLIMIT_VMEM)
287
0
  config_rec *c = NULL;
288
0
  rlim_t current, max;
289
290
  /* Make sure the directive has between 1 and 3 parameters */
291
0
  if (cmd->argc-1 < 1 ||
292
0
      cmd->argc-1 > 3) {
293
0
    CONF_ERROR(cmd, "wrong number of parameters");
294
0
  }
295
296
  /* The context check for this directive depends on the first parameter.
297
   * For backwards compatibility, this parameter may be a number, or it
298
   * may be "daemon", "session", or "none".  If it happens to be
299
   * "daemon", then this directive should be in the CONF_ROOT context only.
300
   * Otherwise, it can appear in the full range of server contexts.
301
   */
302
303
0
  if (strcasecmp(cmd->argv[1], "daemon") == 0) {
304
0
    CHECK_CONF(cmd, CONF_ROOT);
305
306
0
  } else {
307
0
    CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
308
0
  }
309
310
  /* Retrieve the current values */
311
0
  if (pr_rlimit_get_memory(&current, &max) < 0 &&
312
0
      errno != ENOSYS) {
313
0
    pr_log_pri(PR_LOG_NOTICE, "unable to get memory resource limits: %s",
314
0
      strerror(errno));
315
0
  }
316
317
  /* Handle the newer format, which uses "daemon" or "session" or "none"
318
   * as the first parameter.
319
   */
320
0
  if (strcasecmp(cmd->argv[1], "daemon") == 0 ||
321
0
      strcasecmp(cmd->argv[1], "session") == 0) {
322
323
0
    if (strcasecmp(cmd->argv[2], "max") == 0 ||
324
0
        strcasecmp(cmd->argv[2], "unlimited") == 0) {
325
0
      current = RLIM_INFINITY;
326
327
0
    } else {
328
0
      if (get_num_bytes(cmd->argv[2], &current) < 0) {
329
0
        CONF_ERROR(cmd, "badly formatted argument");
330
0
      }
331
0
    }
332
333
    /* Handle the optional "hard limit" parameter, if present. */
334
0
    if (cmd->argc-1 == 3) {
335
0
      if (strcasecmp(cmd->argv[3], "max") == 0 ||
336
0
          strcasecmp(cmd->argv[3], "unlimited") == 0) {
337
0
        max = RLIM_INFINITY;
338
339
0
      } else {
340
0
        if (get_num_bytes(cmd->argv[3], &max) < 0) {
341
0
          CONF_ERROR(cmd, "badly formatted argument");
342
0
        }
343
0
      }
344
345
0
    } else {
346
      /* Assume that the hard limit should be the same as the soft limit. */
347
0
      max = current;
348
0
    }
349
350
0
    c = add_config_param(cmd->argv[0], 3, NULL, NULL, NULL);
351
0
    c->argv[0] = pstrdup(c->pool, cmd->argv[1]);
352
0
    c->argv[1] = palloc(c->pool, sizeof(rlim_t));
353
0
    *((rlim_t *) c->argv[1]) = current;
354
0
    c->argv[2] = palloc(c->pool, sizeof(rlim_t));
355
0
    *((rlim_t *) c->argv[2]) = max;
356
357
  /* Handle the older format, which will have a number as the first
358
   * parameter.
359
   */
360
0
  } else {
361
0
    if (strcasecmp(cmd->argv[1], "max") == 0 ||
362
0
        strcasecmp(cmd->argv[1], "unlimited") == 0) {
363
0
      current = RLIM_INFINITY;
364
365
0
    } else {
366
0
      if (get_num_bytes(cmd->argv[1], &current) < 0) {
367
0
        CONF_ERROR(cmd, "badly formatted argument");
368
0
      }
369
0
    }
370
371
    /* Handle the optional "hard limit" parameter, if present. */
372
0
    if (cmd->argc-1 == 2) {
373
0
      if (strcasecmp(cmd->argv[2], "max") == 0 ||
374
0
          strcasecmp(cmd->argv[2], "unlimited") == 0) {
375
0
        max = RLIM_INFINITY;
376
377
0
      } else {
378
0
        if (get_num_bytes(cmd->argv[2], &max) < 0) {
379
0
          CONF_ERROR(cmd, "badly formatted argument");
380
0
        }
381
0
      }
382
383
0
    } else {
384
      /* Assume that the hard limit should be the same as the soft limit. */
385
0
      max = current;
386
0
    }
387
388
0
    c = add_config_param(cmd->argv[0], 2, NULL, NULL);
389
0
    c->argv[0] = palloc(c->pool, sizeof(rlim_t));
390
0
    *((rlim_t *) c->argv[0]) = current;
391
0
    c->argv[1] = palloc(c->pool, sizeof(rlim_t));
392
0
    *((rlim_t *) c->argv[1]) = max;
393
0
  }
394
395
0
  if (pr_module_exists("mod_ifsession.c")) {
396
    /* These are needed in case this directive is used with mod_ifsession
397
     * configuration.
398
     */
399
0
    c->flags |= CF_MULTI;
400
0
  }
401
402
0
  return PR_HANDLED(cmd);
403
#else
404
  CONF_ERROR(cmd, "RLimitMemory is not supported on this platform");
405
#endif
406
0
}
407
408
/* usage: RLimitOpenFiles ["daemon"|"session"] soft-limit [hard-limit] */
409
0
MODRET set_rlimitopenfiles(cmd_rec *cmd) {
410
0
#if defined(RLIMIT_NOFILE) || defined(RLIMIT_OFILE)
411
0
  config_rec *c = NULL;
412
0
  rlim_t current, max;
413
414
  /* Make sure the directive has between 1 and 3 parameters */
415
0
  if (cmd->argc-1 < 1 ||
416
0
      cmd->argc-1 > 3) {
417
0
    CONF_ERROR(cmd, "wrong number of parameters");
418
0
  }
419
420
  /* The context check for this directive depends on the first parameter.
421
   * For backwards compatibility, this parameter may be a number, or it
422
   * may be "daemon", "session", or "none".  If it happens to be
423
   * "daemon", then this directive should be in the CONF_ROOT context only.
424
   * Otherwise, it can appear in the full range of server contexts.
425
   */
426
427
0
  if (strcasecmp(cmd->argv[1], "daemon") == 0) {
428
0
    CHECK_CONF(cmd, CONF_ROOT);
429
430
0
  } else {
431
0
    CHECK_CONF(cmd, CONF_ROOT|CONF_VIRTUAL|CONF_GLOBAL);
432
0
  }
433
434
  /* Retrieve the current values */
435
0
  if (pr_rlimit_get_files(&current, &max) < 0) {
436
0
    pr_log_pri(PR_LOG_NOTICE, "unable to get file resource limits: %s",
437
0
      strerror(errno));
438
0
  }
439
440
  /* Handle the newer format, which uses "daemon" or "session" or "none"
441
   * as the first parameter.
442
   */
443
0
  if (strcasecmp(cmd->argv[1], "daemon") == 0 ||
444
0
      strcasecmp(cmd->argv[1], "session") == 0) {
445
446
0
    if (strcasecmp(cmd->argv[2], "max") == 0 ||
447
0
        strcasecmp(cmd->argv[2], "unlimited") == 0) {
448
0
      current = sysconf(_SC_OPEN_MAX);
449
450
0
    } else {
451
      /* Check that the non-max argument is a number, and error out if not. */
452
0
      char *ptr = NULL;
453
0
      long num = strtol(cmd->argv[2], &ptr, 10);
454
455
0
      if (ptr && *ptr) {
456
0
        CONF_ERROR(cmd, "badly formatted argument");
457
0
      }
458
459
0
      current = num;
460
0
    }
461
462
    /* Handle the optional "hard limit" parameter, if present. */
463
0
    if (cmd->argc-1 == 3) {
464
0
      if (strcasecmp(cmd->argv[3], "max") == 0 ||
465
0
          strcasecmp(cmd->argv[3], "unlimited") == 0) {
466
0
        max = sysconf(_SC_OPEN_MAX);
467
468
0
      } else {
469
        /* Check that the non-max argument is a number, and error out if not. */
470
0
        char *ptr = NULL;
471
0
        long num = strtol(cmd->argv[3], &ptr, 10);
472
473
0
        if (ptr && *ptr) {
474
0
          CONF_ERROR(cmd, "badly formatted argument");
475
0
        }
476
477
0
        max = num;
478
0
      }
479
480
0
    } else {
481
      /* Assume that the hard limit should be the same as the soft limit. */
482
0
      max = current;
483
0
    }
484
485
0
    c = add_config_param(cmd->argv[0], 3, NULL, NULL, NULL);
486
0
    c->argv[0] = pstrdup(c->pool, cmd->argv[1]);
487
0
    c->argv[1] = palloc(c->pool, sizeof(rlim_t));
488
0
    *((rlim_t *) c->argv[1]) = current;
489
0
    c->argv[2] = palloc(c->pool, sizeof(rlim_t));
490
0
    *((rlim_t *) c->argv[2]) = max;
491
492
  /* Handle the older format, which will have a number as the first
493
   * parameter.
494
   */
495
0
  } else {
496
0
    if (strcasecmp(cmd->argv[1], "max") == 0 ||
497
0
        strcasecmp(cmd->argv[1], "unlimited") == 0) {
498
0
      current = sysconf(_SC_OPEN_MAX);
499
500
0
    } else {
501
      /* Check that the non-max argument is a number, and error out if not. */
502
0
      char *ptr = NULL;
503
0
      long num = strtol(cmd->argv[1], &ptr, 10);
504
505
0
      if (ptr && *ptr) {
506
0
        CONF_ERROR(cmd, "badly formatted argument");
507
0
      }
508
509
0
      current = num;
510
0
    }
511
512
    /* Handle the optional "hard limit" parameter, if present. */
513
0
    if (cmd->argc-1 == 2) {
514
0
      if (strcasecmp(cmd->argv[2], "max") == 0 ||
515
0
          strcasecmp(cmd->argv[2], "unlimited") == 0) {
516
0
        max = sysconf(_SC_OPEN_MAX);
517
518
0
      } else {
519
        /* Check that the non-max argument is a number, and error out if not. */
520
0
        char *ptr = NULL;
521
0
        long num = strtol(cmd->argv[2], &ptr, 10);
522
523
0
        if (ptr && *ptr) {
524
0
          CONF_ERROR(cmd, "badly formatted argument");
525
0
        }
526
527
0
        max = num;
528
0
      }
529
530
0
    } else {
531
      /* Assume that the hard limit should be the same as the soft limit. */
532
0
      max = current;
533
0
    }
534
535
0
    c = add_config_param(cmd->argv[0], 2, NULL, NULL);
536
0
    c->argv[0] = palloc(c->pool, sizeof(rlim_t));
537
0
    *((rlim_t *) c->argv[0]) = current;
538
0
    c->argv[1] = palloc(c->pool, sizeof(rlim_t));
539
0
    *((rlim_t *) c->argv[1]) = max;
540
0
  }
541
542
0
  if (pr_module_exists("mod_ifsession.c")) {
543
    /* These are needed in case this directive is used with mod_ifsession
544
     * configuration.
545
     */
546
0
    c->flags |= CF_MULTI;
547
0
  }
548
549
0
  return PR_HANDLED(cmd);
550
#else
551
  CONF_ERROR(cmd, "RLimitOpenFiles is not supported on this platform");
552
#endif
553
0
}
554
555
0
MODRET rlimit_post_pass(cmd_rec *cmd) {
556
0
  config_rec *c;
557
558
0
  c = find_config(main_server->conf, CONF_PARAM, "RLimitChroot", FALSE);
559
0
  if (c != NULL) {
560
0
    int guard_chroot;
561
562
0
    guard_chroot = *((int *) c->argv[0]);
563
0
    if (guard_chroot == FALSE) {
564
0
      pr_fsio_guard_chroot(FALSE);
565
0
    }
566
0
  }
567
568
0
  return PR_DECLINED(cmd);
569
0
}
570
571
0
static int rlimit_set_core(int scope) {
572
0
  rlim_t current, max;
573
0
  int res, xerrno;
574
575
#ifdef PR_DEVEL_COREDUMP
576
  current = max = RLIM_INFINITY;
577
#else
578
0
  current = max = 0;
579
0
#endif /* PR_DEVEL_COREDUMP */
580
581
0
  PRIVS_ROOT
582
0
  res = pr_rlimit_set_core(current, max);
583
0
  xerrno = errno;
584
0
  PRIVS_RELINQUISH
585
586
0
  if (res < 0) {
587
0
    pr_log_pri(PR_LOG_ERR, "error setting core resource limits: %s",
588
0
      strerror(xerrno));
589
590
0
  } else {
591
0
    pr_log_debug(DEBUG2, "set core resource limits for daemon");
592
0
  }
593
594
0
#if !defined(PR_DEVEL_COREDUMP) && \
595
0
    defined(HAVE_PRCTL) && \
596
0
    defined(PR_SET_DUMPABLE)
597
0
  if (max == 0) {
598
    /* Really, no core dumps please. On Linux, there are exceptions made
599
     * even when setting RLIMIT_CORE = 0; see:
600
     *
601
     *  https://lkml.org/lkml/2011/8/24/136
602
     *
603
     * so when possible, use PR_SET_DUMPABLE to ensure that no coredumps
604
     * happen.
605
     */
606
0
    if (prctl(PR_SET_DUMPABLE, 0, 0, 0, 0) < 0) {
607
0
      pr_log_pri(PR_LOG_ERR, "error setting PR_SET_DUMPABLE to false: %s",
608
0
        strerror(errno));
609
0
    }
610
0
  }
611
0
#endif /* no --enable-devel=coredump and HAVE_PRCTL and PR_SET_DUMPABLE */
612
613
0
  errno = xerrno;
614
0
  return res;
615
0
}
616
617
0
static int rlimit_set_cpu(int scope) {
618
0
  config_rec *c;
619
620
  /* Now check for the configurable resource limits */
621
0
  c = find_config(main_server->conf, CONF_PARAM, "RLimitCPU", FALSE);
622
0
  while (c != NULL) {
623
0
    int res, use_config = FALSE, xerrno;
624
0
    rlim_t current, max;
625
626
0
    pr_signals_handle();
627
628
0
    if (scope == DAEMON_SCOPE) {
629
      /* Does this limit apply to the daemon? */
630
0
      if (c->argc == 3 &&
631
0
          strcasecmp(c->argv[0], "daemon") == 0) {
632
0
        use_config = TRUE;
633
0
      }
634
635
0
    } else if (scope == SESSION_SCOPE) {
636
      /* Does this limit apply to the session? */
637
0
      if (c->argc == 2 ||
638
0
          (c->argc == 3 &&
639
0
           strcasecmp(c->argv[0], "session") == 0)) {
640
0
        use_config = TRUE;
641
0
      }
642
0
    }
643
644
0
    if (use_config == FALSE) {
645
0
      c = find_config_next(c, c->next, CONF_PARAM, "RLimitCPU", FALSE);
646
0
      continue;
647
0
    }
648
649
0
    if (c->argc == 2) {
650
0
      current = *((rlim_t *) c->argv[0]);
651
0
      max = *((rlim_t *) c->argv[1]);
652
653
0
    } else {
654
0
      current = *((rlim_t *) c->argv[1]);
655
0
      max = *((rlim_t *) c->argv[2]);
656
0
    }
657
658
0
    PRIVS_ROOT
659
0
    res = pr_rlimit_set_cpu(current, max);
660
0
    xerrno = errno;
661
0
    PRIVS_RELINQUISH
662
663
0
    if (res < 0) {
664
0
      pr_log_pri(PR_LOG_ERR, "error setting CPU resource limits: %s",
665
0
        strerror(xerrno));
666
667
0
    } else {
668
0
      pr_log_debug(DEBUG2, "set CPU resource limits for %s",
669
0
        scope == DAEMON_SCOPE ? "daemon" : "session");
670
0
    }
671
672
0
    c = find_config_next(c, c->next, CONF_PARAM, "RLimitCPU", FALSE);
673
0
  }
674
675
0
  return 0;
676
0
}
677
678
0
static int rlimit_set_files(int scope) {
679
0
  config_rec *c;
680
681
  /* Now check for the configurable resource limits */
682
0
  c = find_config(main_server->conf, CONF_PARAM, "RLimitOpenFiles", FALSE);
683
0
  while (c != NULL) {
684
0
    int res, use_config = FALSE, xerrno;
685
0
    rlim_t current, max;
686
687
0
    pr_signals_handle();
688
689
0
    if (scope == DAEMON_SCOPE) {
690
      /* Does this limit apply to the daemon? */
691
0
      if (c->argc == 3 &&
692
0
          strcasecmp(c->argv[0], "daemon") == 0) {
693
0
        use_config = TRUE;
694
0
      }
695
696
0
    } else if (scope == SESSION_SCOPE) {
697
      /* Does this limit apply to the session? */
698
0
      if (c->argc == 2 ||
699
0
          (c->argc == 3 &&
700
0
           strcasecmp(c->argv[0], "session") == 0)) {
701
0
        use_config = TRUE;
702
0
      }
703
0
    }
704
705
0
    if (use_config == FALSE) {
706
0
      c = find_config_next(c, c->next, CONF_PARAM, "RLimitOpenFiles", FALSE);
707
0
      continue;
708
0
    }
709
710
0
    if (c->argc == 2) {
711
0
      current = *((rlim_t *) c->argv[0]);
712
0
      max = *((rlim_t *) c->argv[1]);
713
714
0
    } else {
715
0
      current = *((rlim_t *) c->argv[1]);
716
0
      max = *((rlim_t *) c->argv[2]);
717
0
    }
718
719
0
    PRIVS_ROOT
720
0
    res = pr_rlimit_set_files(current, max);
721
0
    xerrno = errno;
722
0
    PRIVS_RELINQUISH
723
724
0
    if (res < 0) {
725
0
      pr_log_pri(PR_LOG_ERR, "error setting file resource limits: %s",
726
0
        strerror(xerrno));
727
728
0
    } else {
729
0
      pr_log_debug(DEBUG2, "set file resource limits for %s",
730
0
        scope == DAEMON_SCOPE ? "daemon" : "session");
731
0
    }
732
733
0
    c = find_config_next(c, c->next, CONF_PARAM, "RLimitOpenFiles", FALSE);
734
0
  }
735
736
0
  return 0;
737
0
}
738
739
0
static int rlimit_set_memory(int scope) {
740
0
  config_rec *c;
741
742
  /* Now check for the configurable resource limits */
743
0
  c = find_config(main_server->conf, CONF_PARAM, "RLimitMemory", FALSE);
744
0
  while (c != NULL) {
745
0
    int res, use_config = FALSE, xerrno;
746
0
    rlim_t current, max;
747
748
0
    pr_signals_handle();
749
750
0
    if (scope == DAEMON_SCOPE) {
751
      /* Does this limit apply to the daemon? */
752
0
      if (c->argc == 3 &&
753
0
          strcasecmp(c->argv[0], "daemon") == 0) {
754
0
        use_config = TRUE;
755
0
      }
756
757
0
    } else if (scope == SESSION_SCOPE) {
758
      /* Does this limit apply to the session? */
759
0
      if (c->argc == 2 ||
760
0
          (c->argc == 3 &&
761
0
           strcasecmp(c->argv[0], "session") == 0)) {
762
0
        use_config = TRUE;
763
0
      }
764
0
    }
765
766
0
    if (use_config == FALSE) {
767
0
      c = find_config_next(c, c->next, CONF_PARAM, "RLimitMemory", FALSE);
768
0
      continue;
769
0
    }
770
771
0
    if (c->argc == 2) {
772
0
      current = *((rlim_t *) c->argv[0]);
773
0
      max = *((rlim_t *) c->argv[1]);
774
775
0
    } else {
776
0
      current = *((rlim_t *) c->argv[1]);
777
0
      max = *((rlim_t *) c->argv[2]);
778
0
    }
779
780
0
    PRIVS_ROOT
781
0
    res = pr_rlimit_set_memory(current, max);
782
0
    xerrno = errno;
783
0
    PRIVS_RELINQUISH
784
785
0
    if (res < 0) {
786
0
      pr_log_pri(PR_LOG_ERR, "error setting memory resource limits: %s",
787
0
        strerror(xerrno));
788
789
0
    } else {
790
0
      pr_log_debug(DEBUG2, "set memory resource limits for %s",
791
0
        scope == DAEMON_SCOPE ? "daemon" : "session");
792
0
    }
793
794
0
    c = find_config_next(c, c->next, CONF_PARAM, "RLimitMemory", FALSE);
795
0
  }
796
797
0
  return 0;
798
0
}
799
800
/* Event listeners */
801
802
0
static void rlimit_chroot_ev(const void *event_data, void *user_data) {
803
0
  const char *path;
804
0
  size_t path_len;
805
806
  /* If we are chrooted, AND the chroot path is anything other than "/",
807
   * then set the FSIO API flag to guard certain sensitive directories.
808
   */
809
810
0
  path = event_data;
811
0
  path_len = strlen(path);
812
813
0
  if (path_len > 1) {
814
0
    pr_fsio_guard_chroot(TRUE);
815
0
  }
816
0
}
817
818
0
static void rlimit_postparse_ev(const void *event_data, void *user_data) {
819
  /* Since we're the parent process, we do not want to set the process
820
   * resource limits; we would prevent future session processes.
821
   */
822
823
0
  rlimit_set_core(DAEMON_SCOPE);
824
0
  rlimit_set_cpu(DAEMON_SCOPE);
825
0
  rlimit_set_memory(DAEMON_SCOPE);
826
0
  rlimit_set_files(DAEMON_SCOPE);
827
0
}
828
829
/* Module initialization */
830
0
static int rlimit_init(void) {
831
0
  pr_event_register(&rlimit_module, "core.postparse", rlimit_postparse_ev,
832
0
    NULL);
833
834
0
  return 0;
835
0
}
836
837
0
static int rlimit_sess_init(void) {
838
0
  config_rec *c;
839
0
  int guard_chroot = TRUE;
840
841
0
  rlimit_set_cpu(SESSION_SCOPE);
842
0
  rlimit_set_memory(SESSION_SCOPE);
843
0
  rlimit_set_files(SESSION_SCOPE);
844
845
0
  c = find_config(main_server->conf, CONF_PARAM, "RLimitChroot", FALSE);
846
0
  if (c != NULL) {
847
0
    guard_chroot = *((int *) c->argv[0]);
848
0
  }
849
850
0
  if (guard_chroot) {
851
    /* Register an event listener for the 'core.chroot' event, so that we
852
     * can set the switch (if necessary) for guarding against attacks like
853
     * "Roaring Beast" when we are chrooted.
854
     */
855
0
    pr_event_register(&rlimit_module, "core.chroot", rlimit_chroot_ev, NULL);
856
0
  }
857
858
0
  return 0;
859
0
}
860
861
/* Module API tables
862
 */
863
864
static conftable rlimit_conftab[] = {
865
  { "RLimitChroot",   set_rlimitchroot,   NULL },
866
  { "RLimitCPU",    set_rlimitcpu,      NULL },
867
  { "RLimitMemory",   set_rlimitmemory,   NULL },
868
  { "RLimitOpenFiles",    set_rlimitopenfiles,    NULL },
869
870
  { NULL, NULL, NULL }
871
};
872
873
static cmdtable rlimit_cmdtab[] = {
874
  { POST_CMD, C_PASS, G_NONE, rlimit_post_pass, FALSE, FALSE, CL_AUTH },
875
  { 0, NULL }
876
};
877
878
module rlimit_module = {
879
  NULL, NULL,
880
881
  /* Module API version */
882
  0x20,
883
884
  /* Module name */
885
  "rlimit",
886
887
  /* Module configuration directive table */
888
  rlimit_conftab,
889
890
  /* Module command handler table */
891
  rlimit_cmdtab,
892
893
  /* Module authentication handler table */
894
  NULL,
895
896
  /* Module initialization function */
897
  rlimit_init,
898
899
  /* Session initialization function */
900
  rlimit_sess_init,
901
902
  /* Module version */
903
  MOD_RLIMIT_VERSION
904
};