/* scm.c - Socket level control messages processing. * * Author: Alexey Kuznetsov, * Alignment and value checking mods by Craig Metz * * This program is free software; you can redistribute it and/or * modify it under the terms of the GNU General Public License * as published by the Free Software Foundation; either version * 2 of the License, or (at your option) any later version. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include /* * Only allow a user to send credentials, that they could set with * setu(g)id. */ static __inline__ int scm_check_creds(struct ucred *creds) { if ((creds->pid == current->pid || capable(CAP_SYS_ADMIN)) && ((creds->uid == current->uid || creds->uid == current->euid || creds->uid == current->suid) || capable(CAP_SETUID)) && ((creds->gid == current->gid || creds->gid == current->egid || creds->gid == current->sgid) || capable(CAP_SETGID))) { return 0; } return -EPERM; } static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp) { int *fdp = (int*)CMSG_DATA(cmsg); struct scm_fp_list *fpl = *fplp; struct file **fpp; int i, num; num = (cmsg->cmsg_len - CMSG_ALIGN(sizeof(struct cmsghdr)))/sizeof(int); if (num <= 0) return 0; if (num > SCM_MAX_FD) return -EINVAL; if (!fpl) { fpl = kmalloc(sizeof(struct scm_fp_list), GFP_KERNEL); if (!fpl) return -ENOMEM; *fplp = fpl; fpl->count = 0; } fpp = &fpl->fp[fpl->count]; if (fpl->count + num > SCM_MAX_FD) return -EINVAL; /* * Verify the descriptors and increment the usage count. */ for (i=0; i< num; i++) { int fd = fdp[i]; struct file *file; if (fd < 0 || !(file = fget(fd))) return -EBADF; *fpp++ = file; fpl->count++; } return num; } void __scm_destroy(struct scm_cookie *scm) { struct scm_fp_list *fpl = scm->fp; struct file *file; int i; if (fpl) { scm->fp = NULL; for (i=fpl->count-1; i>=0; i--) fput(fpl->fp[i]); kfree(fpl); } file = scm->file; if (file) { scm->sock = NULL; scm->file = NULL; fput(file); } } extern __inline__ int not_one_bit(unsigned val) { return (val-1) & val; } int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p) { struct cmsghdr *cmsg; struct file *file; int acc_fd, err; unsigned int scm_flags=0; for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { if (cmsg->cmsg_level != SOL_SOCKET) continue; err = -EINVAL; switch (cmsg->cmsg_type) { case SCM_RIGHTS: err=scm_fp_copy(cmsg, &p->fp); if (err<0) goto error; break; case SCM_CREDENTIALS: if (cmsg->cmsg_len != CMSG_LEN(sizeof(struct ucred))) goto error; memcpy(&p->creds, CMSG_DATA(cmsg), sizeof(struct ucred)); err = scm_check_creds(&p->creds); if (err) goto error; break; case SCM_CONNECT: if (scm_flags) goto error; if (cmsg->cmsg_len != CMSG_LEN(sizeof(int))) goto error; memcpy(&acc_fd, CMSG_DATA(cmsg), sizeof(int)); p->sock = NULL; if (acc_fd != -1) { err = -EBADF; file = fget(acc_fd); if (!file) goto error; p->file = file; err = -ENOTSOCK; if (!file->f_dentry->d_inode || !file->f_dentry->d_inode->i_sock) goto error; p->sock = &file->f_dentry->d_inode->u.socket_i; err = -EINVAL; if (p->sock->state != SS_UNCONNECTED) goto error; } scm_flags |= MSG_SYN; break; default: goto error; } } if (p->fp && !p->fp->count) { kfree(p->fp); p->fp = NULL; } err = -EINVAL; msg->msg_flags |= scm_flags; scm_flags = msg->msg_flags&MSG_CTLFLAGS; if (not_one_bit(scm_flags)) goto error; if (!(scm_flags && p->fp)) return 0; error: scm_destroy(p); return err; } int put_cmsg(struct msghdr * msg, int level, int type, int len, void *data) { struct cmsghdr *cm = (struct cmsghdr*)msg->msg_control; struct cmsghdr cmhdr; int cmlen = CMSG_LEN(len); int err; if (cm==NULL || msg->msg_controllen < sizeof(*cm)) { msg->msg_flags |= MSG_CTRUNC; return 0; /* XXX: return error? check spec. */ } if (msg->msg_controllen < cmlen) { msg->msg_flags |= MSG_CTRUNC; cmlen = msg->msg_controllen; } cmhdr.cmsg_level = level; cmhdr.cmsg_type = type; cmhdr.cmsg_len = cmlen; err = -EFAULT; if (copy_to_user(cm, &cmhdr, sizeof cmhdr)) goto out; if (copy_to_user(CMSG_DATA(cm), data, cmlen - sizeof(struct cmsghdr))) goto out; cmlen = CMSG_SPACE(len); msg->msg_control += cmlen; msg->msg_controllen -= cmlen; err = 0; out: return err; } void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm) { struct cmsghdr *cm = (struct cmsghdr*)msg->msg_control; int fdmax = (msg->msg_controllen - sizeof(struct cmsghdr))/sizeof(int); int fdnum = scm->fp->count; struct file **fp = scm->fp->fp; int *cmfptr; int err = 0, i; if (fdnum < fdmax) fdmax = fdnum; for (i=0, cmfptr=(int*)CMSG_DATA(cm); if_count++; current->files->fd[new_fd] = fp[i]; } if (i > 0) { int cmlen = CMSG_LEN(i*sizeof(int)); if (!err) err = put_user(SOL_SOCKET, &cm->cmsg_level); if (!err) err = put_user(SCM_RIGHTS, &cm->cmsg_type); if (!err) err = put_user(cmlen, &cm->cmsg_len); if (!err) { cmlen = CMSG_SPACE(i*sizeof(int)); msg->msg_control += cmlen; msg->msg_controllen -= cmlen; } } if (i < fdnum) msg->msg_flags |= MSG_CTRUNC; /* * All of the files that fit in the message have had their * usage counts incremented, so we just free the list. */ __scm_destroy(scm); } struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl) { struct scm_fp_list *new_fpl; int i; if (!fpl) return NULL; new_fpl = kmalloc(sizeof(*fpl), GFP_KERNEL); if (new_fpl) { memcpy(new_fpl, fpl, sizeof(*fpl)); for (i=fpl->count-1; i>=0; i--) fpl->fp[i]->f_count++; } return new_fpl; }