12874c5fdSThomas Gleixner // SPDX-License-Identifier: GPL-2.0-or-later 21da177e4SLinus Torvalds /* scm.c - Socket level control messages processing. 31da177e4SLinus Torvalds * 41da177e4SLinus Torvalds * Author: Alexey Kuznetsov, <kuznet@ms2.inr.ac.ru> 51da177e4SLinus Torvalds * Alignment and value checking mods by Craig Metz 61da177e4SLinus Torvalds */ 71da177e4SLinus Torvalds 81da177e4SLinus Torvalds #include <linux/module.h> 91da177e4SLinus Torvalds #include <linux/signal.h> 104fc268d2SRandy Dunlap #include <linux/capability.h> 111da177e4SLinus Torvalds #include <linux/errno.h> 121da177e4SLinus Torvalds #include <linux/sched.h> 138703e8a4SIngo Molnar #include <linux/sched/user.h> 141da177e4SLinus Torvalds #include <linux/mm.h> 151da177e4SLinus Torvalds #include <linux/kernel.h> 161da177e4SLinus Torvalds #include <linux/stat.h> 171da177e4SLinus Torvalds #include <linux/socket.h> 181da177e4SLinus Torvalds #include <linux/file.h> 191da177e4SLinus Torvalds #include <linux/fcntl.h> 201da177e4SLinus Torvalds #include <linux/net.h> 211da177e4SLinus Torvalds #include <linux/interrupt.h> 221da177e4SLinus Torvalds #include <linux/netdevice.h> 231da177e4SLinus Torvalds #include <linux/security.h> 2492f28d97SEric W. Biederman #include <linux/pid_namespace.h> 25b488893aSPavel Emelyanov #include <linux/pid.h> 26b488893aSPavel Emelyanov #include <linux/nsproxy.h> 275a0e3ad6STejun Heo #include <linux/slab.h> 289718475eSDeepa Dinamani #include <linux/errqueue.h> 291da177e4SLinus Torvalds 307c0f6ba6SLinus Torvalds #include <linux/uaccess.h> 311da177e4SLinus Torvalds 321da177e4SLinus Torvalds #include <net/protocol.h> 331da177e4SLinus Torvalds #include <linux/skbuff.h> 341da177e4SLinus Torvalds #include <net/sock.h> 351da177e4SLinus Torvalds #include <net/compat.h> 361da177e4SLinus Torvalds #include <net/scm.h> 37d8429506SDaniel Wagner #include <net/cls_cgroup.h> 381da177e4SLinus Torvalds 391da177e4SLinus Torvalds 401da177e4SLinus Torvalds /* 411da177e4SLinus Torvalds * Only allow a user to send credentials, that they could set with 421da177e4SLinus Torvalds * setu(g)id. 431da177e4SLinus Torvalds */ 441da177e4SLinus Torvalds 451da177e4SLinus Torvalds static __inline__ int scm_check_creds(struct ucred *creds) 461da177e4SLinus Torvalds { 4786a264abSDavid Howells const struct cred *cred = current_cred(); 48b2e4f544SEric W. Biederman kuid_t uid = make_kuid(cred->user_ns, creds->uid); 49b2e4f544SEric W. Biederman kgid_t gid = make_kgid(cred->user_ns, creds->gid); 50b2e4f544SEric W. Biederman 51b2e4f544SEric W. Biederman if (!uid_valid(uid) || !gid_valid(gid)) 52b2e4f544SEric W. Biederman return -EINVAL; 53b6dff3ecSDavid Howells 5492f28d97SEric W. Biederman if ((creds->pid == task_tgid_vnr(current) || 55d661684cSAndy Lutomirski ns_capable(task_active_pid_ns(current)->user_ns, CAP_SYS_ADMIN)) && 56b2e4f544SEric W. Biederman ((uid_eq(uid, cred->uid) || uid_eq(uid, cred->euid) || 57c7b96acfSEric W. Biederman uid_eq(uid, cred->suid)) || ns_capable(cred->user_ns, CAP_SETUID)) && 58b2e4f544SEric W. Biederman ((gid_eq(gid, cred->gid) || gid_eq(gid, cred->egid) || 59c7b96acfSEric W. Biederman gid_eq(gid, cred->sgid)) || ns_capable(cred->user_ns, CAP_SETGID))) { 601da177e4SLinus Torvalds return 0; 611da177e4SLinus Torvalds } 621da177e4SLinus Torvalds return -EPERM; 631da177e4SLinus Torvalds } 641da177e4SLinus Torvalds 651da177e4SLinus Torvalds static int scm_fp_copy(struct cmsghdr *cmsg, struct scm_fp_list **fplp) 661da177e4SLinus Torvalds { 671da177e4SLinus Torvalds int *fdp = (int*)CMSG_DATA(cmsg); 681da177e4SLinus Torvalds struct scm_fp_list *fpl = *fplp; 691da177e4SLinus Torvalds struct file **fpp; 701da177e4SLinus Torvalds int i, num; 711da177e4SLinus Torvalds 721ff8cebfSyuan linyu num = (cmsg->cmsg_len - sizeof(struct cmsghdr))/sizeof(int); 731da177e4SLinus Torvalds 741da177e4SLinus Torvalds if (num <= 0) 751da177e4SLinus Torvalds return 0; 761da177e4SLinus Torvalds 771da177e4SLinus Torvalds if (num > SCM_MAX_FD) 781da177e4SLinus Torvalds return -EINVAL; 791da177e4SLinus Torvalds 801da177e4SLinus Torvalds if (!fpl) 811da177e4SLinus Torvalds { 822c6ad20bSVasily Averin fpl = kmalloc(sizeof(struct scm_fp_list), GFP_KERNEL_ACCOUNT); 831da177e4SLinus Torvalds if (!fpl) 841da177e4SLinus Torvalds return -ENOMEM; 851da177e4SLinus Torvalds *fplp = fpl; 861da177e4SLinus Torvalds fpl->count = 0; 87bba14de9SEric Dumazet fpl->max = SCM_MAX_FD; 88415e3d3eSHannes Frederic Sowa fpl->user = NULL; 891da177e4SLinus Torvalds } 901da177e4SLinus Torvalds fpp = &fpl->fp[fpl->count]; 911da177e4SLinus Torvalds 92bba14de9SEric Dumazet if (fpl->count + num > fpl->max) 931da177e4SLinus Torvalds return -EINVAL; 941da177e4SLinus Torvalds 951da177e4SLinus Torvalds /* 961da177e4SLinus Torvalds * Verify the descriptors and increment the usage count. 971da177e4SLinus Torvalds */ 981da177e4SLinus Torvalds 991da177e4SLinus Torvalds for (i=0; i< num; i++) 1001da177e4SLinus Torvalds { 1011da177e4SLinus Torvalds int fd = fdp[i]; 1021da177e4SLinus Torvalds struct file *file; 1031da177e4SLinus Torvalds 104326be7b4SAl Viro if (fd < 0 || !(file = fget_raw(fd))) 1051da177e4SLinus Torvalds return -EBADF; 1061da177e4SLinus Torvalds *fpp++ = file; 1071da177e4SLinus Torvalds fpl->count++; 1081da177e4SLinus Torvalds } 109415e3d3eSHannes Frederic Sowa 110415e3d3eSHannes Frederic Sowa if (!fpl->user) 111415e3d3eSHannes Frederic Sowa fpl->user = get_uid(current_user()); 112415e3d3eSHannes Frederic Sowa 1131da177e4SLinus Torvalds return num; 1141da177e4SLinus Torvalds } 1151da177e4SLinus Torvalds 1161da177e4SLinus Torvalds void __scm_destroy(struct scm_cookie *scm) 1171da177e4SLinus Torvalds { 1181da177e4SLinus Torvalds struct scm_fp_list *fpl = scm->fp; 1191da177e4SLinus Torvalds int i; 1201da177e4SLinus Torvalds 1211da177e4SLinus Torvalds if (fpl) { 1221da177e4SLinus Torvalds scm->fp = NULL; 1231da177e4SLinus Torvalds for (i=fpl->count-1; i>=0; i--) 1241da177e4SLinus Torvalds fput(fpl->fp[i]); 125415e3d3eSHannes Frederic Sowa free_uid(fpl->user); 1261da177e4SLinus Torvalds kfree(fpl); 1271da177e4SLinus Torvalds } 1281da177e4SLinus Torvalds } 1299e34a5b5SEric Dumazet EXPORT_SYMBOL(__scm_destroy); 1301da177e4SLinus Torvalds 1311da177e4SLinus Torvalds int __scm_send(struct socket *sock, struct msghdr *msg, struct scm_cookie *p) 1321da177e4SLinus Torvalds { 133*1ded5e5aSEric Dumazet const struct proto_ops *ops = READ_ONCE(sock->ops); 1341da177e4SLinus Torvalds struct cmsghdr *cmsg; 1351da177e4SLinus Torvalds int err; 1361da177e4SLinus Torvalds 137f95b414eSGu Zheng for_each_cmsghdr(cmsg, msg) { 1381da177e4SLinus Torvalds err = -EINVAL; 1391da177e4SLinus Torvalds 1401da177e4SLinus Torvalds /* Verify that cmsg_len is at least sizeof(struct cmsghdr) */ 1411da177e4SLinus Torvalds /* The first check was omitted in <= 2.2.5. The reasoning was 1421da177e4SLinus Torvalds that parser checks cmsg_len in any case, so that 1431da177e4SLinus Torvalds additional check would be work duplication. 1441da177e4SLinus Torvalds But if cmsg_level is not SOL_SOCKET, we do not check 1451da177e4SLinus Torvalds for too short ancillary data object at all! Oops. 1461da177e4SLinus Torvalds OK, let's add it... 1471da177e4SLinus Torvalds */ 1481da177e4SLinus Torvalds if (!CMSG_OK(msg, cmsg)) 1491da177e4SLinus Torvalds goto error; 1501da177e4SLinus Torvalds 1511da177e4SLinus Torvalds if (cmsg->cmsg_level != SOL_SOCKET) 1521da177e4SLinus Torvalds continue; 1531da177e4SLinus Torvalds 1541da177e4SLinus Torvalds switch (cmsg->cmsg_type) 1551da177e4SLinus Torvalds { 1561da177e4SLinus Torvalds case SCM_RIGHTS: 157*1ded5e5aSEric Dumazet if (!ops || ops->family != PF_UNIX) 15876dadd76SEric W. Biederman goto error; 1591da177e4SLinus Torvalds err=scm_fp_copy(cmsg, &p->fp); 1601da177e4SLinus Torvalds if (err<0) 1611da177e4SLinus Torvalds goto error; 1621da177e4SLinus Torvalds break; 1631da177e4SLinus Torvalds case SCM_CREDENTIALS: 164b2e4f544SEric W. Biederman { 165dbe9a417SEric W. Biederman struct ucred creds; 166b2e4f544SEric W. Biederman kuid_t uid; 167b2e4f544SEric W. Biederman kgid_t gid; 1681da177e4SLinus Torvalds if (cmsg->cmsg_len != CMSG_LEN(sizeof(struct ucred))) 1691da177e4SLinus Torvalds goto error; 170dbe9a417SEric W. Biederman memcpy(&creds, CMSG_DATA(cmsg), sizeof(struct ucred)); 171dbe9a417SEric W. Biederman err = scm_check_creds(&creds); 1721da177e4SLinus Torvalds if (err) 1731da177e4SLinus Torvalds goto error; 174257b5358SEric W. Biederman 175dbe9a417SEric W. Biederman p->creds.pid = creds.pid; 176dbe9a417SEric W. Biederman if (!p->pid || pid_vnr(p->pid) != creds.pid) { 177257b5358SEric W. Biederman struct pid *pid; 178257b5358SEric W. Biederman err = -ESRCH; 179dbe9a417SEric W. Biederman pid = find_get_pid(creds.pid); 180257b5358SEric W. Biederman if (!pid) 181257b5358SEric W. Biederman goto error; 182257b5358SEric W. Biederman put_pid(p->pid); 183257b5358SEric W. Biederman p->pid = pid; 184257b5358SEric W. Biederman } 185257b5358SEric W. Biederman 186b2e4f544SEric W. Biederman err = -EINVAL; 187dbe9a417SEric W. Biederman uid = make_kuid(current_user_ns(), creds.uid); 188dbe9a417SEric W. Biederman gid = make_kgid(current_user_ns(), creds.gid); 189b2e4f544SEric W. Biederman if (!uid_valid(uid) || !gid_valid(gid)) 190b2e4f544SEric W. Biederman goto error; 191b2e4f544SEric W. Biederman 192dbe9a417SEric W. Biederman p->creds.uid = uid; 193dbe9a417SEric W. Biederman p->creds.gid = gid; 1941da177e4SLinus Torvalds break; 195b2e4f544SEric W. Biederman } 1961da177e4SLinus Torvalds default: 1971da177e4SLinus Torvalds goto error; 1981da177e4SLinus Torvalds } 1991da177e4SLinus Torvalds } 2001da177e4SLinus Torvalds 2011da177e4SLinus Torvalds if (p->fp && !p->fp->count) 2021da177e4SLinus Torvalds { 2031da177e4SLinus Torvalds kfree(p->fp); 2041da177e4SLinus Torvalds p->fp = NULL; 2051da177e4SLinus Torvalds } 2061da177e4SLinus Torvalds return 0; 2071da177e4SLinus Torvalds 2081da177e4SLinus Torvalds error: 2091da177e4SLinus Torvalds scm_destroy(p); 2101da177e4SLinus Torvalds return err; 2111da177e4SLinus Torvalds } 2129e34a5b5SEric Dumazet EXPORT_SYMBOL(__scm_send); 2131da177e4SLinus Torvalds 2141da177e4SLinus Torvalds int put_cmsg(struct msghdr * msg, int level, int type, int len, void *data) 2151da177e4SLinus Torvalds { 2161da177e4SLinus Torvalds int cmlen = CMSG_LEN(len); 2171da177e4SLinus Torvalds 2181f466e1fSChristoph Hellwig if (msg->msg_flags & MSG_CMSG_COMPAT) 2191da177e4SLinus Torvalds return put_cmsg_compat(msg, level, type, len, data); 2201da177e4SLinus Torvalds 2211f466e1fSChristoph Hellwig if (!msg->msg_control || msg->msg_controllen < sizeof(struct cmsghdr)) { 2221da177e4SLinus Torvalds msg->msg_flags |= MSG_CTRUNC; 2231da177e4SLinus Torvalds return 0; /* XXX: return error? check spec. */ 2241da177e4SLinus Torvalds } 2251da177e4SLinus Torvalds if (msg->msg_controllen < cmlen) { 2261da177e4SLinus Torvalds msg->msg_flags |= MSG_CTRUNC; 2271da177e4SLinus Torvalds cmlen = msg->msg_controllen; 2281da177e4SLinus Torvalds } 2291f466e1fSChristoph Hellwig 2301f466e1fSChristoph Hellwig if (msg->msg_control_is_user) { 2311f466e1fSChristoph Hellwig struct cmsghdr __user *cm = msg->msg_control_user; 2321f466e1fSChristoph Hellwig 2335f1eb1ffSEric Dumazet check_object_size(data, cmlen - sizeof(*cm), true); 2345f1eb1ffSEric Dumazet 23538ebcf50SEric Dumazet if (!user_write_access_begin(cm, cmlen)) 23638ebcf50SEric Dumazet goto efault; 23738ebcf50SEric Dumazet 238e7ad33faSEric Dumazet unsafe_put_user(cmlen, &cm->cmsg_len, efault_end); 23938ebcf50SEric Dumazet unsafe_put_user(level, &cm->cmsg_level, efault_end); 24038ebcf50SEric Dumazet unsafe_put_user(type, &cm->cmsg_type, efault_end); 24138ebcf50SEric Dumazet unsafe_copy_to_user(CMSG_USER_DATA(cm), data, 24238ebcf50SEric Dumazet cmlen - sizeof(*cm), efault_end); 24338ebcf50SEric Dumazet user_write_access_end(); 2441f466e1fSChristoph Hellwig } else { 2451f466e1fSChristoph Hellwig struct cmsghdr *cm = msg->msg_control; 2461da177e4SLinus Torvalds 2471f466e1fSChristoph Hellwig cm->cmsg_level = level; 2481f466e1fSChristoph Hellwig cm->cmsg_type = type; 2491f466e1fSChristoph Hellwig cm->cmsg_len = cmlen; 2501f466e1fSChristoph Hellwig memcpy(CMSG_DATA(cm), data, cmlen - sizeof(*cm)); 2511f466e1fSChristoph Hellwig } 2521f466e1fSChristoph Hellwig 2531f466e1fSChristoph Hellwig cmlen = min(CMSG_SPACE(len), msg->msg_controllen); 254c39ef213SKevin Brodsky if (msg->msg_control_is_user) 255c39ef213SKevin Brodsky msg->msg_control_user += cmlen; 256c39ef213SKevin Brodsky else 2571da177e4SLinus Torvalds msg->msg_control += cmlen; 2581da177e4SLinus Torvalds msg->msg_controllen -= cmlen; 2591f466e1fSChristoph Hellwig return 0; 26038ebcf50SEric Dumazet 26138ebcf50SEric Dumazet efault_end: 26238ebcf50SEric Dumazet user_write_access_end(); 26338ebcf50SEric Dumazet efault: 26438ebcf50SEric Dumazet return -EFAULT; 2651da177e4SLinus Torvalds } 2669e34a5b5SEric Dumazet EXPORT_SYMBOL(put_cmsg); 2671da177e4SLinus Torvalds 2689718475eSDeepa Dinamani void put_cmsg_scm_timestamping64(struct msghdr *msg, struct scm_timestamping_internal *tss_internal) 2699718475eSDeepa Dinamani { 2709718475eSDeepa Dinamani struct scm_timestamping64 tss; 2719718475eSDeepa Dinamani int i; 2729718475eSDeepa Dinamani 2739718475eSDeepa Dinamani for (i = 0; i < ARRAY_SIZE(tss.ts); i++) { 2749718475eSDeepa Dinamani tss.ts[i].tv_sec = tss_internal->ts[i].tv_sec; 2759718475eSDeepa Dinamani tss.ts[i].tv_nsec = tss_internal->ts[i].tv_nsec; 2769718475eSDeepa Dinamani } 2779718475eSDeepa Dinamani 2789718475eSDeepa Dinamani put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMPING_NEW, sizeof(tss), &tss); 2799718475eSDeepa Dinamani } 2809718475eSDeepa Dinamani EXPORT_SYMBOL(put_cmsg_scm_timestamping64); 2819718475eSDeepa Dinamani 2829718475eSDeepa Dinamani void put_cmsg_scm_timestamping(struct msghdr *msg, struct scm_timestamping_internal *tss_internal) 2839718475eSDeepa Dinamani { 2849718475eSDeepa Dinamani struct scm_timestamping tss; 2859718475eSDeepa Dinamani int i; 2869718475eSDeepa Dinamani 2870309f98fSArnd Bergmann for (i = 0; i < ARRAY_SIZE(tss.ts); i++) { 2880309f98fSArnd Bergmann tss.ts[i].tv_sec = tss_internal->ts[i].tv_sec; 2890309f98fSArnd Bergmann tss.ts[i].tv_nsec = tss_internal->ts[i].tv_nsec; 2900309f98fSArnd Bergmann } 2919718475eSDeepa Dinamani 2929718475eSDeepa Dinamani put_cmsg(msg, SOL_SOCKET, SO_TIMESTAMPING_OLD, sizeof(tss), &tss); 2939718475eSDeepa Dinamani } 2949718475eSDeepa Dinamani EXPORT_SYMBOL(put_cmsg_scm_timestamping); 2959718475eSDeepa Dinamani 2962618d530SChristoph Hellwig static int scm_max_fds(struct msghdr *msg) 2971da177e4SLinus Torvalds { 2982618d530SChristoph Hellwig if (msg->msg_controllen <= sizeof(struct cmsghdr)) 2992618d530SChristoph Hellwig return 0; 3002618d530SChristoph Hellwig return (msg->msg_controllen - sizeof(struct cmsghdr)) / sizeof(int); 3012618d530SChristoph Hellwig } 3022618d530SChristoph Hellwig 3032618d530SChristoph Hellwig void scm_detach_fds(struct msghdr *msg, struct scm_cookie *scm) 3042618d530SChristoph Hellwig { 305c0029de5SKees Cook struct cmsghdr __user *cm = 306c39ef213SKevin Brodsky (__force struct cmsghdr __user *)msg->msg_control_user; 307c0029de5SKees Cook unsigned int o_flags = (msg->msg_flags & MSG_CMSG_CLOEXEC) ? O_CLOEXEC : 0; 3082618d530SChristoph Hellwig int fdmax = min_t(int, scm_max_fds(msg), scm->fp->count); 3092618d530SChristoph Hellwig int __user *cmsg_data = CMSG_USER_DATA(cm); 3102618d530SChristoph Hellwig int err = 0, i; 3112618d530SChristoph Hellwig 312c0029de5SKees Cook /* no use for FD passing from kernel space callers */ 313c0029de5SKees Cook if (WARN_ON_ONCE(!msg->msg_control_is_user)) 314c0029de5SKees Cook return; 315c0029de5SKees Cook 3162618d530SChristoph Hellwig if (msg->msg_flags & MSG_CMSG_COMPAT) { 3172618d530SChristoph Hellwig scm_detach_fds_compat(msg, scm); 3182618d530SChristoph Hellwig return; 3192618d530SChristoph Hellwig } 3202618d530SChristoph Hellwig 3212618d530SChristoph Hellwig for (i = 0; i < fdmax; i++) { 32266590610SKees Cook err = receive_fd_user(scm->fp->fp[i], cmsg_data + i, o_flags); 323deefa7f3SKees Cook if (err < 0) 3242618d530SChristoph Hellwig break; 3252618d530SChristoph Hellwig } 3262618d530SChristoph Hellwig 3272618d530SChristoph Hellwig if (i > 0) { 3281da177e4SLinus Torvalds int cmlen = CMSG_LEN(i * sizeof(int)); 3292618d530SChristoph Hellwig 3301da177e4SLinus Torvalds err = put_user(SOL_SOCKET, &cm->cmsg_level); 3311da177e4SLinus Torvalds if (!err) 3321da177e4SLinus Torvalds err = put_user(SCM_RIGHTS, &cm->cmsg_type); 3331da177e4SLinus Torvalds if (!err) 3341da177e4SLinus Torvalds err = put_user(cmlen, &cm->cmsg_len); 3351da177e4SLinus Torvalds if (!err) { 3361da177e4SLinus Torvalds cmlen = CMSG_SPACE(i * sizeof(int)); 3376900317fSDaniel Borkmann if (msg->msg_controllen < cmlen) 3386900317fSDaniel Borkmann cmlen = msg->msg_controllen; 339c39ef213SKevin Brodsky msg->msg_control_user += cmlen; 3401da177e4SLinus Torvalds msg->msg_controllen -= cmlen; 3411da177e4SLinus Torvalds } 3421da177e4SLinus Torvalds } 3432618d530SChristoph Hellwig 3442618d530SChristoph Hellwig if (i < scm->fp->count || (scm->fp->count && fdmax <= 0)) 3451da177e4SLinus Torvalds msg->msg_flags |= MSG_CTRUNC; 3461da177e4SLinus Torvalds 3471da177e4SLinus Torvalds /* 3482618d530SChristoph Hellwig * All of the files that fit in the message have had their usage counts 3492618d530SChristoph Hellwig * incremented, so we just free the list. 3501da177e4SLinus Torvalds */ 3511da177e4SLinus Torvalds __scm_destroy(scm); 3521da177e4SLinus Torvalds } 3539e34a5b5SEric Dumazet EXPORT_SYMBOL(scm_detach_fds); 3541da177e4SLinus Torvalds 3551da177e4SLinus Torvalds struct scm_fp_list *scm_fp_dup(struct scm_fp_list *fpl) 3561da177e4SLinus Torvalds { 3571da177e4SLinus Torvalds struct scm_fp_list *new_fpl; 3581da177e4SLinus Torvalds int i; 3591da177e4SLinus Torvalds 3601da177e4SLinus Torvalds if (!fpl) 3611da177e4SLinus Torvalds return NULL; 3621da177e4SLinus Torvalds 363bba14de9SEric Dumazet new_fpl = kmemdup(fpl, offsetof(struct scm_fp_list, fp[fpl->count]), 3642c6ad20bSVasily Averin GFP_KERNEL_ACCOUNT); 3651da177e4SLinus Torvalds if (new_fpl) { 366bba14de9SEric Dumazet for (i = 0; i < fpl->count; i++) 3671da177e4SLinus Torvalds get_file(fpl->fp[i]); 368bba14de9SEric Dumazet new_fpl->max = new_fpl->count; 369415e3d3eSHannes Frederic Sowa new_fpl->user = get_uid(fpl->user); 3701da177e4SLinus Torvalds } 3711da177e4SLinus Torvalds return new_fpl; 3721da177e4SLinus Torvalds } 3731da177e4SLinus Torvalds EXPORT_SYMBOL(scm_fp_dup); 374