analyzer: support for creat, dup, dup2 and dup3 [PR106298]
This patch extends the state machine in sm-fd.cc to support creat, dup, dup2 and dup3 functions. Lightly tested on x86_64 Linux. gcc/analyzer/ChangeLog: PR analyzer/106298 * sm-fd.cc (fd_state_machine::on_open): Add creat, dup, dup2 and dup3 functions. (enum dup): New. (fd_state_machine::valid_to_unchecked_state): New. (fd_state_machine::on_creat): New. (fd_state_machine::on_dup): New. gcc/testsuite/ChangeLog: PR analyzer/106298 * gcc.dg/analyzer/fd-1.c: Add tests for 'creat'. * gcc.dg/analyzer/fd-2.c: Likewise. * gcc.dg/analyzer/fd-4.c: Likewise. * gcc.dg/analyzer/fd-dup-1.c: New tests. Signed-off-by: Immad Mir <mirimmad@outlook.com>
This commit is contained in:
parent
6d41f7c39c
commit
6a11f2d974
5 changed files with 415 additions and 4 deletions
|
@ -69,6 +69,14 @@ enum access_directions
|
|||
DIRS_WRITE
|
||||
};
|
||||
|
||||
/* An enum for distinguishing between dup, dup2 and dup3. */
|
||||
enum dup
|
||||
{
|
||||
DUP_1,
|
||||
DUP_2,
|
||||
DUP_3
|
||||
};
|
||||
|
||||
class fd_state_machine : public state_machine
|
||||
{
|
||||
public:
|
||||
|
@ -114,7 +122,9 @@ public:
|
|||
bool is_readonly_fd_p (state_t s) const;
|
||||
bool is_writeonly_fd_p (state_t s) const;
|
||||
enum access_mode get_access_mode_from_flag (int flag) const;
|
||||
|
||||
/* Function for one-to-one correspondence between valid
|
||||
and unchecked states. */
|
||||
state_t valid_to_unchecked_state (state_t state) const;
|
||||
/* State for a constant file descriptor (>= 0) */
|
||||
state_t m_constant_fd;
|
||||
|
||||
|
@ -147,6 +157,8 @@ public:
|
|||
private:
|
||||
void on_open (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
|
||||
const gcall *call) const;
|
||||
void on_creat (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
|
||||
const gcall *call) const;
|
||||
void on_close (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
|
||||
const gcall *call) const;
|
||||
void on_read (sm_context *sm_ctxt, const supernode *node, const gimple *stmt,
|
||||
|
@ -170,6 +182,9 @@ private:
|
|||
const gimple *stmt, const gcall *call,
|
||||
const tree callee_fndecl, const char *attr_name,
|
||||
access_directions fd_attr_access_dir) const;
|
||||
void check_for_dup (sm_context *sm_ctxt, const supernode *node,
|
||||
const gimple *stmt, const gcall *call, const tree callee_fndecl,
|
||||
enum dup kind) const;
|
||||
};
|
||||
|
||||
/* Base diagnostic class relative to fd_state_machine. */
|
||||
|
@ -723,6 +738,20 @@ fd_state_machine::is_constant_fd_p (state_t state) const
|
|||
return (state == m_constant_fd);
|
||||
}
|
||||
|
||||
fd_state_machine::state_t
|
||||
fd_state_machine::valid_to_unchecked_state (state_t state) const
|
||||
{
|
||||
if (state == m_valid_read_write)
|
||||
return m_unchecked_read_write;
|
||||
else if (state == m_valid_write_only)
|
||||
return m_unchecked_write_only;
|
||||
else if (state == m_valid_read_only)
|
||||
return m_unchecked_read_only;
|
||||
else
|
||||
gcc_unreachable ();
|
||||
return NULL;
|
||||
}
|
||||
|
||||
bool
|
||||
fd_state_machine::on_stmt (sm_context *sm_ctxt, const supernode *node,
|
||||
const gimple *stmt) const
|
||||
|
@ -736,6 +765,11 @@ fd_state_machine::on_stmt (sm_context *sm_ctxt, const supernode *node,
|
|||
return true;
|
||||
} // "open"
|
||||
|
||||
if (is_named_call_p (callee_fndecl, "creat", call, 2))
|
||||
{
|
||||
on_creat (sm_ctxt, node, stmt, call);
|
||||
} // "creat"
|
||||
|
||||
if (is_named_call_p (callee_fndecl, "close", call, 1))
|
||||
{
|
||||
on_close (sm_ctxt, node, stmt, call);
|
||||
|
@ -754,6 +788,23 @@ fd_state_machine::on_stmt (sm_context *sm_ctxt, const supernode *node,
|
|||
return true;
|
||||
} // "read"
|
||||
|
||||
if (is_named_call_p (callee_fndecl, "dup", call, 1))
|
||||
{
|
||||
check_for_dup (sm_ctxt, node, stmt, call, callee_fndecl, DUP_1);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_named_call_p (callee_fndecl, "dup2", call, 2))
|
||||
{
|
||||
check_for_dup (sm_ctxt, node, stmt, call, callee_fndecl, DUP_2);
|
||||
return true;
|
||||
}
|
||||
|
||||
if (is_named_call_p (callee_fndecl, "dup3", call, 3))
|
||||
{
|
||||
check_for_dup (sm_ctxt, node, stmt, call, callee_fndecl, DUP_3);
|
||||
return true;
|
||||
}
|
||||
|
||||
{
|
||||
// Handle __attribute__((fd_arg))
|
||||
|
@ -899,6 +950,78 @@ fd_state_machine::on_open (sm_context *sm_ctxt, const supernode *node,
|
|||
}
|
||||
}
|
||||
|
||||
void
|
||||
fd_state_machine::on_creat (sm_context *sm_ctxt, const supernode *node,
|
||||
const gimple *stmt, const gcall *call) const
|
||||
{
|
||||
tree lhs = gimple_call_lhs (call);
|
||||
if (lhs)
|
||||
sm_ctxt->on_transition (node, stmt, lhs, m_start, m_unchecked_write_only);
|
||||
else
|
||||
sm_ctxt->warn (node, stmt, NULL_TREE, new fd_leak (*this, NULL_TREE));
|
||||
}
|
||||
|
||||
void
|
||||
fd_state_machine::check_for_dup (sm_context *sm_ctxt, const supernode *node,
|
||||
const gimple *stmt, const gcall *call,
|
||||
const tree callee_fndecl, enum dup kind) const
|
||||
{
|
||||
tree lhs = gimple_call_lhs (call);
|
||||
tree arg_1 = gimple_call_arg (call, 0);
|
||||
state_t state_arg_1 = sm_ctxt->get_state (stmt, arg_1);
|
||||
if (state_arg_1 == m_stop)
|
||||
return;
|
||||
if (!(is_constant_fd_p (state_arg_1) || is_valid_fd_p (state_arg_1)))
|
||||
{
|
||||
check_for_open_fd (sm_ctxt, node, stmt, call, callee_fndecl,
|
||||
DIRS_READ_WRITE);
|
||||
if (kind == DUP_1)
|
||||
return;
|
||||
}
|
||||
switch (kind)
|
||||
{
|
||||
case DUP_1:
|
||||
if (lhs)
|
||||
{
|
||||
if (is_constant_fd_p (state_arg_1))
|
||||
sm_ctxt->set_next_state (stmt, lhs, m_unchecked_read_write);
|
||||
else
|
||||
sm_ctxt->set_next_state (stmt, lhs,
|
||||
valid_to_unchecked_state (state_arg_1));
|
||||
}
|
||||
break;
|
||||
|
||||
case DUP_2:
|
||||
case DUP_3:
|
||||
tree arg_2 = gimple_call_arg (call, 1);
|
||||
state_t state_arg_2 = sm_ctxt->get_state (stmt, arg_2);
|
||||
tree diag_arg_2 = sm_ctxt->get_diagnostic_tree (arg_2);
|
||||
if (state_arg_2 == m_stop)
|
||||
return;
|
||||
/* Check if -1 was passed as second argument to dup2. */
|
||||
if (!(is_constant_fd_p (state_arg_2) || is_valid_fd_p (state_arg_2)))
|
||||
{
|
||||
sm_ctxt->warn (
|
||||
node, stmt, arg_2,
|
||||
new fd_use_without_check (*this, diag_arg_2, callee_fndecl));
|
||||
return;
|
||||
}
|
||||
/* dup2 returns value of its second argument on success.But, the
|
||||
access mode of the returned file descriptor depends on the duplicated
|
||||
file descriptor i.e the first argument. */
|
||||
if (lhs)
|
||||
{
|
||||
if (is_constant_fd_p (state_arg_1))
|
||||
sm_ctxt->set_next_state (stmt, lhs, m_unchecked_read_write);
|
||||
else
|
||||
sm_ctxt->set_next_state (stmt, lhs,
|
||||
valid_to_unchecked_state (state_arg_1));
|
||||
}
|
||||
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
fd_state_machine::on_close (sm_context *sm_ctxt, const supernode *node,
|
||||
const gimple *stmt, const gcall *call) const
|
||||
|
@ -964,6 +1087,8 @@ fd_state_machine::check_for_open_fd (
|
|||
}
|
||||
switch (callee_fndecl_dir)
|
||||
{
|
||||
case DIRS_READ_WRITE:
|
||||
break;
|
||||
case DIRS_READ:
|
||||
if (is_writeonly_fd_p (state))
|
||||
{
|
||||
|
@ -984,8 +1109,6 @@ fd_state_machine::check_for_open_fd (
|
|||
*this, diag_arg, DIRS_READ, callee_fndecl));
|
||||
}
|
||||
break;
|
||||
default:
|
||||
gcc_unreachable ();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
|
@ -3,6 +3,13 @@ int open(const char *, int mode);
|
|||
#define O_WRONLY 1
|
||||
#define O_RDWR 2
|
||||
|
||||
typedef enum {
|
||||
S_IRWXU
|
||||
// etc
|
||||
} mode_t;
|
||||
|
||||
int creat (const char *, mode_t mode);
|
||||
|
||||
void
|
||||
test_1 (const char *path)
|
||||
{
|
||||
|
@ -37,3 +44,17 @@ void test_4 (const char *path)
|
|||
/* { dg-message "\\(1\\) leaks here" "" { target *-*-* } .-1 } */
|
||||
}
|
||||
|
||||
void
|
||||
test_5 (const char *path, mode_t mode)
|
||||
{
|
||||
creat (path, mode); /* { dg-warning "leak of file descriptor \\\[CWE-775\\\]" } */
|
||||
}
|
||||
|
||||
void
|
||||
test_6 (const char *path, mode_t mode)
|
||||
{
|
||||
int fd = creat (path, mode);
|
||||
return; /* { dg-warning "leak of file descriptor 'fd' \\\[CWE-775\\\]" } */
|
||||
}
|
||||
|
||||
|
||||
|
|
|
@ -5,6 +5,13 @@ void close(int fd);
|
|||
#define O_RDWR 2
|
||||
#define STDIN 0
|
||||
|
||||
typedef enum {
|
||||
S_IRWXU
|
||||
// etc
|
||||
} mode_t;
|
||||
|
||||
int creat (const char *, mode_t mode);
|
||||
|
||||
void
|
||||
test_1 (const char *path)
|
||||
{
|
||||
|
@ -46,4 +53,12 @@ test_4 ()
|
|||
int fd = -1;
|
||||
close(fd);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void
|
||||
test_5 (const char *path, mode_t mode)
|
||||
{
|
||||
int fd = creat (path, mode);
|
||||
close(fd);
|
||||
close(fd); /* { dg-warning "double 'close' of file descriptor 'fd' \\\[CWE-1341\\\]" "warning" } */
|
||||
}
|
|
@ -9,6 +9,12 @@ int read (int fd, void *buf, int nbytes);
|
|||
#define O_WRONLY 1
|
||||
#define O_RDWR 2
|
||||
|
||||
typedef enum {
|
||||
S_IRWXU
|
||||
// etc
|
||||
} mode_t;
|
||||
|
||||
int creat (const char *, mode_t mode);
|
||||
|
||||
void
|
||||
test_1 (const char *path, void *buf)
|
||||
|
@ -69,4 +75,27 @@ test_5 (const char *path)
|
|||
int fd = open (path, O_RDWR);
|
||||
close(fd);
|
||||
printf("%d", fd); /* { dg-bogus "'printf' on a closed file descriptor 'fd'" } */
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void
|
||||
test_6 (const char *path, mode_t mode, void *buf)
|
||||
{
|
||||
int fd = creat (path, mode);
|
||||
if (fd != -1)
|
||||
{
|
||||
read (fd, buf, 1); /* { dg-warning "'read' on write-only file descriptor 'fd'" } */
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_7 (const char *path, mode_t mode, void *buf)
|
||||
{
|
||||
int fd = creat (path, mode);
|
||||
if (fd != -1)
|
||||
{
|
||||
write (fd, buf, 1);
|
||||
close(fd);
|
||||
}
|
||||
}
|
||||
|
|
223
gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
Normal file
223
gcc/testsuite/gcc.dg/analyzer/fd-dup-1.c
Normal file
|
@ -0,0 +1,223 @@
|
|||
int open(const char *, int mode);
|
||||
void close(int fd);
|
||||
int dup (int old_fd);
|
||||
int dup2 (int old_fd, int new_fd);
|
||||
int dup3 (int old_fd, int new_fd, int flags);
|
||||
int write (int fd, void *buf, int nbytes);
|
||||
int read (int fd, void *buf, int nbytes);
|
||||
#define O_RDONLY 0
|
||||
#define O_WRONLY 1
|
||||
#define O_RDWR 2
|
||||
|
||||
void test_1 (const char *path)
|
||||
{
|
||||
int old_fd = open (path, O_RDWR);
|
||||
int new_fd = dup (old_fd); /* { dg-warning "'dup' on possibly invalid file descriptor 'old_fd'" } */
|
||||
close(old_fd);
|
||||
close(new_fd);
|
||||
}
|
||||
|
||||
void test_2 (const char *path)
|
||||
{
|
||||
int old_fd = open (path, O_RDWR);
|
||||
if (old_fd != -1)
|
||||
{
|
||||
int new_fd = dup (old_fd);
|
||||
close(old_fd);
|
||||
return; /* { dg-warning "leak of file descriptor 'new_fd' \\\[CWE-775\\\]" } */
|
||||
}
|
||||
}
|
||||
|
||||
void test_3 (const char *path, void *buf)
|
||||
{
|
||||
int old_fd = open (path, O_RDWR);
|
||||
if (old_fd != -1)
|
||||
{
|
||||
int new_fd = dup (old_fd);
|
||||
write (new_fd, buf, 1); /* { dg-warning "'write' on possibly invalid file descriptor 'new_fd'" } */
|
||||
close (new_fd);
|
||||
close(old_fd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test_5 (const char *path, void *buf)
|
||||
{
|
||||
int old_fd = open (path, O_RDWR);
|
||||
if (old_fd != -1)
|
||||
{
|
||||
int new_fd = dup (old_fd);
|
||||
if (new_fd != -1)
|
||||
{
|
||||
write (new_fd, buf, 1);
|
||||
close (new_fd);
|
||||
|
||||
}
|
||||
close(old_fd);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
void test_7 (const char *path)
|
||||
{
|
||||
int old_fd = open (path, O_RDWR);
|
||||
dup2 (old_fd, 4); /* { dg-warning "'dup2' on possibly invalid file descriptor 'old_fd'" } */
|
||||
close(old_fd);
|
||||
}
|
||||
|
||||
void test_8 (const char *path)
|
||||
{
|
||||
int old_fd = open (path, O_RDWR);
|
||||
int new_fd = open (path, O_RDWR);
|
||||
if (old_fd != -1)
|
||||
{
|
||||
dup2 (old_fd, new_fd); /* { dg-warning "'dup2' on possibly invalid file descriptor 'new_fd'" } */
|
||||
close (old_fd);
|
||||
}
|
||||
close (new_fd);
|
||||
}
|
||||
|
||||
void test_9 (const char *path, void *buf)
|
||||
{
|
||||
int old_fd = open (path, O_RDWR);
|
||||
|
||||
if (old_fd != -1)
|
||||
{
|
||||
int new_fd = open (path, O_RDWR);
|
||||
if (new_fd != -1)
|
||||
{
|
||||
int lhs = dup2 (old_fd, new_fd);
|
||||
write (lhs, buf, 1); /* { dg-warning "'write' on possibly invalid file descriptor 'lhs'" } */
|
||||
close(new_fd);
|
||||
close(lhs);
|
||||
}
|
||||
close(old_fd);
|
||||
}
|
||||
}
|
||||
|
||||
void test_10 (const char *path, int flags)
|
||||
{
|
||||
int old_fd = open (path, O_RDWR);
|
||||
int new_fd = open (path, O_RDWR);
|
||||
if (old_fd != -1)
|
||||
{
|
||||
dup3 (old_fd, new_fd, flags); /* { dg-warning "'dup3' on possibly invalid file descriptor 'new_fd'" } */
|
||||
close(old_fd);
|
||||
|
||||
}
|
||||
close(new_fd);
|
||||
}
|
||||
|
||||
void test_11 (const char *path, int flags)
|
||||
{
|
||||
int old_fd = open (path, O_RDWR);
|
||||
int new_fd = open (path, O_RDWR);
|
||||
if (new_fd != -1)
|
||||
{
|
||||
dup3 (old_fd, new_fd, flags); /* { dg-warning "'dup3' on possibly invalid file descriptor 'old_fd'" } */
|
||||
close(new_fd);
|
||||
|
||||
}
|
||||
close(old_fd);
|
||||
}
|
||||
|
||||
void test_12 (const char *path, void *buf)
|
||||
{
|
||||
int old_fd = open (path, O_RDONLY);
|
||||
if (old_fd != -1)
|
||||
{
|
||||
int new_fd = dup (old_fd);
|
||||
if (new_fd != -1)
|
||||
{
|
||||
write (new_fd, buf, 1); /* { dg-warning "'write' on read-only file descriptor 'new_fd'" } */
|
||||
close(new_fd);
|
||||
}
|
||||
close(old_fd);
|
||||
}
|
||||
}
|
||||
|
||||
void test_13 (const char *path, void *buf)
|
||||
{
|
||||
int old_fd = open (path, O_WRONLY);
|
||||
if (old_fd != -1)
|
||||
{
|
||||
int new_fd = dup (old_fd);
|
||||
if (new_fd != -1)
|
||||
{
|
||||
read (new_fd, buf, 1); /* { dg-warning "'read' on write-only file descriptor 'new_fd'" } */
|
||||
close(new_fd);
|
||||
}
|
||||
close(old_fd);
|
||||
}
|
||||
}
|
||||
|
||||
void test_14 (const char *path, void *buf)
|
||||
{
|
||||
int old_fd = open (path, O_RDWR);
|
||||
if (old_fd != -1)
|
||||
{
|
||||
int new_fd = dup (old_fd);
|
||||
if (new_fd != -1)
|
||||
{
|
||||
write (new_fd, buf, 1);
|
||||
read (new_fd, buf, 1);
|
||||
close(new_fd);
|
||||
}
|
||||
close(old_fd);
|
||||
}
|
||||
}
|
||||
|
||||
void test_15 (void *buf)
|
||||
{
|
||||
int fd = dup(0);
|
||||
read (fd, buf, 1); /* { dg-warning "'read' on possibly invalid file descriptor 'fd'" } */
|
||||
close(fd);
|
||||
}
|
||||
|
||||
void test_16 (void *buf)
|
||||
{
|
||||
int fd = dup(1);
|
||||
if (fd != -1)
|
||||
{
|
||||
write (fd, buf, 1);
|
||||
close (fd);
|
||||
}
|
||||
}
|
||||
|
||||
void test_17 (const char *path)
|
||||
{
|
||||
int fd = open (path, O_RDWR);
|
||||
close(fd);
|
||||
dup (fd); /* { dg-warning "'dup' on closed file descriptor 'fd'" } */
|
||||
dup2 (fd, 4); /* { dg-warning "'dup2' on closed file descriptor 'fd'" } */
|
||||
}
|
||||
|
||||
void
|
||||
test_18 (const char *path, void *buf)
|
||||
{
|
||||
int fd = open (path, O_RDWR);
|
||||
if (fd != -1)
|
||||
{
|
||||
int fd2 = dup2 (fd, 3);
|
||||
read (fd2, buf, 1); /* { dg-warning "'read' on possibly invalid file descriptor 'fd2'" } */
|
||||
close(fd);
|
||||
close(fd2);
|
||||
}
|
||||
}
|
||||
|
||||
void
|
||||
test_19 (const char *path, void *buf)
|
||||
{
|
||||
int fd = open (path, O_WRONLY);
|
||||
if (fd != -1)
|
||||
{
|
||||
int fd2 = dup2 (fd, 4);
|
||||
if (fd2 != -1)
|
||||
{
|
||||
read (fd2, buf, 1); /* { dg-warning "'read' on write-only file descriptor 'fd2'" } */
|
||||
close(fd2);
|
||||
}
|
||||
close (fd);
|
||||
}
|
||||
|
||||
}
|
Loading…
Add table
Reference in a new issue