The Linux kernel's lockd requires that rpc.statd perform notification
callbacks from a privileged source port. To guarantee rpc.statd gets a
privileged source port but runs unprivileged, it calls
statd_get_socket() then drops root privileges before starting it's svc
request processing loop.
Statd's svc request loop is the only caller of the process_foo()
functions in utils/statd/rmtcall.c, but one of them,
process_notify_list() attempts to invoke statd_get_socket() again.
In today's code, this is unneeded because statd_get_socket() is always
invoked before my_svc_run(). However, if it ever succeeded, it would
get an unprivileged source port anyway, causing the kernel to reject
all subsequent requests from statd.
Thus the process_notify_list() function should not ever call
statd_get_socket() because root privileges have been dropped by this
point, and statd_get_socket() wouldn't get a privileged source port,
causing the kernel to reject all subsequent SM_NOTIFY requests.
So all of the process_foo functions in utils/statd/rmtcall.c should use
the global sockfd instead of a local copy, as it already has a
privileged source port.
I've seen some unexplained behavior where statd starts making calls to
the kernel via an unprivileged port. This could be one way that might
occur.
Signed-off-by: Chuck Lever <chuck.lever@oracle.com>
Signed-off-by: Steve Dickson <steved@redhat.com>
static int sockfd = -1; /* notify socket */
/*
static int sockfd = -1; /* notify socket */
/*
- * Initialize callback socket
+ * Initialize socket used to notify lockd of peer reboots.
+ *
+ * Returns the file descriptor of the new socket if successful;
+ * otherwise returns -1 and logs an error.
+ *
+ * Lockd rejects such requests if the source port is not privileged.
+ * statd_get_socket() must be invoked while statd still holds root
+ * privileges in order for the socket to acquire a privileged source
+ * port.
*/
int
statd_get_socket(void)
*/
int
statd_get_socket(void)
-xmit_call(int sockfd, struct sockaddr_in *sin,
+xmit_call(struct sockaddr_in *sin,
u_int32_t prog, u_int32_t vers, u_int32_t proc,
xdrproc_t func, void *obj)
/* __u32 prog, __u32 vers, __u32 proc, xdrproc_t func, void *obj) */
u_int32_t prog, u_int32_t vers, u_int32_t proc,
xdrproc_t func, void *obj)
/* __u32 prog, __u32 vers, __u32 proc, xdrproc_t func, void *obj) */
-recv_rply(int sockfd, struct sockaddr_in *sin, u_long *portp)
+recv_rply(struct sockaddr_in *sin, u_long *portp)
{
unsigned int msgbuf[MAXMSGSIZE], msglen;
struct rpc_msg mesg;
{
unsigned int msgbuf[MAXMSGSIZE], msglen;
struct rpc_msg mesg;
* Notify operation for a single list entry
*/
static int
* Notify operation for a single list entry
*/
static int
-process_entry(int sockfd, notify_list *lp)
+process_entry(notify_list *lp)
{
struct sockaddr_in sin;
struct status new_status;
{
struct sockaddr_in sin;
struct status new_status;
new_status.state = NL_STATE(lp);
memcpy(new_status.priv, NL_PRIV(lp), SM_PRIV_SIZE);
new_status.state = NL_STATE(lp);
memcpy(new_status.priv, NL_PRIV(lp), SM_PRIV_SIZE);
- lp->xid = xmit_call(sockfd, &sin, prog, vers, proc, func, objp);
+ lp->xid = xmit_call(&sin, prog, vers, proc, func, objp);
if (!lp->xid) {
note(N_WARNING, "%s: failed to notify port %d",
__func__, ntohs(lp->port));
if (!lp->xid) {
note(N_WARNING, "%s: failed to notify port %d",
__func__, ntohs(lp->port));
if (sockfd == -1 || !FD_ISSET(sockfd, rfds))
return 0;
if (sockfd == -1 || !FD_ISSET(sockfd, rfds))
return 0;
- if (!(lp = recv_rply(sockfd, &sin, &port)))
+ if (!(lp = recv_rply(&sin, &port)))
return 1;
if (lp->port == 0) {
if (port != 0) {
lp->port = htons((unsigned short) port);
return 1;
if (lp->port == 0) {
if (port != 0) {
lp->port = htons((unsigned short) port);
- process_entry(sockfd, lp);
NL_WHEN(lp) = time(NULL) + NOTIFY_TIMEOUT;
nlist_remove(¬ify, lp);
nlist_insert_timer(¬ify, lp);
NL_WHEN(lp) = time(NULL) + NOTIFY_TIMEOUT;
nlist_remove(¬ify, lp);
nlist_insert_timer(¬ify, lp);
{
notify_list *entry;
time_t now;
{
notify_list *entry;
time_t now;
- int fd;
-
- if ((fd = statd_get_socket()) < 0)
- return 0;
while ((entry = notify) != NULL && NL_WHEN(entry) < time(&now)) {
while ((entry = notify) != NULL && NL_WHEN(entry) < time(&now)) {
- if (process_entry(fd, entry)) {
+ if (process_entry(entry)) {
NL_WHEN(entry) = time(NULL) + NOTIFY_TIMEOUT;
nlist_remove(¬ify, entry);
nlist_insert_timer(¬ify, entry);
NL_WHEN(entry) = time(NULL) + NOTIFY_TIMEOUT;
nlist_remove(¬ify, entry);
nlist_insert_timer(¬ify, entry);
};
extern void sm_prog_1 (struct svc_req *, register SVCXPRT *);
};
extern void sm_prog_1 (struct svc_req *, register SVCXPRT *);
-extern int statd_get_socket(void);
static void load_state_number(void);
#ifdef SIMULATIONS
static void load_state_number(void);
#ifdef SIMULATIONS
}
/* Make sure we have a privilege port for calling into the kernel */
}
/* Make sure we have a privilege port for calling into the kernel */
+ if (statd_get_socket() < 0)
+ exit(1);
/* If sm-notify didn't take all the state files, load
* state information into our notify-list so we can
/* If sm-notify didn't take all the state files, load
* state information into our notify-list so we can
extern void my_svc_run(void);
extern void notify_hosts(void);
extern void shuffle_dirs(void);
extern void my_svc_run(void);
extern void notify_hosts(void);
extern void shuffle_dirs(void);
+extern int statd_get_socket(void);
extern int process_notify_list(void);
extern int process_reply(FD_SET_TYPE *);
extern char * xstrdup(const char *);
extern int process_notify_list(void);
extern int process_reply(FD_SET_TYPE *);
extern char * xstrdup(const char *);