-
Notifications
You must be signed in to change notification settings - Fork 5
/
index.js
157 lines (133 loc) · 5.53 KB
/
index.js
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
/**
* Copyright (c) 2014, 2015, 2021 Tim Kuijsten
*
* Permission to use, copy, modify, and/or distribute this software for any
* purpose with or without fee is hereby granted, provided that the above
* copyright notice and this permission notice appear in all copies.
*
* THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES
* WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
* MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR
* ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
* WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
* ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
* OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
*/
'use strict';
var fs = require('fs');
var path = require('path');
var posix = require('posix');
/**
* Change the root directory of the current process. A normal user must be provided
* since changing root without dropping privileges makes no sense from a security
* point of view.
*
* @param {String} newRoot The path to the new root directory for this process.
* The whole path should be owned by the super user and may not be writable
* by the group owner or others.
* @param {String|Number} user The user to switch to after changing the root
* directory. Can be either a name or an id.
* @param {String|Number} [group] The group to switch to after changing the root
* directory. Can be either a name or an id of any group the user belongs to
* (see /etc/groups). Defaults to the users primary group (see /etc/passwd).
* @throw if any operation fails
*/
module.exports = function chroot(newRoot, user, group) {
if (typeof newRoot !== 'string') { throw new TypeError('newRoot must be a string'); }
if (typeof user !== 'string' && typeof user !== 'number') { throw new TypeError('user must be a string or a number'); }
if (typeof group !== 'undefined') {
if (typeof group !== 'string' && typeof group !== 'number') { throw new TypeError('group must be a string or a number'); }
}
if (!(newRoot.length > 0)) { throw new Error('newRoot must contain at least one character'); }
if (typeof user === 'string' && !(user.length > 0)) { throw new Error('user must contain at least one character'); }
if (process.getuid() !== 0 || posix.geteuid() !== 0) {
throw new Error('chroot must be called while running as root');
}
var pwd, grp, uid, gid;
// resolve user to a numeric id
try {
pwd = posix.getpwnam(user);
} catch(err) {
throw new Error('user not found: ' + user);
}
uid = pwd.uid;
gid = pwd.gid;
if (typeof group !== 'undefined') {
if (typeof group === 'number') {
gid = group;
} else {
// resolve group to a numeric id
try {
grp = posix.getgrnam(group);
} catch(err) {
throw new Error('group not found: ' + group);
}
gid = grp.gid;
}
}
if (typeof uid !== 'number') { throw new TypeError('could not resolve the user to a number'); }
if (typeof gid !== 'number') { throw new TypeError('could not resolve the group to a number'); }
if (!(uid > 0)) { throw new Error('new user can not have user id 0'); }
if (!(gid > 0)) { throw new Error('new group can not have group id 0'); }
// check permissions up to the original root of the file system
var rpath = newRoot = fs.realpathSync(newRoot);
var stats;
do {
stats = fs.statSync(rpath);
if (stats.uid !== 0 || (stats.mode & (fs.constants.S_IWGRP | fs.constants.S_IWOTH)) !== 0) {
throw new Error('bad chroot dir ' + rpath + ' owner: ' + stats.uid + ' or permissions: 0' + stats.mode.toString(8));
}
rpath = path.dirname(rpath);
} while (rpath !== '/');
// set supplementary groups before chrooting
try {
if (typeof group === 'undefined') {
// change to the given user and all the groups that it is a member of
process.initgroups(uid, gid);
} else {
// change to the given user and the given group
process.setgroups([gid]);
}
} catch(err) {
throw new Error('changing groups failed: ' + err.message);
}
try {
posix.chroot(newRoot);
} catch(err) {
throw new Error('changing root failed: ' + err.message);
}
process.chdir('/');
// PWD might be set in some environments and is part of POSIX
if (typeof process.env.PWD !== 'undefined') {
process.env.PWD = '/';
}
process.setgid(gid);
process.setuid(uid);
// try to restore privileges
try {
posix.setreuid(-1, 0);
} catch(err) {
// double check real and effective ids of the user and group and supplemental groups
var ids = [process.getuid(), process.getgid(), posix.geteuid(), posix.getegid()];
Array.prototype.push.apply(ids, process.getgroups());
// if none of the ids is zero, privileges are successfully dropped
if (!~ids.indexOf(0)) {
// success
return;
}
}
throw new Error('unable to drop privileges');
};
/**
* TODO: add utilities that can help a user in inspecting a designated chroot by
* checking paths up to the original root for ownership and permissions,
* environment and open file descriptors.
*
* see also:
* http://www.unixwiz.net/techtips/chroot-practices.html
* http://www.dwheeler.com/secure-programs/Secure-Programs-HOWTO/minimize-privileges.html
* http://www.cs.berkeley.edu/~daw/papers/setuid-usenix02.pdf
* http://www.lst.de/~okir/blackhats/node97.html
* https://medium.com/@fun_cuddles/opening-files-in-node-js-considered-harmful-d7de566d499f
* https://github.com/joyent/node/issues/6905
*/