4
0
mirror of git://sourceware.org/git/newlib-cygwin.git synced 2025-03-06 15:06:25 +08:00

Cygwin: AF_UNIX: create an ack mechanism for SCM_RIGHTS

When a process sends a file descriptor via an SCM_RIGHTS control
message, it creates a temporary copy of the fhandler associated with
that descriptor and sends a serialization of that copy.  The
deserialization done by the receiver involves duplicating handles from
the copy, so the latter must stay alive until the deserialization is
done.  But it must ultimately be closed in order to avoid a memory
leak.

We coordinate all this as follows:

- Introduce a new struct scm_pending_fd that contains information
  about the temporary copy.  For brevity, call such a struct a
  "pending fd" in what follows.

- Maintain a list of pending fds in shared memory.

- Add several methods for manipulating the list to the af_unix_shmem_t
  and fhandler_socket_unix classes.

- Also add a lock, 'scm_fd_lock', to control access to the list.

- When a serialized fhandler is received, the receiver sends an ack
  back to the sender in an administrative packet with a control
  message of a new (Cygwin-specific) type SCM_RIGHTS_ACK.

- grab_admin_pkt is called in various places to process these packets.
  A complication here is that the process that calls grab_admin_pkt
  might not be the process that originally sent the serialized
  fhandler.  (It could be a subprocess of the original process, for
  example.)  This is why we need to maintain the list of pending fds
  in shared memory.

- Each fhandler_socket_unix keeps a count of the pending fds that it
  has created but not yet processed; this count is in a new data
  member 'my_npending_fd'.

- fhandler_socket_unix::close tries to process any remaining pending
  fds before closing, but it gives up after a short timeout and
  forcibly deletes them if necessary.
This commit is contained in:
Ken Brown 2020-11-18 13:00:02 -05:00
parent ee4f7ec67e
commit 81ceb96528
3 changed files with 212 additions and 12 deletions

View File

@ -928,6 +928,25 @@ class sun_name_t
void set (const struct sockaddr_un *name, __socklen_t namelen);
};
#define MAX_PENDING_FD 32
struct scm_pending_fd
{
fhandler_base *fh;
int64_t id;
pid_t pid; /* pid of creator */
bool ack_recvd;
int close ()
{
/* FIXME: This may not be right for all types of fhandlers; all we
really want to do is close all handles. */
int ret = fh->close ();
delete fh;
return ret;
}
};
/* For each AF_UNIX socket, we need to maintain socket-wide data,
regardless of the number of descriptors. The shmem region gets created
in socket, socketpair or accept4 and reopened by dup, fork or exec. */
@ -941,6 +960,7 @@ class af_unix_shmem_t
af_unix_spinlock_t _conn_lock;
af_unix_spinlock_t _state_lock;
af_unix_spinlock_t _io_lock;
af_unix_spinlock_t _scm_fd_lock;
LONG _connection_state; /* conn_state */
LONG _binding_state; /* bind_state */
LONG _shutdown; /* shut_state */
@ -954,6 +974,10 @@ class af_unix_shmem_t
struct ucred _sock_cred; /* filled at listen time */
struct ucred _peer_cred; /* filled at connect time */
/* SCM_RIGHTS fds sent and waiting for ack. */
scm_pending_fd pending_fd[MAX_PENDING_FD];
int npending;
public:
void bind_lock () { _bind_lock.lock (); }
void bind_unlock () { _bind_lock.unlock (); }
@ -963,6 +987,8 @@ class af_unix_shmem_t
void state_unlock () { _state_lock.unlock (); }
void io_lock () { _io_lock.lock (); }
void io_unlock () { _io_lock.unlock (); }
void scm_fd_lock () { _scm_fd_lock.lock (); }
void scm_fd_unlock () { _scm_fd_lock.unlock (); }
conn_state connect_state (conn_state val)
{ return (conn_state) InterlockedExchange (&_connection_state, val); }
@ -1004,6 +1030,57 @@ class af_unix_shmem_t
struct ucred *sock_cred () { return &_sock_cred; }
void peer_cred (struct ucred *uc) { _peer_cred = *uc; }
struct ucred *peer_cred () { return &_peer_cred; }
bool add_pending_fd (scm_pending_fd pfd)
{
if (npending >= MAX_PENDING_FD)
return false;
pending_fd[npending++] = pfd;
return true;
}
void delete_pending_fd (int i)
{
pending_fd[i].close ();
if (i < --npending)
pending_fd[i] = pending_fd[npending];
}
/* Return true if a pending fd is deleted. */
bool recv_scm_fd_ack (int64_t id)
{
bool ret = false;
int i;
for (i = 0; i < npending; ++i)
if (pending_fd[i].id == id)
break;
if (i < npending)
{
if (pending_fd[i].pid == myself->pid)
{
delete_pending_fd (i);
ret = true;
}
else
pending_fd[i].ack_recvd = true;
}
return ret;
}
/* Return number of pending fds deleted. */
int cleanup_pending_fd (pid_t pid, bool force = false)
{
int ret = 0;
/* Work from the top down to try to avoid copying. */
for (int i = npending - 1; i >= 0; --i)
if (pending_fd[i].pid == pid && (force || pending_fd[i].ack_recvd))
{
delete_pending_fd (i);
++ret;
}
return ret;
}
};
/* See the commentary in fhandler_socket_unix.cc. */
@ -1044,6 +1121,8 @@ class fhandler_socket_unix : public fhandler_socket
HANDLE connect_wait_thr;
HANDLE cwt_termination_evt;
PVOID cwt_param;
int my_npending_fd; /* Number of SCM_RIGHTS fds sent by me
and waiting for ack. */
void bind_lock () { shmem->bind_lock (); }
void bind_unlock () { shmem->bind_unlock (); }
@ -1053,6 +1132,8 @@ class fhandler_socket_unix : public fhandler_socket
void state_unlock () { shmem->state_unlock (); }
void io_lock () { shmem->io_lock (); }
void io_unlock () { shmem->io_unlock (); }
void scm_fd_lock () { shmem->scm_fd_lock (); }
void scm_fd_unlock () { shmem->scm_fd_unlock (); }
bind_state binding_state (bind_state val)
{ return shmem->binding_state (val); }
bind_state binding_state () const { return shmem->binding_state (); }
@ -1115,6 +1196,21 @@ class fhandler_socket_unix : public fhandler_socket
void set_close_on_exec (bool val);
void fixup_helper ();
bool send_scm_fd_ack (int64_t id);
void recv_scm_fd_ack (int64_t id)
{
if (shmem->recv_scm_fd_ack (id))
--my_npending_fd;
}
bool add_pending_fd (scm_pending_fd pfd);
bool check_pending_fd (DWORD timeout = 0);
/* scm_fd_lock should be in place. */
void cleanup_pending_fd (bool force = false)
{
grab_admin_pkt ();
my_npending_fd -= shmem->cleanup_pending_fd (myself->pid, force);
}
public:
fhandler_socket_unix ();
~fhandler_socket_unix ();

