Skip to content

Commit 1f2865c

Browse files
authored
Merge pull request #7798 from jketema/missing-open-arg
C++: Add query for missing mode argument in `open`/`openat` calls
2 parents ac03fab + b967eaf commit 1f2865c

11 files changed

+190
-17
lines changed
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: minorAnalysis
3+
---
4+
* The `cpp/world-writable-file-creation` query now only detects `open` and `openat` calls with the `O_CREAT` or `O_TMPFILE` flag.
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
---
2+
category: newQuery
3+
---
4+
* Added a new query, `cpp/open-call-with-mode-argument`, to detect when `open` or `openat` is called with the `O_CREAT` or `O_TMPFILE` flag but when the `mode` argument is omitted.

cpp/ql/lib/semmle/code/cpp/commons/unix/Constants.qll

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -11,10 +11,10 @@ import cpp
1111
*/
1212
bindingset[input]
1313
int parseOctal(string input) {
14-
input.charAt(0) = "0" and
14+
input.regexpMatch("0[0-7]+") and
1515
result =
1616
strictsum(int ix |
17-
ix in [0 .. input.length()]
17+
ix in [1 .. input.length()]
1818
|
1919
8.pow(input.length() - (ix + 1)) * input.charAt(ix).toInt()
2020
)

cpp/ql/src/Security/CWE/CWE-732/DoNotCreateWorldWritable.ql

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -12,17 +12,16 @@
1212

1313
import cpp
1414
import FilePermissions
15-
import semmle.code.cpp.commons.unix.Constants
1615

1716
predicate worldWritableCreation(FileCreationExpr fc, int mode) {
1817
mode = localUmask(fc).mask(fc.getMode()) and
19-
sets(mode, s_iwoth())
18+
setsAnyBits(mode, UnixConstants::s_iwoth())
2019
}
2120

2221
predicate setWorldWritable(FunctionCall fc, int mode) {
2322
fc.getTarget().getName() = ["chmod", "fchmod", "_chmod", "_wchmod"] and
2423
mode = fc.getArgument(1).getValue().toInt() and
25-
sets(mode, s_iwoth())
24+
setsAnyBits(mode, UnixConstants::s_iwoth())
2625
}
2726

2827
from Expr fc, int mode, string message

cpp/ql/src/Security/CWE/CWE-732/FilePermissions.qll

Lines changed: 85 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,49 @@
11
import cpp
2-
import semmle.code.cpp.commons.unix.Constants
2+
import semmle.code.cpp.commons.unix.Constants as UnixConstants
3+
4+
/**
5+
* Gets the number corresponding to the contents of `input` in base-16.
6+
* Note: the first two characters of `input` must be `0x`. For example:
7+
* `parseHex("0x123abc") = 1194684`.
8+
*/
9+
bindingset[input]
10+
int parseHex(string input) {
11+
exists(string lowerCaseInput | lowerCaseInput = input.toLowerCase() |
12+
lowerCaseInput.regexpMatch("0x[0-9a-f]+") and
13+
result =
14+
strictsum(int ix |
15+
ix in [2 .. input.length()]
16+
|
17+
16.pow(input.length() - (ix + 1)) * "0123456789abcdef".indexOf(lowerCaseInput.charAt(ix))
18+
)
19+
)
20+
}
21+
22+
/**
23+
* Gets the value defined by the `O_CREAT` macro if the macro
24+
* exists and if every definition defines the same value.
25+
*/
26+
int o_creat() {
27+
result =
28+
unique(int v |
29+
exists(Macro m | m.getName() = "O_CREAT" |
30+
v = parseHex(m.getBody()) or v = UnixConstants::parseOctal(m.getBody())
31+
)
32+
)
33+
}
34+
35+
/**
36+
* Gets the value defined by the `O_TMPFILE` macro if the macro
37+
* exists and if every definition defines the same value.
38+
*/
39+
int o_tmpfile() {
40+
result =
41+
unique(int v |
42+
exists(Macro m | m.getName() = "O_TMPFILE" |
43+
v = parseHex(m.getBody()) or v = UnixConstants::parseOctal(m.getBody())
44+
)
45+
)
46+
}
347

448
bindingset[n, digit]
549
private string octalDigit(int n, int digit) {
@@ -20,11 +64,17 @@ string octalFileMode(int mode) {
2064
else result = "[non-standard mode: decimal " + mode + "]"
2165
}
2266

67+
/**
68+
* Holds if the bitmask `value` sets the bits in `flag`.
69+
*/
70+
bindingset[value, flag]
71+
predicate setsFlag(int value, int flag) { value.bitAnd(flag) = flag }
72+
2373
/**
2474
* Holds if the bitmask `mask` sets any of the bit fields in `fields`.
2575
*/
2676
bindingset[mask, fields]
27-
predicate sets(int mask, int fields) { mask.bitAnd(fields) != 0 }
77+
predicate setsAnyBits(int mask, int fields) { mask.bitAnd(fields) != 0 }
2878

2979
/**
3080
* Gets the value that `fc` sets the umask to, if `fc` is a call to
@@ -83,16 +133,24 @@ abstract class FileCreationExpr extends FunctionCall {
83133
abstract int getMode();
84134
}
85135

86-
class OpenCreationExpr extends FileCreationExpr {
136+
abstract class FileCreationWithOptionalModeExpr extends FileCreationExpr {
137+
abstract predicate hasModeArgument();
138+
}
139+
140+
class OpenCreationExpr extends FileCreationWithOptionalModeExpr {
87141
OpenCreationExpr() {
88-
this.getTarget().getName() = ["open", "_open", "_wopen"] and
89-
sets(this.getArgument(1).getValue().toInt(), o_creat())
142+
this.getTarget().hasGlobalOrStdName(["open", "_open", "_wopen"]) and
143+
exists(int flag | flag = this.getArgument(1).getValue().toInt() |
144+
setsFlag(flag, o_creat()) or setsFlag(flag, o_tmpfile())
145+
)
90146
}
91147

92148
override Expr getPath() { result = this.getArgument(0) }
93149

150+
override predicate hasModeArgument() { exists(this.getArgument(2)) }
151+
94152
override int getMode() {
95-
if exists(this.getArgument(2))
153+
if this.hasModeArgument()
96154
then result = this.getArgument(2).getValue().toInt()
97155
else
98156
// assume anything is permitted
@@ -108,20 +166,35 @@ class CreatCreationExpr extends FileCreationExpr {
108166
override int getMode() { result = this.getArgument(1).getValue().toInt() }
109167
}
110168

111-
class OpenatCreationExpr extends FileCreationExpr {
169+
class OpenatCreationExpr extends FileCreationWithOptionalModeExpr {
112170
OpenatCreationExpr() {
113-
this.getTarget().getName() = "openat" and
114-
this.getNumberOfArguments() = 4
171+
this.getTarget().hasGlobalOrStdName("openat") and
172+
exists(int flag | flag = this.getArgument(2).getValue().toInt() |
173+
setsFlag(flag, o_creat()) or setsFlag(flag, o_tmpfile())
174+
)
115175
}
116176

117177
override Expr getPath() { result = this.getArgument(1) }
118178

119-
override int getMode() { result = this.getArgument(3).getValue().toInt() }
179+
override predicate hasModeArgument() { exists(this.getArgument(3)) }
180+
181+
override int getMode() {
182+
if this.hasModeArgument()
183+
then result = this.getArgument(3).getValue().toInt()
184+
else
185+
// assume anything is permitted
186+
result = 0.bitNot()
187+
}
120188
}
121189

122190
private int fopenMode() {
123191
result =
124-
s_irusr().bitOr(s_irgrp()).bitOr(s_iroth()).bitOr(s_iwusr()).bitOr(s_iwgrp()).bitOr(s_iwoth())
192+
UnixConstants::s_irusr()
193+
.bitOr(UnixConstants::s_irgrp())
194+
.bitOr(UnixConstants::s_iroth())
195+
.bitOr(UnixConstants::s_iwusr())
196+
.bitOr(UnixConstants::s_iwgrp())
197+
.bitOr(UnixConstants::s_iwoth())
125198
}
126199

127200
class FopenCreationExpr extends FileCreationExpr {
@@ -153,6 +226,6 @@ class FopensCreationExpr extends FileCreationExpr {
153226
// fopen_s has restrictive permissions unless you have "u" in the mode
154227
if this.getArgument(2).getValue().charAt(_) = "u"
155228
then result = fopenMode()
156-
else result = s_irusr().bitOr(s_iwusr())
229+
else result = UnixConstants::s_irusr().bitOr(UnixConstants::s_iwusr())
157230
}
158231
}
Lines changed: 9 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
int open_file_bad() {
2+
// BAD - this uses arbitrary bytes from the stack as mode argument
3+
return open(FILE, O_CREAT)
4+
}
5+
6+
int open_file_good() {
7+
// GOOD - the mode argument is supplied
8+
return open(FILE, O_CREAT, S_IRUSR | S_IWUSR)
9+
}
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
<!DOCTYPE qhelp PUBLIC
2+
"-//Semmle//qhelp//EN"
3+
"qhelp.dtd">
4+
<qhelp>
5+
6+
<overview>
7+
<p>
8+
When opening a file with the <code>O_CREAT</code> or <code>O_TMPFILE</code> flag, the <code>mode</code> must
9+
be supplied. If the <code>mode</code> argument is omitted, some arbitrary bytes from the stack will be used
10+
as the file mode. This leaks some bits from the stack into the permissions of the file.
11+
</p>
12+
</overview>
13+
14+
<recommendation>
15+
<p>
16+
The <code>mode</code> must be supplied when <code>O_CREAT</code> or <code>O_TMPFILE</code> is specified.
17+
</p>
18+
</recommendation>
19+
20+
<example>
21+
<p>
22+
The first example opens a file with the <code>O_CREAT</code> flag without supplying the <code>mode</code>
23+
argument. In this case arbitrary bytes from the stack will be used as <code>mode</code> argument. The
24+
second example correctly supplies the <code>mode</code> argument and creates a file that is user readable
25+
and writable.
26+
</p>
27+
28+
<sample src="OpenCallMissingModeArgument.c" />
29+
30+
</example>
31+
</qhelp>
Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
/**
2+
* @name File opened with O_CREAT flag but without mode argument
3+
* @description Opening a file with the O_CREAT flag but without mode argument reads arbitrary bytes from the stack.
4+
* @kind problem
5+
* @problem.severity error
6+
* @security-severity 7.8
7+
* @precision high
8+
* @id cpp/open-call-with-mode-argument
9+
* @tags security
10+
* external/cwe/cwe-732
11+
*/
12+
13+
import cpp
14+
import FilePermissions
15+
16+
from FileCreationWithOptionalModeExpr fc
17+
where not fc.hasModeArgument()
18+
select fc,
19+
"A file is created here without providing a mode argument, which may leak bits from the stack."
Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,29 @@
1+
typedef unsigned int mode_t;
2+
3+
#define O_RDWR 0x0002
4+
#define O_CLOEXEC 0x0040
5+
#define O_NONBLOCK 0x0080
6+
#define O_CREAT 0x0200
7+
#define O_APPEND 0x0800
8+
#define O_TMPFILE 0x2000
9+
10+
int open(const char *pathname, int flags, ...);
11+
12+
int openat(int dirfd, const char *pathname, int flags, ...);
13+
14+
const char *a_file = "/a_file";
15+
16+
void test_open() {
17+
open(a_file, O_NONBLOCK); // GOOD
18+
open(a_file, O_RDWR | O_CLOEXEC); // GOOD
19+
open(a_file, O_APPEND); // GOOD
20+
open(a_file, O_CREAT); // BAD
21+
open(a_file, O_CREAT, 0); // GOOD
22+
open(a_file, O_TMPFILE); // BAD
23+
open(a_file, O_TMPFILE, 0); // GOOD
24+
openat(0, a_file, O_APPEND); // GOOD
25+
openat(0, a_file, O_CREAT); // BAD
26+
openat(0, a_file, O_CREAT, 0); // GOOD
27+
openat(0, a_file, O_TMPFILE); // BAD
28+
openat(0, a_file, O_TMPFILE, 0); // GOOD
29+
}
Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,4 @@
1+
| OpenCallMissingModeArgument.c:20:3:20:6 | call to open | A file is created here without providing a mode argument, which may leak bits from the stack. |
2+
| OpenCallMissingModeArgument.c:22:3:22:6 | call to open | A file is created here without providing a mode argument, which may leak bits from the stack. |
3+
| OpenCallMissingModeArgument.c:25:3:25:8 | call to openat | A file is created here without providing a mode argument, which may leak bits from the stack. |
4+
| OpenCallMissingModeArgument.c:27:3:27:8 | call to openat | A file is created here without providing a mode argument, which may leak bits from the stack. |
Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
Security/CWE/CWE-732/OpenCallMissingModeArgument.ql

0 commit comments

Comments
 (0)