Blame view
net/core/scm.c
7.64 KB
1da177e4c
|
1 2 3 4 5 6 7 8 9 10 11 12 13 |
/* scm.c - Socket level control messages processing. * * Author: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> * 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 <linux/module.h> #include <linux/signal.h> |
4fc268d24
|
14 |
#include <linux/capability.h> |
1da177e4c
|
15 16 17 18 |
#include <linux/errno.h> #include <linux/sched.h> #include <linux/mm.h> #include <linux/kernel.h> |
1da177e4c
|
19 20 21 22 23 24 25 26 |
#include <linux/stat.h> #include <linux/socket.h> #include <linux/file.h> #include <linux/fcntl.h> #include <linux/net.h> #include <linux/interrupt.h> #include <linux/netdevice.h> #include <linux/security.h> |
b488893a3
|
27 28 |
#include <linux/pid.h> #include <linux/nsproxy.h> |
5a0e3ad6a
|
29 |
#include <linux/slab.h> |
1da177e4c
|
30 |
|
1da177e4c
|
31 32 33 34 35 36 37 38 39 40 |
#include <asm/uaccess.h> #include <net/protocol.h> #include <linux/skbuff.h> #include <net/sock.h> #include <net/compat.h> #include <net/scm.h> /* |
4ec93edb1
|
41 |
* Only allow a user to send credentials, that they could set with |
1da177e4c
|
42 43 44 45 46 |
* setu(g)id. */ static __inline__ int scm_check_creds(struct ucred *creds) { |
86a264abe
|
47 |
const struct cred *cred = current_cred(); |
b6dff3ec5
|
48 |
|
b488893a3
|
49 |
if ((creds->pid == task_tgid_vnr(current) || capable(CAP_SYS_ADMIN)) && |
b6dff3ec5
|
50 51 52 53 |
((creds->uid == cred->uid || creds->uid == cred->euid || creds->uid == cred->suid) || capable(CAP_SETUID)) && ((creds->gid == cred->gid || creds->gid == cred->egid || creds->gid == cred->sgid) || capable(CAP_SETGID))) { |
1da177e4c
|
54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 |
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; |
bba14de98
|
81 |
fpl->max = SCM_MAX_FD; |
1da177e4c
|
82 83 |
} fpp = &fpl->fp[fpl->count]; |
bba14de98
|
84 |
if (fpl->count + num > fpl->max) |
1da177e4c
|
85 |
return -EINVAL; |
4ec93edb1
|
86 |
|
1da177e4c
|
87 88 89 |
/* * Verify the descriptors and increment the usage count. */ |
4ec93edb1
|
90 |
|
1da177e4c
|
91 92 93 94 |
for (i=0; i< num; i++) { int fd = fdp[i]; struct file *file; |
326be7b48
|
95 |
if (fd < 0 || !(file = fget_raw(fd))) |
1da177e4c
|
96 97 98 99 100 101 102 103 104 105 106 107 108 109 |
return -EBADF; *fpp++ = file; fpl->count++; } return num; } void __scm_destroy(struct scm_cookie *scm) { struct scm_fp_list *fpl = scm->fp; int i; if (fpl) { scm->fp = NULL; |
f8d570a47
|
110 111 112 113 114 115 116 117 118 119 120 121 122 123 124 125 126 127 128 |
if (current->scm_work_list) { list_add_tail(&fpl->list, current->scm_work_list); } else { LIST_HEAD(work_list); current->scm_work_list = &work_list; list_add(&fpl->list, &work_list); while (!list_empty(&work_list)) { fpl = list_first_entry(&work_list, struct scm_fp_list, list); list_del(&fpl->list); for (i=fpl->count-1; i>=0; i--) fput(fpl->fp[i]); kfree(fpl); } current->scm_work_list = NULL; } |
1da177e4c
|
129 130 |
} } |
9e34a5b51
|
131 |
EXPORT_SYMBOL(__scm_destroy); |
1da177e4c
|
132 133 134 135 136 137 138 139 140 141 142 143 144 145 |
int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p) { struct cmsghdr *cmsg; int err; for (cmsg = CMSG_FIRSTHDR(msg); cmsg; cmsg = CMSG_NXTHDR(msg, cmsg)) { err = -EINVAL; /* Verify that cmsg_len is at least sizeof(struct cmsghdr) */ /* The first check was omitted in <= 2.2.5. The reasoning was that parser checks cmsg_len in any case, so that additional check would be work duplication. |
4ec93edb1
|
146 |
But if cmsg_level is not SOL_SOCKET, we do not check |
1da177e4c
|
147 148 149 150 151 152 153 154 155 156 157 158 |
for too short ancillary data object at all! Oops. OK, let's add it... */ if (!CMSG_OK(msg, cmsg)) goto error; if (cmsg->cmsg_level != SOL_SOCKET) continue; switch (cmsg->cmsg_type) { case SCM_RIGHTS: |
76dadd76c
|
159 160 |
if (!sock->ops || sock->ops->family != PF_UNIX) goto error; |
1da177e4c
|
161 162 163 164 165 166 167 168 169 170 171 |
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; |
257b5358b
|
172 |
|
16e572626
|
173 |
if (!p->pid || pid_vnr(p->pid) != p->creds.pid) { |
257b5358b
|
174 175 176 177 178 179 180 181 |
struct pid *pid; err = -ESRCH; pid = find_get_pid(p->creds.pid); if (!pid) goto error; put_pid(p->pid); p->pid = pid; } |
16e572626
|
182 183 184 |
if (!p->cred || (p->cred->euid != p->creds.uid) || (p->cred->egid != p->creds.gid)) { |
257b5358b
|
185 186 187 188 189 190 191 |
struct cred *cred; err = -ENOMEM; cred = prepare_creds(); if (!cred) goto error; cred->uid = cred->euid = p->creds.uid; |
e33f7a9f3
|
192 |
cred->gid = cred->egid = p->creds.gid; |
16e572626
|
193 194 |
if (p->cred) put_cred(p->cred); |
257b5358b
|
195 196 |
p->cred = cred; } |
1da177e4c
|
197 198 199 200 201 202 203 204 205 206 207 208 |
break; default: goto error; } } if (p->fp && !p->fp->count) { kfree(p->fp); p->fp = NULL; } return 0; |
4ec93edb1
|
209 |
|
1da177e4c
|
210 211 212 213 |
error: scm_destroy(p); return err; } |
9e34a5b51
|
214 |
EXPORT_SYMBOL(__scm_send); |
1da177e4c
|
215 216 217 |
int put_cmsg(struct msghdr * msg, int level, int type, int len, void *data) { |
cfcabdcc2
|
218 219 |
struct cmsghdr __user *cm = (__force struct cmsghdr __user *)msg->msg_control; |
1da177e4c
|
220 221 222 223 224 225 226 227 228 229 230 231 232 233 234 235 236 237 238 239 240 |
struct cmsghdr cmhdr; int cmlen = CMSG_LEN(len); int err; if (MSG_CMSG_COMPAT & msg->msg_flags) return put_cmsg_compat(msg, level, type, len, data); 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)) |
4ec93edb1
|
241 |
goto out; |
1da177e4c
|
242 243 244 |
if (copy_to_user(CMSG_DATA(cm), data, cmlen - sizeof(struct cmsghdr))) goto out; cmlen = CMSG_SPACE(len); |
1ac70e7ad
|
245 246 |
if (msg->msg_controllen < cmlen) cmlen = msg->msg_controllen; |
1da177e4c
|
247 248 249 250 251 252 |
msg->msg_control += cmlen; msg->msg_controllen -= cmlen; err = 0; out: return err; } |
9e34a5b51
|
253 |
EXPORT_SYMBOL(put_cmsg); |
1da177e4c
|
254 255 256 |
void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm) { |
cfcabdcc2
|
257 258 |
struct cmsghdr __user *cm = (__force struct cmsghdr __user*)msg->msg_control; |
1da177e4c
|
259 260 261 262 263 264 265 266 267 268 269 270 271 272 273 274 275 276 |
int fdmax = 0; int fdnum = scm->fp->count; struct file **fp = scm->fp->fp; int __user *cmfptr; int err = 0, i; if (MSG_CMSG_COMPAT & msg->msg_flags) { scm_detach_fds_compat(msg, scm); return; } if (msg->msg_controllen > sizeof(struct cmsghdr)) fdmax = ((msg->msg_controllen - sizeof(struct cmsghdr)) / sizeof(int)); if (fdnum < fdmax) fdmax = fdnum; |
cfcabdcc2
|
277 278 |
for (i=0, cmfptr=(__force int __user *)CMSG_DATA(cm); i<fdmax; i++, cmfptr++) |
1da177e4c
|
279 280 281 282 283 |
{ int new_fd; err = security_file_receive(fp[i]); if (err) break; |
4a19542e5
|
284 285 |
err = get_unused_fd_flags(MSG_CMSG_CLOEXEC & msg->msg_flags ? O_CLOEXEC : 0); |
1da177e4c
|
286 287 288 289 290 291 292 293 294 295 296 297 298 299 300 301 |
if (err < 0) break; new_fd = err; err = put_user(new_fd, cmfptr); if (err) { put_unused_fd(new_fd); break; } /* Bump the usage count and install the file. */ get_file(fp[i]); fd_install(new_fd, fp[i]); } if (i > 0) { int cmlen = CMSG_LEN(i*sizeof(int)); |
effee6a00
|
302 |
err = put_user(SOL_SOCKET, &cm->cmsg_level); |
1da177e4c
|
303 304 305 306 307 308 309 310 311 312 313 314 315 316 317 318 319 320 321 |
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 || (fdnum && fdmax <= 0)) 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); } |
9e34a5b51
|
322 |
EXPORT_SYMBOL(scm_detach_fds); |
1da177e4c
|
323 324 325 326 327 328 329 330 |
struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl) { struct scm_fp_list *new_fpl; int i; if (!fpl) return NULL; |
bba14de98
|
331 332 |
new_fpl = kmemdup(fpl, offsetof(struct scm_fp_list, fp[fpl->count]), GFP_KERNEL); |
1da177e4c
|
333 |
if (new_fpl) { |
bba14de98
|
334 |
for (i = 0; i < fpl->count; i++) |
1da177e4c
|
335 |
get_file(fpl->fp[i]); |
bba14de98
|
336 |
new_fpl->max = new_fpl->count; |
1da177e4c
|
337 338 339 |
} return new_fpl; } |
1da177e4c
|
340 |
EXPORT_SYMBOL(scm_fp_dup); |