View File

@ -84,6 +84,9 @@ GUID __cygwin_socket_guid = {
A bound DGRAM socket sends its sun_path with each sendmsg/sendto.
*/
/* FIXME: Add commentary explaining how we handle SCM_RIGHTS ancillary
data. */
static inline ptrdiff_t
AF_UNIX_PKT_OFFSETOF_NAME (af_unix_pkt_hdr_t *phdr)
{
@ -716,9 +719,18 @@ fhandler_socket_unix::process_admin_pkt (af_unix_pkt_hdr_t *packet)
struct cmsghdr *cmsg = (struct cmsghdr *)
alloca (packet->cmsg_len);
memcpy (cmsg, AF_UNIX_PKT_CMSG (packet), packet->cmsg_len);
if (cmsg->cmsg_level == SOL_SOCKET
&& cmsg->cmsg_type == SCM_CREDENTIALS)
peer_cred ((struct ucred *) CMSG_DATA(cmsg));
if (cmsg->cmsg_level == SOL_SOCKET)
switch (cmsg->cmsg_type)
{
case SCM_CREDENTIALS:
peer_cred ((struct ucred *) CMSG_DATA(cmsg));
break;
case SCM_RIGHTS_ACK:
recv_scm_fd_ack (*((int64_t *) CMSG_DATA (cmsg)));
break;
default:
break;
}
}
state_unlock ();
}
@ -1272,7 +1284,8 @@ fhandler_socket_unix::set_close_on_exec (bool val)
fhandler_socket_unix::fhandler_socket_unix () :
fhandler_socket (),
shmem_handle (NULL), shmem (NULL), backing_file_handle (NULL),
connect_wait_thr (NULL), cwt_termination_evt (NULL), cwt_param (NULL)
connect_wait_thr (NULL), cwt_termination_evt (NULL), cwt_param (NULL),
my_npending_fd (0)
{
need_fork_fixup (true);
}
@ -1890,6 +1903,17 @@ fhandler_socket_unix::close ()
PVOID param = InterlockedExchangePointer (&cwt_param, NULL);
if (param)
cfree (param);
/* Try to clear pending fds. Give up if we can't make progress. */
while (my_npending_fd > 0)
if (!check_pending_fd (100))
break;
if (my_npending_fd > 0)
{
debug_printf ("can't cleanup pending fds; forcibly deleting");
scm_fd_lock ();
cleanup_pending_fd (true);
scm_fd_unlock ();
}
HANDLE hdl = InterlockedExchangePointer (&get_handle (), NULL);
if (hdl)
NtClose (hdl);
@ -1945,11 +1969,6 @@ struct fh_ser
DWORD winpid; /* Windows pid of sender. */
};
/* FIXME: For testing purposes I'm allowing a memory leak. save_fh is
a reminder. It needs to be alive until the receiver runs
deserialize and notifies us that it can be closed. */
static fhandler_base *save_fh;
/* Return a pointer to an allocated buffer containing an fh_ser. The
caller has to free it. */
void *
@ -1990,8 +2009,14 @@ fhandler_socket_unix::serialize (int fd)
fhs->winpid = GetCurrentProcessId ();
NtAllocateLocallyUniqueId ((PLUID) &id);
fhs->uniq_id = id;
/* FIXME: Save pending ack. */
save_fh = newfh;
scm_pending_fd pfd;
pfd.fh = newfh;
pfd.id = id;
pfd.pid = myself->pid;
pfd.ack_recvd = false;
scm_fd_lock ();
add_pending_fd (pfd);
scm_fd_unlock ();
out:
return (void *) fhs;
}
@ -2024,7 +2049,8 @@ fhandler_socket_unix::deserialize (void *bufp)
return -1;
newfh = oldfh->clone ();
int ret = oldfh->dup (newfh, 0, winpid);
/* FIXME: Send ack to sender that it can close fhs->uniq_id. */
if (!send_scm_fd_ack (fhs->uniq_id))
debug_printf ("can't send ack");
if (ret < 0)
{
debug_printf ("can't duplicate handles");
@ -2050,6 +2076,81 @@ fhandler_socket_unix::deserialize (void *bufp)
return cfd;
}
/* FIXME: This only works if we have a pipe handle. */
bool
fhandler_socket_unix::send_scm_fd_ack (int64_t id)
{
if (!get_handle ())
{
debug_printf ("can't send ack; no handle");
return false;
}
size_t clen, plen;
af_unix_pkt_hdr_t *packet;
NTSTATUS status;
IO_STATUS_BLOCK io;
clen = CMSG_SPACE (sizeof (int64_t));
plen = sizeof *packet + clen;
packet = (af_unix_pkt_hdr_t *) alloca (plen);
packet->init (true, _SHUT_NONE, 0, clen, 0);
struct cmsghdr *cmsg = AF_UNIX_PKT_CMSG (packet);
cmsg->cmsg_level = SOL_SOCKET;
cmsg->cmsg_type = SCM_RIGHTS_ACK;
cmsg->cmsg_len = CMSG_LEN (sizeof (int64_t));
memcpy (CMSG_DATA(cmsg), &id, sizeof (int64_t));
/* The theory: Fire and forget. */
io_lock ();
set_pipe_non_blocking (true);
status = NtWriteFile (get_handle (), NULL, NULL, NULL, &io, packet,
packet->pckt_len, NULL, NULL);
set_pipe_non_blocking (is_nonblocking ());
io_unlock ();
if (!NT_SUCCESS (status))
{
debug_printf ("can't send ack: NtWriteFile: %y", status);
return false;
}
return true;
}
/* scm_fd_lock should be in place when this is called. */
bool
fhandler_socket_unix::add_pending_fd (scm_pending_fd pfd)
{
if (my_npending_fd > 0)
cleanup_pending_fd ();
if (shmem->add_pending_fd (pfd))
{
++my_npending_fd;
return true;
}
return false;
}
/* Return true if my_npending_fd is reduced. */
bool
fhandler_socket_unix::check_pending_fd (DWORD timeout)
{
DWORD sleep_time = 0, tot_sleep_time = 0;
int save_npending = my_npending_fd;
while (tot_sleep_time <= timeout)
{
scm_fd_lock ();
cleanup_pending_fd ();
scm_fd_unlock ();
if (my_npending_fd < save_npending)
break;
cygwait (sleep_time >> 3);
tot_sleep_time += sleep_time >> 3;
if (sleep_time < 80)
++sleep_time;
}
return my_npending_fd < save_npending;
}
/* FIXME: Check all errnos below. */
bool
fhandler_socket_unix::evaluate_cmsg_data (af_unix_pkt_hdr_t *packet,

View File

@ -107,6 +107,9 @@ struct cmsghdr
#ifdef __WITH_AF_UNIX
#define SCM_MAX_FD 20
#ifdef __INSIDE_CYGWIN__
#define SCM_RIGHTS_ACK 0x10 /* descriptor acknowledgement; Cygwin specific */
#endif
#endif
#ifdef __INSIDE_CYGWIN__