66 * @kind problem
77 * @problem.severity warning
88 * @security-severity 7.7
9- * @precision medium
9+ * @precision high
1010 * @id cpp/toctou-race-condition
1111 * @tags security
1212 * external/cwe/cwe-367
@@ -16,59 +16,60 @@ import cpp
1616import semmle.code.cpp.controlflow.Guards
1717
1818/**
19- * An operation on a filename.
19+ * An operation on a filename that is likely to modify the corresponding file
20+ * and may return an indication of success.
2021 *
21- * Note: we're not interested in operations on file descriptors, as they
22- * are better behaved.
22+ * Note: we're not interested in operations where the file is specified by a
23+ * descriptor, rather than a filename, as they are better behaved. We are
24+ * interested in functions that take a filename and return a file descriptor,
25+ * however.
2326 */
2427FunctionCall filenameOperation ( Expr path ) {
2528 exists ( string name | name = result .getTarget ( ) .getName ( ) |
26- (
27- name = "remove" or
28- name = "unlink" or
29- name = "rmdir" or
30- name = "rename" or
31- name = "chmod" or
32- name = "chown" or
33- name = "fopen" or
34- name = "open" or
35- name = "freopen" or
36- name = "_open" or
37- name = "_wopen" or
38- name = "_wfopen"
39- ) and
29+ name =
30+ [
31+ "remove" , "unlink" , "rmdir" , "rename" , "fopen" , "open" , "freopen" , "_open" , "_wopen" ,
32+ "_wfopen" , "_fsopen" , "_wfsopen"
33+ ] and
4034 result .getArgument ( 0 ) = path
4135 or
42- (
43- name = "fopen_s" or
44- name = "wfopen_s"
45- ) and
36+ name = [ "fopen_s" , "wfopen_s" , "rename" ] and
4637 result .getArgument ( 1 ) = path
4738 )
39+ or
40+ result = sensitiveFilenameOperation ( path )
41+ }
42+
43+ /**
44+ * An operation on a filename that is likely to modify the security properties
45+ * of the corresponding file and may return an indication of success.
46+ */
47+ FunctionCall sensitiveFilenameOperation ( Expr path ) {
48+ exists ( string name | name = result .getTarget ( ) .getName ( ) |
49+ name = [ "chmod" , "chown" ] and
50+ result .getArgument ( 0 ) = path
51+ )
4852}
4953
5054/**
51- * A use of `access` (or similar) on a filename.
55+ * An operation on a filename that returns information in the return value but
56+ * does not modify the corresponding file. For example, `access`.
5257 */
5358FunctionCall accessCheck ( Expr path ) {
5459 exists ( string name | name = result .getTarget ( ) .getName ( ) |
55- name = "access" or
56- name = "_access" or
57- name = "_waccess" or
58- name = "_access_s" or
59- name = "_waccess_s"
60+ name = [ "access" , "_access" , "_waccess" , "_access_s" , "_waccess_s" ]
6061 ) and
6162 path = result .getArgument ( 0 )
6263}
6364
6465/**
65- * A use of `stat` (or similar) on a filename.
66+ * An operation on a filename that returns information via a pointer argument
67+ * and any return value, but does not modify the corresponding file. For
68+ * example, `stat`.
6669 */
6770FunctionCall stat ( Expr path , Expr buf ) {
6871 exists ( string name | name = result .getTarget ( ) .getName ( ) |
69- name = "stat" or
70- name = "lstat" or
71- name = "fstat" or
72+ name = [ "stat" , "lstat" , "fstat" ] or
7273 name .matches ( "\\_stat%" ) or
7374 name .matches ( "\\_wstat%" )
7475 ) and
@@ -77,7 +78,7 @@ FunctionCall stat(Expr path, Expr buf) {
7778}
7879
7980/**
80- * Holds if `use` points to `source`, either by being the same or by
81+ * Holds if `use` refers to `source`, either by being the same or by
8182 * one step of variable indirection.
8283 */
8384predicate referenceTo ( Expr source , Expr use ) {
@@ -88,36 +89,45 @@ predicate referenceTo(Expr source, Expr use) {
8889 )
8990}
9091
91- from FunctionCall fc , Expr check , Expr checkUse , Expr opUse
92+ from Expr check , Expr checkPath , FunctionCall use , Expr usePath
9293where
93- // checkUse looks like a check on a filename
94+ // `check` looks like a check on a filename
9495 (
95- // either:
96- // an access check
97- check = accessCheck ( checkUse )
98- or
99- // a stat
100- check = stat ( checkUse , _)
96+ (
97+ // either:
98+ // an access check
99+ check = accessCheck ( checkPath )
100+ or
101+ // a stat
102+ check = stat ( checkPath , _)
103+ or
104+ // access to a member variable on the stat buf
105+ // (morally, this should be a use-use pair, but it seems unlikely
106+ // that this variable will get reused in practice)
107+ exists ( Expr call , Expr e , Variable v |
108+ call = stat ( checkPath , e ) and
109+ e .getAChild * ( ) .( VariableAccess ) .getTarget ( ) = v and
110+ check .( VariableAccess ) .getTarget ( ) = v and
111+ not e .getAChild * ( ) = check // the call that writes to the pointer is not where the pointer is checked.
112+ )
113+ ) and
114+ // `op` looks like an operation on a filename
115+ use = filenameOperation ( usePath )
101116 or
102117 // another filename operation (null pointers can indicate errors)
103- check = filenameOperation ( checkUse )
104- or
105- // access to a member variable on the stat buf
106- // (morally, this should be a use-use pair, but it seems unlikely
107- // that this variable will get reused in practice)
108- exists ( Variable buf | exists ( stat ( checkUse , buf .getAnAccess ( ) ) ) |
109- check .( VariableAccess ) .getQualifier ( ) = buf .getAnAccess ( )
110- )
118+ check = filenameOperation ( checkPath ) and
119+ // `op` looks like a sensitive operation on a filename
120+ use = sensitiveFilenameOperation ( usePath )
121+ ) and
122+ // `checkPath` and `usePath` refer to the same SSA variable
123+ exists ( SsaDefinition def , StackVariable v |
124+ def .getAUse ( v ) = checkPath and def .getAUse ( v ) = usePath
111125 ) and
112- // checkUse and opUse refer to the same SSA variable
113- exists ( SsaDefinition def , StackVariable v | def .getAUse ( v ) = checkUse and def .getAUse ( v ) = opUse ) and
114- // opUse looks like an operation on a filename
115- fc = filenameOperation ( opUse ) and
116- // the return value of check is used (possibly with one step of
117- // variable indirection) in a guard which controls fc
126+ // the return value of `check` is used (possibly with one step of
127+ // variable indirection) in a guard which controls `use`
118128 exists ( GuardCondition guard | referenceTo ( check , guard .getAChild * ( ) ) |
119- guard .controls ( fc .( ControlFlowNode ) .getBasicBlock ( ) , _)
129+ guard .controls ( use .( ControlFlowNode ) .getBasicBlock ( ) , _)
120130 )
121- select fc ,
131+ select use ,
122132 "The $@ being operated upon was previously $@, but the underlying file may have been changed since then." ,
123- opUse , "filename" , check , "checked"
133+ usePath , "filename" , check , "checked"
0 commit comments