Skip to content

Commit 3c0f20c

Browse files
authored
Merge pull request #170 from github/weak-file-permissions
Add `rb/overly-permissive-file` query
2 parents 6c382cc + b2f2f78 commit 3c0f20c

6 files changed

Lines changed: 250 additions & 0 deletions

File tree

ql/src/codeql_ruby/ast/Literal.qll

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -47,6 +47,42 @@ class IntegerLiteral extends NumericLiteral, TIntegerLiteral {
4747

4848
final override string getValueText() { result = g.getValue() }
4949

50+
/** Gets the numerical value of this integer literal. */
51+
final int getValue() {
52+
exists(string s, string values, string str |
53+
s = this.getValueText().toLowerCase() and
54+
(
55+
s.matches("0b%") and
56+
values = "01" and
57+
str = s.suffix(2)
58+
or
59+
s.matches("0x%") and
60+
values = "0123456789abcdef" and
61+
str = s.suffix(2)
62+
or
63+
s.charAt(0) = "0" and
64+
not s.charAt(1) = ["b", "x", "o"] and
65+
values = "01234567" and
66+
str = s.suffix(1)
67+
or
68+
s.matches("0o%") and
69+
values = "01234567" and
70+
str = s.suffix(2)
71+
or
72+
s.charAt(0) != "0" and values = "0123456789" and str = s
73+
)
74+
|
75+
result =
76+
sum(int index, string c, int v, int exp |
77+
c = str.replaceAll("_", "").charAt(index) and
78+
v = values.indexOf(c.toLowerCase()) and
79+
exp = str.replaceAll("_", "").length() - index - 1
80+
|
81+
v * values.length().pow(exp)
82+
)
83+
)
84+
}
85+
5086
final override string toString() { result = this.getValueText() }
5187

5288
final override string getAPrimaryQlClass() { result = "IntegerLiteral" }
Lines changed: 26 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,26 @@
1+
<!DOCTYPE qhelp PUBLIC "-//Semmle//qhelp//EN" "qhelp.dtd">
2+
<qhelp>
3+
4+
<overview>
5+
<p>
6+
When creating a file, POSIX systems allow permissions to be specified
7+
for owner, group and others separately. Permissions should be kept as
8+
strict as possible, preventing access to the files contents by other users.
9+
</p>
10+
11+
</overview>
12+
13+
<recommendation>
14+
<p>
15+
Restrict the file permissions of files to prevent any but the owner being able to read or write to that file
16+
</p>
17+
</recommendation>
18+
19+
<references>
20+
<li>
21+
Wikipedia:
22+
<a href="https://en.wikipedia.org/wiki/File_system_permissions">File system permissions</a>.
23+
</li>
24+
</references>
25+
26+
</qhelp>
Lines changed: 99 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,99 @@
1+
/**
2+
* @name Overly permissive file permissions
3+
* @description Allowing files to be readable or writable by users other than the owner may allow sensitive information to be accessed.
4+
* @kind path-problem
5+
* @problem.severity warning
6+
* @id rb/overly-permissive-file
7+
* @tags external/cwe/cwe-732
8+
* security
9+
* @precision low
10+
*/
11+
12+
import ruby
13+
import codeql_ruby.DataFlow
14+
import DataFlow::PathGraph
15+
private import codeql_ruby.dataflow.SSA
16+
17+
// TODO: account for flows through tuple assignments
18+
/** An expression referencing the File or FileUtils module */
19+
class FileModuleAccess extends Expr {
20+
FileModuleAccess() {
21+
this.(ConstantAccess).getName() = "File"
22+
or
23+
this.(ConstantAccess).getName() = "FileUtils"
24+
or
25+
exists(FileModuleAccess fma, Ssa::WriteDefinition def |
26+
def.getARead() = this.getAControlFlowNode() and
27+
def.getWriteAccess().getParent().(Assignment).getRightOperand() = fma
28+
)
29+
}
30+
}
31+
32+
bindingset[p]
33+
int world_permission(int p) { result = p.bitAnd(7) }
34+
35+
// 70 oct = 56 dec
36+
bindingset[p]
37+
int group_permission(int p) { result = p.bitAnd(56) }
38+
39+
bindingset[p]
40+
string access(int p) {
41+
p.bitAnd(2) != 0 and result = "writable"
42+
or
43+
p.bitAnd(4) != 0 and result = "readable"
44+
}
45+
46+
/** An expression specifing a file permission that allows group/others read or write access */
47+
class PermissivePermissionsExpr extends Expr {
48+
// TODO: non-literal expressions?
49+
PermissivePermissionsExpr() {
50+
exists(int perm, string acc |
51+
perm = this.(IntegerLiteral).getValue() and
52+
(acc = access(world_permission(perm)) or acc = access(group_permission(perm)))
53+
)
54+
or
55+
// adding/setting read or write permissions for all/group/other
56+
this.(StringLiteral).getValueText().regexpMatch(".*[ago][^-=+]*[+=][xXst]*[rw].*")
57+
}
58+
}
59+
60+
/** A call to a method of File or FileUtils that may modify file permissions */
61+
class PermissionSettingMethodCall extends MethodCall {
62+
private string methodName;
63+
private Expr permArg;
64+
65+
PermissionSettingMethodCall() {
66+
this.getReceiver() instanceof FileModuleAccess and
67+
this.getMethodName() = methodName and
68+
(
69+
methodName in ["chmod", "chmod_R", "lchmod"] and permArg = this.getArgument(0)
70+
or
71+
methodName = "mkfifo" and permArg = this.getArgument(1)
72+
or
73+
methodName in ["new", "open"] and permArg = this.getArgument(2)
74+
or
75+
methodName in ["install", "makedirs", "mkdir", "mkdir_p", "mkpath"] and
76+
permArg = this.getKeywordArgument("mode")
77+
// TODO: defaults for optional args? This may depend on the umask
78+
)
79+
}
80+
81+
Expr getPermissionArgument() { result = permArg }
82+
}
83+
84+
class PermissivePermissionsConfig extends DataFlow::Configuration {
85+
PermissivePermissionsConfig() { this = "PermissivePermissionsConfig" }
86+
87+
override predicate isSource(DataFlow::Node source) {
88+
exists(PermissivePermissionsExpr ppe | source.asExpr().getExpr() = ppe)
89+
}
90+
91+
override predicate isSink(DataFlow::Node sink) {
92+
exists(PermissionSettingMethodCall c | sink.asExpr().getExpr() = c.getPermissionArgument())
93+
}
94+
}
95+
96+
from DataFlow::PathNode source, DataFlow::PathNode sink, PermissivePermissionsConfig conf
97+
where conf.hasFlowPath(source, sink)
98+
select sink.getNode(), source, sink, "Overly permissive mask sets file to $@.", source.getNode(),
99+
source.getNode().toString()
Lines changed: 58 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,58 @@
1+
require "fileutils"
2+
3+
def run_chmod_1(filename)
4+
FileUtils.chmod 0222, filename
5+
FileUtils.chmod 0622, filename
6+
FileUtils.chmod 0755, filename
7+
FileUtils.chmod 0777, filename
8+
end
9+
10+
module DummyModule
11+
def chmod(mode, list, options = {} )
12+
list
13+
end
14+
end
15+
16+
def run_chmod_2(filename)
17+
foo = FileUtils
18+
bar = foo
19+
baz = Dummy
20+
# "safe"
21+
baz.chmod 0755, filename
22+
baz = bar
23+
# unsafe
24+
baz.chmod 0755, filename
25+
end
26+
27+
def run_chmod_3(filename)
28+
# TODO: we currently miss this
29+
foo = FileUtils
30+
bar, baz = foo, 7
31+
bar.chmod 0755, filename
32+
end
33+
34+
def run_chmod_4(filename)
35+
# safe permissions
36+
FileUtils.chmod 0700, filename
37+
FileUtils.chmod 0711, filename
38+
FileUtils.chmod 0701, filename
39+
FileUtils.chmod 0710, filename
40+
end
41+
42+
def run_chmod_5(filename)
43+
perm = 0777
44+
FileUtils.chmod perm, filename
45+
perm2 = perm
46+
FileUtils.chmod perm2, filename
47+
48+
perm = "u=wrx,g=rwx,o=x"
49+
perm2 = perm
50+
FileUtils.chmod perm2, filename
51+
FileUtils.chmod "u=rwx,o+r", filename
52+
FileUtils.chmod "u=rwx,go-r", filename
53+
FileUtils.chmod "a+rw", filename
54+
end
55+
56+
def run_chmod_R(filename)
57+
File.chmod_R 0755, filename
58+
end
Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,30 @@
1+
edges
2+
| FilePermissions.rb:43:10:43:13 | 0777 : | FilePermissions.rb:44:19:44:22 | perm |
3+
| FilePermissions.rb:43:10:43:13 | 0777 : | FilePermissions.rb:46:19:46:23 | perm2 |
4+
| FilePermissions.rb:48:10:48:26 | "u=wrx,g=rwx,o=x" : | FilePermissions.rb:50:19:50:23 | perm2 |
5+
nodes
6+
| FilePermissions.rb:4:19:4:22 | 0222 | semmle.label | 0222 |
7+
| FilePermissions.rb:5:19:5:22 | 0622 | semmle.label | 0622 |
8+
| FilePermissions.rb:6:19:6:22 | 0755 | semmle.label | 0755 |
9+
| FilePermissions.rb:7:19:7:22 | 0777 | semmle.label | 0777 |
10+
| FilePermissions.rb:24:13:24:16 | 0755 | semmle.label | 0755 |
11+
| FilePermissions.rb:43:10:43:13 | 0777 : | semmle.label | 0777 : |
12+
| FilePermissions.rb:44:19:44:22 | perm | semmle.label | perm |
13+
| FilePermissions.rb:46:19:46:23 | perm2 | semmle.label | perm2 |
14+
| FilePermissions.rb:48:10:48:26 | "u=wrx,g=rwx,o=x" : | semmle.label | "u=wrx,g=rwx,o=x" : |
15+
| FilePermissions.rb:50:19:50:23 | perm2 | semmle.label | perm2 |
16+
| FilePermissions.rb:51:19:51:29 | "u=rwx,o+r" | semmle.label | "u=rwx,o+r" |
17+
| FilePermissions.rb:53:19:53:24 | "a+rw" | semmle.label | "a+rw" |
18+
| FilePermissions.rb:57:16:57:19 | 0755 | semmle.label | 0755 |
19+
#select
20+
| FilePermissions.rb:4:19:4:22 | 0222 | FilePermissions.rb:4:19:4:22 | 0222 | FilePermissions.rb:4:19:4:22 | 0222 | Overly permissive mask sets file to $@. | FilePermissions.rb:4:19:4:22 | 0222 | 0222 |
21+
| FilePermissions.rb:5:19:5:22 | 0622 | FilePermissions.rb:5:19:5:22 | 0622 | FilePermissions.rb:5:19:5:22 | 0622 | Overly permissive mask sets file to $@. | FilePermissions.rb:5:19:5:22 | 0622 | 0622 |
22+
| FilePermissions.rb:6:19:6:22 | 0755 | FilePermissions.rb:6:19:6:22 | 0755 | FilePermissions.rb:6:19:6:22 | 0755 | Overly permissive mask sets file to $@. | FilePermissions.rb:6:19:6:22 | 0755 | 0755 |
23+
| FilePermissions.rb:7:19:7:22 | 0777 | FilePermissions.rb:7:19:7:22 | 0777 | FilePermissions.rb:7:19:7:22 | 0777 | Overly permissive mask sets file to $@. | FilePermissions.rb:7:19:7:22 | 0777 | 0777 |
24+
| FilePermissions.rb:24:13:24:16 | 0755 | FilePermissions.rb:24:13:24:16 | 0755 | FilePermissions.rb:24:13:24:16 | 0755 | Overly permissive mask sets file to $@. | FilePermissions.rb:24:13:24:16 | 0755 | 0755 |
25+
| FilePermissions.rb:44:19:44:22 | perm | FilePermissions.rb:43:10:43:13 | 0777 : | FilePermissions.rb:44:19:44:22 | perm | Overly permissive mask sets file to $@. | FilePermissions.rb:43:10:43:13 | 0777 | 0777 |
26+
| FilePermissions.rb:46:19:46:23 | perm2 | FilePermissions.rb:43:10:43:13 | 0777 : | FilePermissions.rb:46:19:46:23 | perm2 | Overly permissive mask sets file to $@. | FilePermissions.rb:43:10:43:13 | 0777 | 0777 |
27+
| FilePermissions.rb:50:19:50:23 | perm2 | FilePermissions.rb:48:10:48:26 | "u=wrx,g=rwx,o=x" : | FilePermissions.rb:50:19:50:23 | perm2 | Overly permissive mask sets file to $@. | FilePermissions.rb:48:10:48:26 | "u=wrx,g=rwx,o=x" | "u=wrx,g=rwx,o=x" |
28+
| FilePermissions.rb:51:19:51:29 | "u=rwx,o+r" | FilePermissions.rb:51:19:51:29 | "u=rwx,o+r" | FilePermissions.rb:51:19:51:29 | "u=rwx,o+r" | Overly permissive mask sets file to $@. | FilePermissions.rb:51:19:51:29 | "u=rwx,o+r" | "u=rwx,o+r" |
29+
| FilePermissions.rb:53:19:53:24 | "a+rw" | FilePermissions.rb:53:19:53:24 | "a+rw" | FilePermissions.rb:53:19:53:24 | "a+rw" | Overly permissive mask sets file to $@. | FilePermissions.rb:53:19:53:24 | "a+rw" | "a+rw" |
30+
| FilePermissions.rb:57:16:57:19 | 0755 | FilePermissions.rb:57:16:57:19 | 0755 | FilePermissions.rb:57:16:57:19 | 0755 | Overly permissive mask sets file to $@. | FilePermissions.rb:57:16:57:19 | 0755 | 0755 |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
queries/security/cwe-732/WeakFilePermissions.ql

0 commit comments

Comments
 (0)