Coverage Report

Created: 2025-08-29 06:11

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