diff options
author | David Malcolm <dmalcolm@redhat.com> | 2022-11-15 13:53:42 -0500 |
---|---|---|
committer | David Malcolm <dmalcolm@redhat.com> | 2022-11-15 13:53:42 -0500 |
commit | 86a90006864840c2e222d46ead551850caba184b (patch) | |
tree | dbd40ec5eead9974edb997d246a79036eed69d31 /gcc/testsuite | |
parent | d8aba860b34203621586df8c5a6756b18c2a0c32 (diff) | |
download | gcc-86a90006864840c2e222d46ead551850caba184b.zip gcc-86a90006864840c2e222d46ead551850caba184b.tar.gz gcc-86a90006864840c2e222d46ead551850caba184b.tar.bz2 |
analyzer: add warnings relating to sockets [PR106140]
This patch generalizes the analyzer's file descriptor state machine
so that it tracks the states of sockets.
It adds two new warnings relating to misuses of socket APIs:
* -Wanalyzer-fd-phase-mismatch (e.g. calling 'accept' on a socket
before calling 'listen' on it)
* -Wanalyzer-fd-type-mismatch (e.g. using a stream socket operation
on a datagram socket)
gcc/analyzer/ChangeLog:
PR analyzer/106140
* analyzer-language.cc (on_finish_translation_unit): Stash named
constants "SOCK_STREAM" and "SOCK_DGRAM".
* analyzer.opt (Wanalyzer-fd-phase-mismatch): New.
(Wanalyzer-fd-type-mismatch): New.
* engine.cc (impl_region_model_context::get_state_map_by_name):
Add "out_sm_context" param. Allow out_sm_idx to be NULL.
* exploded-graph.h
(impl_region_model_context::get_state_map_by_name):
Add "out_sm_context" param.
* region-model-impl-calls.cc (region_model::impl_call_accept): New.
(region_model::impl_call_bind): New.
(region_model::impl_call_connect): New.
(region_model::impl_call_listen): New.
(region_model::impl_call_socket): New.
* region-model.cc (region_model::on_call_pre): Special-case
"bind".
(region_model::on_call_post): Special-case "accept", "bind",
"connect", "listen", and "socket".
* region-model.h (region_model::impl_call_accept): New decl.
(region_model::impl_call_bind): New decl.
(region_model::impl_call_connect): New decl.
(region_model::impl_call_listen): New decl.
(region_model::impl_call_socket): New decl.
(region_model::on_socket): New decl.
(region_model::on_bind): New decl.
(region_model::on_listen): New decl.
(region_model::on_accept): New decl.
(region_model::on_connect): New decl.
(region_model::add_constraint): Make public.
(region_model::check_for_poison): Make public.
(region_model_context::get_state_map_by_name): Add out_sm_context param.
(region_model_context::get_fd_map): Likewise.
(region_model_context::get_malloc_map): Likewise.
(region_model_context::get_taint_map): Likewise.
(noop_region_model_context::get_state_map_by_name): Likewise.
(region_model_context_decorator::get_state_map_by_name): Likewise.
* sm-fd.cc: Include "analyzer/supergraph.h" and
"analyzer/analyzer-language.h".
(enum expected_phase): New enum.
(fd_state_machine::m_new_datagram_socket): New.
(fd_state_machine::m_new_stream_socket): New.
(fd_state_machine::m_new_unknown_socket): New.
(fd_state_machine::m_bound_datagram_socket): New.
(fd_state_machine::m_bound_stream_socket): New.
(fd_state_machine::m_bound_unknown_socket): New.
(fd_state_machine::m_listening_stream_socket): New.
(fd_state_machine::m_m_connected_stream_socket): New.
(fd_state_machine::m_SOCK_STREAM): New.
(fd_state_machine::m_SOCK_DGRAM): New.
(fd_diagnostic::describe_state_change): Handle socket states.
(fd_diagnostic::get_meaning_for_state_change): Likewise.
(class fd_phase_mismatch): New.
(enum expected_type): New enum.
(class fd_type_mismatch): New.
(fd_state_machine::fd_state_machine): Initialize new states and
stashed named constants.
(fd_state_machine::is_socket_fd_p): New.
(fd_state_machine::is_datagram_socket_fd_p): New.
(fd_state_machine::is_stream_socket_fd_p): New.
(fd_state_machine::on_close): Handle the socket states.
(fd_state_machine::check_for_open_fd): Complain about fncalls on
sockets in the wrong phase. Support socket FDs.
(add_constraint_ge_zero): New.
(fd_state_machine::get_state_for_socket_type): New.
(fd_state_machine::on_socket): New.
(fd_state_machine::check_for_socket_fd): New.
(fd_state_machine::check_for_new_socket_fd): New.
(fd_state_machine::on_bind): New.
(fd_state_machine::on_listen): New.
(fd_state_machine::on_accept): New.
(fd_state_machine::on_connect): New.
(fd_state_machine::can_purge_p): Don't purge socket values.
(get_fd_state): New.
(region_model::mark_as_valid_fd): Use get_fd_state.
(region_model::on_socket): New.
(region_model::on_bind): New.
(region_model::on_listen): New.
(region_model::on_accept): New.
(region_model::on_connect): New.
* sm-fd.dot: Update to reflect sm-fd.cc changes.
gcc/ChangeLog:
PR analyzer/106140
* doc/invoke.texi (Static Analyzer Options): Add
-Wanalyzer-fd-phase-mismatch and -Wanalyzer-fd-type-mismatch. Add
"socket", "bind", "listen", "accept", and "connect" to the list of
functions known to the analyzer.
gcc/testsuite/ChangeLog:
PR analyzer/106140
* gcc.dg/analyzer/fd-accept.c: New test.
* gcc.dg/analyzer/fd-bind.c: New test.
* gcc.dg/analyzer/fd-connect.c: New test.
* gcc.dg/analyzer/fd-datagram-socket.c: New test.
* gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c: New test.
* gcc.dg/analyzer/fd-glibc-byte-stream-socket.c: New test.
* gcc.dg/analyzer/fd-glibc-datagram-client.c: New test.
* gcc.dg/analyzer/fd-glibc-datagram-socket.c: New test.
* gcc.dg/analyzer/fd-glibc-make_named_socket.h: New test.
* gcc.dg/analyzer/fd-listen.c: New test.
* gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c: New test.
* gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c: New test.
* gcc.dg/analyzer/fd-socket-meaning.c: New test.
* gcc.dg/analyzer/fd-socket-misuse.c: New test.
* gcc.dg/analyzer/fd-stream-socket-active-open.c: New test.
* gcc.dg/analyzer/fd-stream-socket-passive-open.c: New test.
* gcc.dg/analyzer/fd-stream-socket.c: New test.
* gcc.dg/analyzer/fd-symbolic-socket.c: New test.
* gcc.dg/analyzer/pr104369-1.c: Add -Wno-analyzer-too-complex and
-Wno-analyzer-fd-leak to options.
* gcc.dg/analyzer/pr104369-2.c: Add -Wno-analyzer-fd-leak to
options.
Signed-off-by: David Malcolm <dmalcolm@redhat.com>
Diffstat (limited to 'gcc/testsuite')
20 files changed, 1542 insertions, 2 deletions
diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-accept.c b/gcc/testsuite/gcc.dg/analyzer/fd-accept.c new file mode 100644 index 0000000..36cc7af --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-accept.c @@ -0,0 +1,69 @@ +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> +#include "analyzer-decls.h" + +int test_accept (int fd, struct sockaddr *addr, socklen_t *addrlen) +{ + return accept (fd, addr, addrlen); +} + +void test_accept_leak_no_lhs (int fd, struct sockaddr *addr, socklen_t *addrlen) +{ + accept (fd, addr, addrlen); /* { dg-warning "leak of file descriptor" } */ +} + +void test_accept_leak_with_lhs (int fd, struct sockaddr *addr, socklen_t *addrlen) +{ + int newfd = accept (fd, addr, addrlen); /* { dg-message "socket created here" } */ +} /* { dg-warning "leak of file descriptor 'newfd'" } */ + +int test_accept_null_addr (int fd) +{ + return accept (fd, NULL, 0); +} + +int test_accept_uninit_addrlen (int fd) +{ + struct sockaddr_storage addr; + socklen_t addr_len; + return accept (fd, (struct sockaddr *)&addr, &addr_len); /* { dg-warning "use of uninitialized value 'addr_len'" } */ +} + +int test_accept_writes_to_addr_and_len (int fd) +{ + struct sockaddr_storage addr; + socklen_t addr_len = sizeof (addr); + __analyzer_eval (addr_len == sizeof (addr)); /* { dg-warning "TRUE" } */ + int newfd = accept (fd, (struct sockaddr *)&addr, &addr_len); + if (newfd == -1) + return newfd; + /* Check that the analyzer considers addr and addr_len to + have been written to. */ + __analyzer_eval (((char *)&addr)[0]); /* { dg-warning "UNKNOWN" } */ + __analyzer_eval (addr_len == sizeof (addr)); /* { dg-warning "UNKNOWN" } */ + return newfd; +} + +void test_accept_on_new_datagram_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_DGRAM, 0); + if (fd == -1) + return; + accept (fd, NULL, NULL); /* { dg-message "'accept' on datagram socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */ + /* { dg-message "'accept' expects a stream socket file descriptor but 'fd' is a datagram socket" "final event" { target *-*-* } .-1 } */ + close (fd); +} + +int test_accept_on_accept (int fd_a) +{ + int fd_b = accept (fd_a, NULL, 0); + if (fd_b == -1) + return -1; + + int fd_c = accept (fd_b, NULL, 0); /* { dg-warning "'accept' on file descriptor 'fd_b' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */ + /* { dg-message "'accept' expects a listening stream socket file descriptor but 'fd_b' is connected" "final event" { target *-*-* } .-1 } */ + + return fd_b; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-bind.c b/gcc/testsuite/gcc.dg/analyzer/fd-bind.c new file mode 100644 index 0000000..6f91bc4 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-bind.c @@ -0,0 +1,74 @@ +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> +#include "analyzer-decls.h" + +void test_bind (int fd, const char *sockname) +{ + struct sockaddr_un addr; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1) + __analyzer_dump_path (); /* { dg-message "path" } */ + else + __analyzer_dump_path (); /* { dg-message "path" } */ +} + +void test_null_bind (int fd) +{ + errno = 0; + int result = bind (fd, NULL, 0); + __analyzer_eval (result == -1); /* { dg-warning "TRUE" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ +} + +void test_double_bind (int fd, const char *sockname) +{ + struct sockaddr_un addr; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'fd' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */ + /* { dg-message "'bind' expects a new socket file descriptor but 'fd' has already been bound" "final event" { target *-*-* } .-1 } */ +} + +int test_uninit_addr (int fd, const char *sockname) +{ + struct sockaddr_un addr; + return bind (fd, (struct sockaddr *)&addr, sizeof (addr)); + // TODO: complain about uninit addr. +} + +void test_bind_after_connect (int fd, const char *sockname, + const struct sockaddr *caddr, socklen_t caddrlen) +{ + if (connect (fd, caddr, caddrlen) == -1) + return; + + struct sockaddr_un addr; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); + /* TODO: we don't warn for this; after the plain "connect" we're + in the stop state. */ +} + +void test_bind_after_accept (int fd, const char *sockname) +{ + int afd = accept (fd, NULL, NULL); + if (afd == -1) + return; + + struct sockaddr_un addr; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (afd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'afd' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */ + /* { dg-message "'bind' expects a new socket file descriptor but 'afd' is already connected" "final event" { target *-*-* } .-1 } */ + + close (afd); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-connect.c b/gcc/testsuite/gcc.dg/analyzer/fd-connect.c new file mode 100644 index 0000000..1ab54d0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-connect.c @@ -0,0 +1,46 @@ +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> +#include "analyzer-decls.h" + +int test_connect (int sockfd, const struct sockaddr *addr, + socklen_t addrlen) +{ + return connect (sockfd, addr, addrlen); +} + +void test_null_connect (int fd) +{ + errno = 0; + int result = connect (fd, NULL, 0); + __analyzer_eval (result == -1); /* { dg-warning "TRUE" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ +} + +int test_uninit_addr (int fd, const char *sockname) +{ + struct sockaddr_un addr; + return connect (fd, (struct sockaddr *)&addr, sizeof (addr)); + // TODO: complain about uninit addr. +} + +void test_connect_after_bind (const char *sockname, + const struct sockaddr *baddr, socklen_t baddrlen, + const struct sockaddr *caddr, socklen_t caddrlen) +{ + int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */ + if (fd == -1) + return; + + if (bind (fd, baddr, baddrlen) == -1) + { + close (fd); + return; + } + + connect (fd, caddr, caddrlen); /* { dg-warning "'connect' on file descriptor 'fd' in wrong phase" "warning" } */ + /* { dg-message "'connect' expects a new socket file descriptor but 'fd' is bound" "final event" { target *-*-* } .-1 } */ + + close (fd); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c new file mode 100644 index 0000000..045bdfa --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-datagram-socket.c @@ -0,0 +1,108 @@ +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> +#include "analyzer-decls.h" + +void test_leak_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */ +} /* { dg-warning "leak of file descriptor 'fd'" } */ + +void test_leak_socket_no_lhs (void) +{ + socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-warning "leak of file descriptor" } */ +} + +void test_close_unchecked_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_DGRAM, 0); + close (fd); +} + +void test_close_checked_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_DGRAM, 0); + if (fd == -1) + return; + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-datagram-socket'" } */ + close (fd); +} + +void test_leak_checked_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */ + if (fd == -1) /* { dg-warning "leak of file descriptor 'fd'" } */ + return; + // TODO: strange location for leak message +} + +void test_bind (const char *sockname) +{ + struct sockaddr_un addr; + int fd = socket (AF_UNIX, SOCK_DGRAM, 0); + if (fd == -1) + return; + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-datagram-socket'" } */ + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); + close (fd); +} + +void test_bind_on_unchecked_socket (const char *sockname) +{ + struct sockaddr_un addr; + int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "when 'socket' fails" } */ + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on possibly invalid file descriptor 'fd'" } */ + close (fd); +} + +void test_leak_of_bound_socket (const char *sockname) +{ + struct sockaddr_un addr; + int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */ + if (fd == -1) + return; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "leak of file descriptor 'fd'" } */ +} + +void test_listen_on_datagram_socket_without_bind (void) +{ + int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */ + if (fd == -1) + return; + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-datagram-socket'" } */ + listen (fd, 5); /* { dg-warning "'listen' on datagram socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */ + /* { dg-message "'listen' expects a stream socket file descriptor but 'fd' is a datagram socket" "final event" { target *-*-* } .-1 } */ + close (fd); +} + +void test_listen_on_datagram_socket_with_bind (const char *sockname) +{ + int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "datagram socket created here" } */ + if (fd == -1) + return; + + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-datagram-socket'" } */ + + struct sockaddr_un addr; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1) /* { dg message "datagram socket bound here" } */ + { + close (fd); + return; + } + listen (fd, 5); /* { dg-warning "'listen' on datagram socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */ + /* { dg-message "'listen' expects a stream socket file descriptor but 'fd' is a datagram socket" "final event" { target *-*-* } .-1 } */ + close (fd); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c new file mode 100644 index 0000000..1ff9028 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-connection-server.c @@ -0,0 +1,133 @@ +/* Example from glibc manual (16.9.7). */ +/* { dg-additional-options "-Wno-analyzer-too-complex" } */ + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <netdb.h> + +#define PORT 5555 +#define MAXMSG 512 + +int +make_socket (uint16_t port) +{ + int sock; + struct sockaddr_in name; + + /* Create the socket. */ + sock = socket (PF_INET, SOCK_STREAM, 0); + if (sock < 0) + { + perror ("socket"); + exit (EXIT_FAILURE); + } + + /* Give the socket a name. */ + name.sin_family = AF_INET; + name.sin_port = htons (port); + name.sin_addr.s_addr = htonl (INADDR_ANY); + if (bind (sock, (struct sockaddr *) &name, sizeof (name)) < 0) + { + perror ("bind"); + exit (EXIT_FAILURE); + } + + return sock; +} + +int +read_from_client (int filedes) +{ + char buffer[MAXMSG]; + int nbytes; + + nbytes = read (filedes, buffer, MAXMSG); + if (nbytes < 0) + { + /* Read error. */ + perror ("read"); + exit (EXIT_FAILURE); + } + else if (nbytes == 0) + /* End-of-file. */ + return -1; + else + { + /* Data read. */ + fprintf (stderr, "Server: got message: `%s'\n", buffer); + return 0; + } +} + +int +main (void) +{ + int sock; + fd_set active_fd_set, read_fd_set; + int i; + struct sockaddr_in clientname; + socklen_t size; + + /* Create the socket and set it up to accept connections. */ + sock = make_socket (PORT); + if (listen (sock, 1) < 0) + { + perror ("listen"); + exit (EXIT_FAILURE); + } + + /* Initialize the set of active sockets. */ + FD_ZERO (&active_fd_set); + FD_SET (sock, &active_fd_set); + + while (1) + { + /* Block until input arrives on one or more active sockets. */ + read_fd_set = active_fd_set; + if (select (FD_SETSIZE, &read_fd_set, NULL, NULL, NULL) < 0) + { + perror ("select"); + exit (EXIT_FAILURE); + } + + /* Service all the sockets with input pending. */ + for (i = 0; i < FD_SETSIZE; ++i) + if (FD_ISSET (i, &read_fd_set)) + { + if (i == sock) + { + /* Connection request on original socket. */ + int new; + size = sizeof (clientname); + new = accept (sock, + (struct sockaddr *) &clientname, + &size); + if (new < 0) + { + perror ("accept"); + exit (EXIT_FAILURE); + } + fprintf (stderr, + "Server: connect from host %s, port %hd.\n", + inet_ntoa (clientname.sin_addr), + ntohs (clientname.sin_port)); + FD_SET (new, &active_fd_set); + } + else + { + /* Data arriving on an already-connected socket. */ + if (read_from_client (i) < 0) + { + close (i); + FD_CLR (i, &active_fd_set); + } + } + } + } +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c new file mode 100644 index 0000000..f96da81 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-byte-stream-socket.c @@ -0,0 +1,62 @@ +/* Example from glibc manual (16.9.6). */ + +#include <stdio.h> +#include <string.h> +#include <errno.h> +#include <stdlib.h> +#include <unistd.h> +#include <sys/types.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <netdb.h> + +#define PORT 5555 +#define MESSAGE "Yow!!! Are we having fun yet?!?" +#define SERVERHOST "www.gnu.org" + +void +write_to_server (int filedes) +{ + int nbytes; + + nbytes = write (filedes, MESSAGE, strlen (MESSAGE) + 1); + if (nbytes < 0) + { + perror ("write"); + exit (EXIT_FAILURE); + } +} + + +int +main (void) +{ + extern void init_sockaddr (struct sockaddr_in *name, + const char *hostname, + uint16_t port); + int sock; + struct sockaddr_in servername; + + /* Create the socket. */ + sock = socket (PF_INET, SOCK_STREAM, 0); + if (sock < 0) + { + perror ("socket (client)"); + exit (EXIT_FAILURE); + } + + /* Connect to the server. */ + init_sockaddr (&servername, SERVERHOST, PORT); + if (0 > connect (sock, + (struct sockaddr *) &servername, + sizeof (servername))) + { + perror ("connect (client)"); + exit (EXIT_FAILURE); + } + + /* Send data to the server. */ + write_to_server (sock); + close (sock); + exit (EXIT_SUCCESS); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c new file mode 100644 index 0000000..888c751 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-client.c @@ -0,0 +1,56 @@ +/* Example from the glibc manual (16.10.4). */ + +#include <stdio.h> +#include <errno.h> +#include <unistd.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/un.h> +#include "fd-glibc-make_named_socket.h" + +#define SERVER "/tmp/serversocket" +#define CLIENT "/tmp/mysocket" +#define MAXMSG 512 +#define MESSAGE "Yow!!! Are we having fun yet?!?" + +int +main (void) +{ + int sock; + char message[MAXMSG]; + struct sockaddr_un name; + size_t size; + int nbytes; + + /* Make the socket. */ + sock = make_named_socket (CLIENT); + + /* Initialize the server socket address. */ + name.sun_family = AF_LOCAL; + strcpy (name.sun_path, SERVER); + size = strlen (name.sun_path) + sizeof (name.sun_family); + + /* Send the datagram. */ + nbytes = sendto (sock, MESSAGE, strlen (MESSAGE) + 1, 0, + (struct sockaddr *) & name, size); + if (nbytes < 0) + { + perror ("sendto (client)"); + exit (EXIT_FAILURE); + } + + /* Wait for a reply. */ + nbytes = recvfrom (sock, message, MAXMSG, 0, NULL, 0); + if (nbytes < 0) + { + perror ("recfrom (client)"); + exit (EXIT_FAILURE); + } + + /* Print a diagnostic message. */ + fprintf (stderr, "Client: got message: %s\n", message); + + /* Clean up. */ + remove (CLIENT); + close (sock); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c new file mode 100644 index 0000000..b8b6876 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-datagram-socket.c @@ -0,0 +1,52 @@ +/* Example from glibc manual (16.10.3). */ + +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include "fd-glibc-make_named_socket.h" + +#define SERVER "/tmp/serversocket" +#define MAXMSG 512 + +int +main (void) +{ + int sock; + char message[MAXMSG]; + struct sockaddr_un name; + socklen_t size; + int nbytes; + + /* Remove the filename first, it’s ok if the call fails */ + unlink (SERVER); + + /* Make the socket, then loop endlessly. */ + sock = make_named_socket (SERVER); + while (1) + { + /* Wait for a datagram. */ + size = sizeof (name); + nbytes = recvfrom (sock, message, MAXMSG, 0, + (struct sockaddr *) & name, &size); + if (nbytes < 0) + { + perror ("recfrom (server)"); + exit (EXIT_FAILURE); + } + + /* Give a diagnostic message. */ + fprintf (stderr, "Server: got message: %s\n", message); + + /* Bounce the message back to the sender. */ + nbytes = sendto (sock, message, nbytes, 0, + (struct sockaddr *) & name, size); + if (nbytes < 0) + { + perror ("sendto (server)"); + exit (EXIT_FAILURE); + } + } +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h new file mode 100644 index 0000000..bdb6de0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-glibc-make_named_socket.h @@ -0,0 +1,47 @@ +/* Example of Local-Namespace Sockets from the glibc manual (16.5.3). */ + +#include <stddef.h> +#include <stdio.h> +#include <errno.h> +#include <stdlib.h> +#include <string.h> +#include <sys/socket.h> +#include <sys/un.h> + +int +make_named_socket (const char *filename) +{ + struct sockaddr_un name; + int sock; + size_t size; + + /* Create the socket. */ + sock = socket (PF_LOCAL, SOCK_DGRAM, 0); + if (sock < 0) + { + perror ("socket"); + exit (EXIT_FAILURE); + } + + /* Bind a name to the socket. */ + name.sun_family = AF_LOCAL; + strncpy (name.sun_path, filename, sizeof (name.sun_path)); + name.sun_path[sizeof (name.sun_path) - 1] = '\0'; + + /* The size of the address is + the offset of the start of the filename, + plus its length (not including the terminating null byte). + Alternatively you can just do: + size = SUN_LEN (&name); + */ + size = (offsetof (struct sockaddr_un, sun_path) + + strlen (name.sun_path)); + + if (bind (sock, (struct sockaddr *) &name, size) < 0) + { + perror ("bind"); + exit (EXIT_FAILURE); + } + + return sock; +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-listen.c b/gcc/testsuite/gcc.dg/analyzer/fd-listen.c new file mode 100644 index 0000000..1f54a8f --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-listen.c @@ -0,0 +1,63 @@ +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> +#include "analyzer-decls.h" + +int test_listen (int fd, int backlog) +{ + return listen (fd, backlog); +} + +/* Some systems seem to allow repeated calls to listen. */ + +void test_double_listen (int fd, int backlog) +{ + listen (fd, backlog); + listen (fd, backlog); +} + +void test_listen_before_bind (int fd, const char *sockname) +{ + if (listen (fd, 5) == -1) /* { dg-message "stream socket marked as passive here via 'listen'" } */ + return; + + struct sockaddr_un addr; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'fd' in wrong phase" "warning" } */ + /* { dg-message "'bind' expects a new socket file descriptor but 'fd' is already listening" "final event" { target *-*-* } .-1 } */ +} + +void test_listen_on_unchecked_bind (int fd, const char *sockname) +{ + struct sockaddr_un addr; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-message "when 'bind' fails" } */ + listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" "warning" } */ + /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */ + close (fd); +} + +void test_listen_on_new_datagram_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_DGRAM, 0); + if (fd == -1) + return; + listen (fd, 5); /* { dg-message "'listen' on datagram socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */ + /* { dg-message "'listen' expects a stream socket file descriptor but 'fd' is a datagram socket" "final event" { target *-*-* } .-1 } */ + close (fd); +} + +void test_listed_on_connected_socket (int fd) +{ + int afd = accept (fd, NULL, 0); + if (afd == -1) + return; + listen (afd, 5); /* { dg-warning "'listen' on file descriptor 'afd' in wrong phase" "warning" } */ + /* { dg-message "'listen' expects a bound stream socket file descriptor but 'afd' is connected" "final event" { target *-*-* } .-1 } */ + close (afd); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c b/gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c new file mode 100644 index 0000000..d9c3ff0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-manpage-getaddrinfo-client.c @@ -0,0 +1,122 @@ +/* Example from getaddrinfo.3 manpage, which has this license: + +Copyright (c) 2007, 2008 Michael Kerrisk <mtk.manpages@gmail.com> +and Copyright (c) 2006 Ulrich Drepper <drepper@redhat.com> +A few pieces of an earlier version remain: +Copyright 2000, Sam Varshavchik <mrsam@courier-mta.com> + +Permission is granted to make and distribute verbatim copies of this +manual provided the copyright notice and this permission notice are +preserved on all copies. + +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one. + +Since the Linux kernel and libraries are constantly changing, this +manual page may be incorrect or out-of-date. The author(s) assume no +responsibility for errors or omissions, or for damages resulting from +the use of the information contained herein. The author(s) may not +have taken the same level of care in the production of this manual, +which is licensed free of charge, as they might when working +professionally. + +Formatted or processed versions of this manual, if unaccompanied by +the source, must acknowledge the copyright and authors of this work. +*/ + +/* { dg-additional-options "-Wno-analyzer-too-complex" } */ + +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> + +#define BUF_SIZE 500 + +int +main(int argc, char *argv[]) +{ + struct addrinfo hints; + struct addrinfo *result, *rp; + int sfd, s; + size_t len; + ssize_t nread; + char buf[BUF_SIZE]; + + if (argc < 3) { + fprintf(stderr, "Usage: %s host port msg...\n", argv[0]); + exit(EXIT_FAILURE); + } + + /* Obtain address(es) matching host/port. */ + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ + hints.ai_flags = 0; + hints.ai_protocol = 0; /* Any protocol */ + + s = getaddrinfo(argv[1], argv[2], &hints, &result); + if (s != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); + exit(EXIT_FAILURE); + } + + /* getaddrinfo() returns a list of address structures. + Try each address until we successfully connect(2). + If socket(2) (or connect(2)) fails, we (close the socket + and) try the next address. */ + + for (rp = result; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (sfd == -1) + continue; + + if (connect(sfd, rp->ai_addr, rp->ai_addrlen) != -1) + break; /* Success */ + + close(sfd); + } + + freeaddrinfo(result); /* No longer needed */ + + if (rp == NULL) { /* No address succeeded */ + fprintf(stderr, "Could not connect\n"); + exit(EXIT_FAILURE); + } + + /* Send remaining command-line arguments as separate + datagrams, and read responses from server. */ + + for (int j = 3; j < argc; j++) { + len = strlen(argv[j]) + 1; + /* +1 for terminating null byte */ + + if (len > BUF_SIZE) { + fprintf(stderr, + "Ignoring long message in argument %d\n", j); + continue; + } + + if (write(sfd, argv[j], len) != len) { + fprintf(stderr, "partial/failed write\n"); + exit(EXIT_FAILURE); + } + + nread = read(sfd, buf, BUF_SIZE); + if (nread == -1) { + perror("read"); + exit(EXIT_FAILURE); + } + + printf("Received %zd bytes: %s\n", nread, buf); + } + + exit(EXIT_SUCCESS); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c b/gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c new file mode 100644 index 0000000..66398e8 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-mappage-getaddrinfo-server.c @@ -0,0 +1,119 @@ +/* Example from getaddrinfo.3 manpage, which has this license: + +Copyright (c) 2007, 2008 Michael Kerrisk <mtk.manpages@gmail.com> +and Copyright (c) 2006 Ulrich Drepper <drepper@redhat.com> +A few pieces of an earlier version remain: +Copyright 2000, Sam Varshavchik <mrsam@courier-mta.com> + +Permission is granted to make and distribute verbatim copies of this +manual provided the copyright notice and this permission notice are +preserved on all copies. + +Permission is granted to copy and distribute modified versions of this +manual under the conditions for verbatim copying, provided that the +entire resulting derived work is distributed under the terms of a +permission notice identical to this one. + +Since the Linux kernel and libraries are constantly changing, this +manual page may be incorrect or out-of-date. The author(s) assume no +responsibility for errors or omissions, or for damages resulting from +the use of the information contained herein. The author(s) may not +have taken the same level of care in the production of this manual, +which is licensed free of charge, as they might when working +professionally. + +Formatted or processed versions of this manual, if unaccompanied by +the source, must acknowledge the copyright and authors of this work. +*/ + +#include <sys/types.h> +#include <stdio.h> +#include <stdlib.h> +#include <unistd.h> +#include <string.h> +#include <sys/socket.h> +#include <netdb.h> + +#define BUF_SIZE 500 + +int +main(int argc, char *argv[]) +{ + struct addrinfo hints; + struct addrinfo *result, *rp; + int sfd, s; + struct sockaddr_storage peer_addr; + socklen_t peer_addr_len; + ssize_t nread; + char buf[BUF_SIZE]; + + if (argc != 2) { + fprintf(stderr, "Usage: %s port\n", argv[0]); + exit(EXIT_FAILURE); + } + + memset(&hints, 0, sizeof(hints)); + hints.ai_family = AF_UNSPEC; /* Allow IPv4 or IPv6 */ + hints.ai_socktype = SOCK_DGRAM; /* Datagram socket */ + hints.ai_flags = AI_PASSIVE; /* For wildcard IP address */ + hints.ai_protocol = 0; /* Any protocol */ + hints.ai_canonname = NULL; + hints.ai_addr = NULL; + hints.ai_next = NULL; + + s = getaddrinfo(NULL, argv[1], &hints, &result); + if (s != 0) { + fprintf(stderr, "getaddrinfo: %s\n", gai_strerror(s)); + exit(EXIT_FAILURE); + } + + /* getaddrinfo() returns a list of address structures. + Try each address until we successfully bind(2). + If socket(2) (or bind(2)) fails, we (close the socket + and) try the next address. */ + + for (rp = result; rp != NULL; rp = rp->ai_next) { + sfd = socket(rp->ai_family, rp->ai_socktype, + rp->ai_protocol); + if (sfd == -1) + continue; + + if (bind(sfd, rp->ai_addr, rp->ai_addrlen) == 0) + break; /* Success */ + + close(sfd); + } + + freeaddrinfo(result); /* No longer needed */ + + if (rp == NULL) { /* No address succeeded */ + fprintf(stderr, "Could not bind\n"); + exit(EXIT_FAILURE); + } + + /* Read datagrams and echo them back to sender. */ + + for (;;) { + peer_addr_len = sizeof(peer_addr); + nread = recvfrom(sfd, buf, BUF_SIZE, 0, + (struct sockaddr *) &peer_addr, &peer_addr_len); + if (nread == -1) + continue; /* Ignore failed request */ + + char host[NI_MAXHOST], service[NI_MAXSERV]; + + s = getnameinfo((struct sockaddr *) &peer_addr, + peer_addr_len, host, NI_MAXHOST, + service, NI_MAXSERV, NI_NUMERICSERV); + if (s == 0) + printf("Received %zd bytes from %s:%s\n", + nread, host, service); + else + fprintf(stderr, "getnameinfo: %s\n", gai_strerror(s)); + + if (sendto(sfd, buf, nread, 0, + (struct sockaddr *) &peer_addr, + peer_addr_len) != nread) + fprintf(stderr, "Error sending response\n"); + } +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c b/gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c new file mode 100644 index 0000000..5bfb57f --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-socket-meaning.c @@ -0,0 +1,21 @@ +/* { dg-additional-options "-fanalyzer-verbose-state-changes" } */ + +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> + +void test_leak_unchecked_stream_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "meaning: \\{verb: 'acquire', noun: 'resource'\\}" } */ +} /* { dg-warning "leak of file descriptor 'fd'" } */ + +void test_leak_unchecked_datagram_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_DGRAM, 0); /* { dg-message "meaning: \\{verb: 'acquire', noun: 'resource'\\}" } */ +} /* { dg-warning "leak of file descriptor 'fd'" } */ + +void test_leak_unchecked_socket (int type) +{ + int fd = socket (AF_UNIX, type, 0); /* { dg-message "meaning: \\{verb: 'acquire', noun: 'resource'\\}" } */ +} /* { dg-warning "leak of file descriptor 'fd'" } */ diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c b/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c new file mode 100644 index 0000000..4ff08d5 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-socket-misuse.c @@ -0,0 +1,98 @@ +/* Various operations done on sockets in the wrong phase. */ + +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> +#include "analyzer-decls.h" + +void test_read_on_new_socket (void *buf) +{ + int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */ + if (fd == -1) + return; + read (fd, buf, 1); /* { dg-warning "'read' on file descriptor 'fd' in wrong phase \\\[-Wanalyzer-fd-phase-mismatch\\\]" "warning" } */ + /* { dg-message "'read' expects a stream socket to be connected via 'accept' but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */ + close (fd); +} + +void test_read_on_bound_socket (int fd, const char *sockname, void *buf) +{ + struct sockaddr_un addr; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1) + return; + /* This could be a datagram socket, so we shouldn't complain here. */ + read (fd, buf, 1); +} + +void test_read_on_listening_socket (int fd, void *buf) +{ + if (listen (fd, 5) == -1) /* { dg-message "stream socket marked as passive here via 'listen'" } */ + return; + read (fd, buf, 1); /* { dg-message "'read' on file descriptor 'fd' in wrong phase" "warning" } */ + /* { dg-message "'read' expects a stream socket to be connected via the return value of 'accept' but 'fd' is listening; wrong file descriptor\\\?" "final event" { target *-*-* } .-1 } */ +} + +void test_bind_on_non_socket (const char *filename, const char *sockname) +{ + int fd = open (filename, O_RDONLY); + if (fd == -1) + return; + + struct sockaddr_un addr; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + int result = bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on non-socket file descriptor 'fd' \\\[-Wanalyzer-fd-type-mismatch\\\]" "warning" } */ + /* { dg-message "'bind' expects a socket file descriptor but 'fd' is not a socket" "final event" { target *-*-* } .-1 } */ + __analyzer_eval (result == -1); /* { dg-warning "TRUE" } */ + + close (fd); +} + +void test_passive_open_read_on_wrong_socket (int sfd) +{ + int cfd = accept (sfd, NULL, NULL); + write (sfd, "hello", 6); /* { dg-warning "'write' on file descriptor 'sfd' in wrong phase" "warning" } */ + /* { dg-message "'write' expects a stream socket to be connected via the return value of 'accept' but 'sfd' is listening; wrong file descriptor\\\?" "final event" { target *-*-* } .-1 } */ + close (cfd); +} + +void test_listen_on_new_stream_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + return; + listen (fd, 5); /* { dg-message "'listen' on file descriptor 'fd' in wrong phase" "warning" } */ + /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */ + close (fd); +} + +void test_accept_on_new_stream_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + return; + accept (fd, NULL, NULL); /* { dg-message "'accept' on file descriptor 'fd' in wrong phase" "warning" } */ + /* { dg-message "'accept' expects a listening stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */ + close (fd); +} + +void test_listen_before_bind (int fd, const char *sockname) +{ + if (listen (fd, 5) == -1) /* { dg-message "stream socket marked as passive here via 'listen'" } */ + return; + + struct sockaddr_un addr; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on file descriptor 'fd' in wrong phase" "warning" } */ + /* { dg-message "'bind' expects a new socket file descriptor but 'fd' is already listening" "final event" { target *-*-* } .-1 } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c new file mode 100644 index 0000000..7fde0ef --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-active-open.c @@ -0,0 +1,74 @@ +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> +#include "analyzer-decls.h" + +void test_active_open_from_scratch (const char *sockname, void *buf) +{ + errno = 0; + int fd = socket (AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-invalid'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + return; + } + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-stream-socket'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + + struct sockaddr_un addr; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + + errno = 0; + if (connect (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-stream-socket'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ + return; + } + + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */ + + write (fd, "hello", 6); + read (fd, buf, 100); + + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ +} + +void test_active_open_from_connect (int fd, const char *sockname, void *buf) +{ + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'start'" } */ + + struct sockaddr_un addr; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + + errno = 0; + if (connect (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-unknown-socket'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ + return; + } + + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-stop'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */ + + write (fd, "hello", 6); + read (fd, buf, 100); + + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-stop'" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c new file mode 100644 index 0000000..c31e5b5 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket-passive-open.c @@ -0,0 +1,197 @@ +/* Verify the various states when performing a passive open, + either from scratch, or when various phases are assumed to already + be done. */ + +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> +#include "analyzer-decls.h" + +void test_passive_open_from_scratch (const char *sockname, void *buf) +{ + struct sockaddr_un addr; + int afd; + errno = 0; + int fd = socket (AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-invalid'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + return; + } + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-stream-socket'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + errno = 0; + if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-stream-socket'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ + return; + } + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-stream-socket'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */ + if (listen (fd, 5) == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-stream-socket'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ + return; + } + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */ + afd = accept (fd, NULL, NULL); + if (afd == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ + return; + } + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */ + __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (afd >= 0); /* { dg-warning "TRUE" } */ + + write (afd, "hello", 6); + read (afd, buf, 100); + + close (afd); + close (fd); + __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-closed'" } */ + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ +} + +void test_passive_open_from_bind (int fd, const char *sockname, void *buf) +{ + struct sockaddr_un addr; + int afd; + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'start'" } */ + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + errno = 0; + if (bind (fd, (struct sockaddr *)&addr, sizeof (addr)) == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-new-unknown-socket'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ + return; + } + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-unknown-socket'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */ + if (listen (fd, 5) == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-unknown-socket'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ + return; + } + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */ + afd = accept (fd, NULL, NULL); + if (afd == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ + return; + } + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */ + __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (afd >= 0); /* { dg-warning "TRUE" } */ + + write (afd, "hello", 6); + read (afd, buf, 100); + + close (afd); + close (fd); + __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-closed'" } */ + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ +} + +void test_passive_open_from_listen (int fd, void *buf) +{ + int afd; + errno = 0; + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'start'" } */ + if (listen (fd, 5) == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-bound-stream-socket'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ + return; + } + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */ + afd = accept (fd, NULL, NULL); + if (afd == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ + return; + } + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */ + __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (afd >= 0); /* { dg-warning "TRUE" } */ + + write (afd, "hello", 6); + read (afd, buf, 100); + + close (afd); + close (fd); + __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-closed'" } */ + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ +} + +void test_passive_open_from_accept (int fd, void *buf) +{ + int afd; + errno = 0; + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'start'" } */ + afd = accept (fd, NULL, NULL); + if (afd == -1) + { + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */ + __analyzer_eval (errno > 0); /* { dg-warning "TRUE" } */ + close (fd); + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ + return; + } + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-listening-stream-socket'" } */ + __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-connected-stream-socket'" } */ + __analyzer_eval (errno == 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (fd >= 0); /* { dg-warning "TRUE" } */ + __analyzer_eval (afd >= 0); /* { dg-warning "TRUE" } */ + + write (afd, "hello", 6); + read (afd, buf, 100); + + close (afd); + close (fd); + __analyzer_dump_state ("file-descriptor", afd); /* { dg-warning "state: 'fd-closed'" } */ + __analyzer_dump_state ("file-descriptor", fd); /* { dg-warning "state: 'fd-closed'" } */ +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c new file mode 100644 index 0000000..3a292d0 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-stream-socket.c @@ -0,0 +1,98 @@ +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> +#include "analyzer-decls.h" + +void test_leak_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */ +} /* { dg-warning "leak of file descriptor 'fd'" } */ + +void test_leak_socket_no_lhs (void) +{ + socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-warning "leak of file descriptor" } */ +} + +void test_close_unchecked_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_STREAM, 0); + close (fd); +} + +void test_close_checked_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + return; + close (fd); +} + +void test_leak_checked_socket (void) +{ + int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */ + if (fd == -1) /* { dg-warning "leak of file descriptor 'fd'" } */ + return; + // TODO: strange location for leak message +} + +void test_bind_on_checked_socket (const char *sockname) +{ + struct sockaddr_un addr; + int fd = socket (AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + return; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); + close (fd); +} + +void test_bind_on_unchecked_socket (const char *sockname) +{ + struct sockaddr_un addr; + int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "when 'socket' fails" } */ + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on possibly invalid file descriptor 'fd'" } */ + close (fd); +} + +void test_leak_of_bound_socket (const char *sockname) +{ + struct sockaddr_un addr; + int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */ + if (fd == -1) + return; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "leak of file descriptor 'fd'" } */ +} + +void test_listen_without_bind (void) +{ + int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */ + if (fd == -1) + return; + listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" "warning" } */ + /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */ + close (fd); +} + +void test_listen_on_unchecked_bind (const char *sockname) +{ + struct sockaddr_un addr; + int fd = socket (AF_UNIX, SOCK_STREAM, 0); /* { dg-message "stream socket created here" } */ + if (fd == -1) + return; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-message "when 'bind' fails" } */ + listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" "warning" } */ + /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "final event" { target *-*-* } .-1 } */ + close (fd); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c b/gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c new file mode 100644 index 0000000..83400c1 --- /dev/null +++ b/gcc/testsuite/gcc.dg/analyzer/fd-symbolic-socket.c @@ -0,0 +1,98 @@ +#include <sys/socket.h> +#include <sys/un.h> +#include <unistd.h> +#include <errno.h> +#include "analyzer-decls.h" + +void test_leak_socket (int type) +{ + int fd = socket (AF_UNIX, type, 0); /* { dg-message "socket created here" } */ +} /* { dg-warning "leak of file descriptor 'fd'" } */ + +void test_leak_socket_no_lhs (int type) +{ + socket (AF_UNIX, type, 0); /* { dg-warning "leak of file descriptor" } */ +} + +void test_close_unchecked_socket (int type) +{ + int fd = socket (AF_UNIX, type, 0); + close (fd); +} + +void test_close_checked_socket (int type) +{ + int fd = socket (AF_UNIX, type, 0); + if (fd == -1) + return; + close (fd); +} + +void test_leak_checked_socket (int type) +{ + int fd = socket (AF_UNIX, type, 0); /* { dg-message "socket created here" } */ + if (fd == -1) /* { dg-warning "leak of file descriptor 'fd'" } */ + return; + // TODO: strange location for leak message +} + +void test_bind_on_checked_socket (int type, const char *sockname) +{ + struct sockaddr_un addr; + int fd = socket (AF_UNIX, type, 0); + if (fd == -1) + return; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); + close (fd); +} + +void test_bind_on_unchecked_socket (int type, const char *sockname) +{ + struct sockaddr_un addr; + int fd = socket (AF_UNIX, type, 0); /* { dg-message "when 'socket' fails" } */ + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "'bind' on possibly invalid file descriptor 'fd'" } */ + close (fd); +} + +void test_leak_of_bound_socket (int type, const char *sockname) +{ + struct sockaddr_un addr; + int fd = socket (AF_UNIX, type, 0); /* { dg-message "socket created here" } */ + if (fd == -1) + return; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-warning "leak of file descriptor 'fd'" } */ +} + +void test_listen_without_bind (int type) +{ + int fd = socket (AF_UNIX, type, 0); + if (fd == -1) + return; + listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" } */ + /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "msg" { target *-*-* } .-1 } */ + close (fd); +} + +void test_listen_on_unchecked_bind (int type, const char *sockname) +{ + struct sockaddr_un addr; + int fd = socket (AF_UNIX, type, 0); + if (fd == -1) + return; + memset (&addr, 0, sizeof (addr)); + addr.sun_family = AF_UNIX; + strncpy (addr.sun_path, sockname, sizeof(addr.sun_path) - 1); + bind (fd, (struct sockaddr *)&addr, sizeof (addr)); /* { dg-message "when 'bind' fails" } */ + listen (fd, 5); /* { dg-warning "'listen' on file descriptor 'fd' in wrong phase" "warning" } */ + /* { dg-message "'listen' expects a bound stream socket file descriptor but 'fd' has not yet been bound" "msg" { target *-*-* } .-1 } */ + close (fd); +} diff --git a/gcc/testsuite/gcc.dg/analyzer/pr104369-1.c b/gcc/testsuite/gcc.dg/analyzer/pr104369-1.c index d3b3241..c05137b 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr104369-1.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr104369-1.c @@ -1,5 +1,5 @@ -/* { dg-additional-options "--param analyzer-max-enodes-per-program-point=10" } */ -// TODO: remove need for this option +/* { dg-additional-options "-Wno-analyzer-too-complex -Wno-analyzer-fd-leak" } */ +// TODO: remove need for these options typedef __SIZE_TYPE__ size_t; #define NULL ((void *)0) diff --git a/gcc/testsuite/gcc.dg/analyzer/pr104369-2.c b/gcc/testsuite/gcc.dg/analyzer/pr104369-2.c index 57dc9ca..93d9987 100644 --- a/gcc/testsuite/gcc.dg/analyzer/pr104369-2.c +++ b/gcc/testsuite/gcc.dg/analyzer/pr104369-2.c @@ -1,3 +1,6 @@ +/* { dg-additional-options "-Wno-analyzer-fd-leak" } */ +// TODO: remove need for this option + typedef __SIZE_TYPE__ size_t; #define NULL ((void *)0) #define POLLIN 0x001 |