diff options
author | Jann Horn <jannh@google.com> | 2018-06-25 12:34:10 -0400 |
---|---|---|
committer | Eric W. Biederman <ebiederm@xmission.com> | 2018-08-11 03:05:53 -0400 |
commit | 42a0cc3478584d4d63f68f2f5af021ddbea771fa (patch) | |
tree | 8dc33625ea1c8488e01965f159ef3f597eba42cb | |
parent | 5820f140edef111a9ea2ef414ab2428b8cb805b1 (diff) |
sys: don't hold uts_sem while accessing userspace memory
Holding uts_sem as a writer while accessing userspace memory allows a
namespace admin to stall all processes that attempt to take uts_sem.
Instead, move data through stack buffers and don't access userspace memory
while uts_sem is held.
Cc: stable@vger.kernel.org
Fixes: 1da177e4c3f4 ("Linux-2.6.12-rc2")
Signed-off-by: Jann Horn <jannh@google.com>
Signed-off-by: Eric W. Biederman <ebiederm@xmission.com>
-rw-r--r-- | arch/alpha/kernel/osf_sys.c | 51 | ||||
-rw-r--r-- | arch/sparc/kernel/sys_sparc_32.c | 22 | ||||
-rw-r--r-- | arch/sparc/kernel/sys_sparc_64.c | 20 | ||||
-rw-r--r-- | kernel/sys.c | 95 | ||||
-rw-r--r-- | kernel/utsname_sysctl.c | 41 |
5 files changed, 119 insertions, 110 deletions
diff --git a/arch/alpha/kernel/osf_sys.c b/arch/alpha/kernel/osf_sys.c index 6e921754c8fc..2a5c768df335 100644 --- a/arch/alpha/kernel/osf_sys.c +++ b/arch/alpha/kernel/osf_sys.c | |||
@@ -530,24 +530,19 @@ SYSCALL_DEFINE4(osf_mount, unsigned long, typenr, const char __user *, path, | |||
530 | SYSCALL_DEFINE1(osf_utsname, char __user *, name) | 530 | SYSCALL_DEFINE1(osf_utsname, char __user *, name) |
531 | { | 531 | { |
532 | int error; | 532 | int error; |
533 | char tmp[5 * 32]; | ||
533 | 534 | ||
534 | down_read(&uts_sem); | 535 | down_read(&uts_sem); |
535 | error = -EFAULT; | 536 | memcpy(tmp + 0 * 32, utsname()->sysname, 32); |
536 | if (copy_to_user(name + 0, utsname()->sysname, 32)) | 537 | memcpy(tmp + 1 * 32, utsname()->nodename, 32); |
537 | goto out; | 538 | memcpy(tmp + 2 * 32, utsname()->release, 32); |
538 | if (copy_to_user(name + 32, utsname()->nodename, 32)) | 539 | memcpy(tmp + 3 * 32, utsname()->version, 32); |
539 | goto out; | 540 | memcpy(tmp + 4 * 32, utsname()->machine, 32); |
540 | if (copy_to_user(name + 64, utsname()->release, 32)) | 541 | up_read(&uts_sem); |
541 | goto out; | ||
542 | if (copy_to_user(name + 96, utsname()->version, 32)) | ||
543 | goto out; | ||
544 | if (copy_to_user(name + 128, utsname()->machine, 32)) | ||
545 | goto out; | ||
546 | 542 | ||
547 | error = 0; | 543 | if (copy_to_user(name, tmp, sizeof(tmp))) |
548 | out: | 544 | return -EFAULT; |
549 | up_read(&uts_sem); | 545 | return 0; |
550 | return error; | ||
551 | } | 546 | } |
552 | 547 | ||
553 | SYSCALL_DEFINE0(getpagesize) | 548 | SYSCALL_DEFINE0(getpagesize) |
@@ -567,18 +562,21 @@ SYSCALL_DEFINE2(osf_getdomainname, char __user *, name, int, namelen) | |||
567 | { | 562 | { |
568 | int len, err = 0; | 563 | int len, err = 0; |
569 | char *kname; | 564 | char *kname; |
565 | char tmp[32]; | ||
570 | 566 | ||
571 | if (namelen > 32) | 567 | if (namelen < 0 || namelen > 32) |
572 | namelen = 32; | 568 | namelen = 32; |
573 | 569 | ||
574 | down_read(&uts_sem); | 570 | down_read(&uts_sem); |
575 | kname = utsname()->domainname; | 571 | kname = utsname()->domainname; |
576 | len = strnlen(kname, namelen); | 572 | len = strnlen(kname, namelen); |
577 | if (copy_to_user(name, kname, min(len + 1, namelen))) | 573 | len = min(len + 1, namelen); |
578 | err = -EFAULT; | 574 | memcpy(tmp, kname, len); |
579 | up_read(&uts_sem); | 575 | up_read(&uts_sem); |
580 | 576 | ||
581 | return err; | 577 | if (copy_to_user(name, tmp, len)) |
578 | return -EFAULT; | ||
579 | return 0; | ||
582 | } | 580 | } |
583 | 581 | ||
584 | /* | 582 | /* |
@@ -739,13 +737,14 @@ SYSCALL_DEFINE3(osf_sysinfo, int, command, char __user *, buf, long, count) | |||
739 | }; | 737 | }; |
740 | unsigned long offset; | 738 | unsigned long offset; |
741 | const char *res; | 739 | const char *res; |
742 | long len, err = -EINVAL; | 740 | long len; |
741 | char tmp[__NEW_UTS_LEN + 1]; | ||
743 | 742 | ||
744 | offset = command-1; | 743 | offset = command-1; |
745 | if (offset >= ARRAY_SIZE(sysinfo_table)) { | 744 | if (offset >= ARRAY_SIZE(sysinfo_table)) { |
746 | /* Digital UNIX has a few unpublished interfaces here */ | 745 | /* Digital UNIX has a few unpublished interfaces here */ |
747 | printk("sysinfo(%d)", command); | 746 | printk("sysinfo(%d)", command); |
748 | goto out; | 747 | return -EINVAL; |
749 | } | 748 | } |
750 | 749 | ||
751 | down_read(&uts_sem); | 750 | down_read(&uts_sem); |
@@ -753,13 +752,11 @@ SYSCALL_DEFINE3(osf_sysinfo, int, command, char __user *, buf, long, count) | |||
753 | len = strlen(res)+1; | 752 | len = strlen(res)+1; |
754 | if ((unsigned long)len > (unsigned long)count) | 753 | if ((unsigned long)len > (unsigned long)count) |
755 | len = count; | 754 | len = count; |
756 | if (copy_to_user(buf, res, len)) | 755 | memcpy(tmp, res, len); |
757 | err = -EFAULT; | ||
758 | else | ||
759 | err = 0; | ||
760 | up_read(&uts_sem); | 756 | up_read(&uts_sem); |
761 | out: | 757 | if (copy_to_user(buf, tmp, len)) |
762 | return err; | 758 | return -EFAULT; |
759 | return 0; | ||
763 | } | 760 | } |
764 | 761 | ||
765 | SYSCALL_DEFINE5(osf_getsysinfo, unsigned long, op, void __user *, buffer, | 762 | SYSCALL_DEFINE5(osf_getsysinfo, unsigned long, op, void __user *, buffer, |
diff --git a/arch/sparc/kernel/sys_sparc_32.c b/arch/sparc/kernel/sys_sparc_32.c index 7f3d9c59719a..452e4d080855 100644 --- a/arch/sparc/kernel/sys_sparc_32.c +++ b/arch/sparc/kernel/sys_sparc_32.c | |||
@@ -197,23 +197,27 @@ SYSCALL_DEFINE5(rt_sigaction, int, sig, | |||
197 | 197 | ||
198 | SYSCALL_DEFINE2(getdomainname, char __user *, name, int, len) | 198 | SYSCALL_DEFINE2(getdomainname, char __user *, name, int, len) |
199 | { | 199 | { |
200 | int nlen, err; | 200 | int nlen, err; |
201 | 201 | char tmp[__NEW_UTS_LEN + 1]; | |
202 | |||
202 | if (len < 0) | 203 | if (len < 0) |
203 | return -EINVAL; | 204 | return -EINVAL; |
204 | 205 | ||
205 | down_read(&uts_sem); | 206 | down_read(&uts_sem); |
206 | 207 | ||
207 | nlen = strlen(utsname()->domainname) + 1; | 208 | nlen = strlen(utsname()->domainname) + 1; |
208 | err = -EINVAL; | 209 | err = -EINVAL; |
209 | if (nlen > len) | 210 | if (nlen > len) |
210 | goto out; | 211 | goto out_unlock; |
212 | memcpy(tmp, utsname()->domainname, nlen); | ||
211 | 213 | ||
212 | err = -EFAULT; | 214 | up_read(&uts_sem); |
213 | if (!copy_to_user(name, utsname()->domainname, nlen)) | ||
214 | err = 0; | ||
215 | 215 | ||
216 | out: | 216 | if (copy_to_user(name, tmp, nlen)) |
217 | return -EFAULT; | ||
218 | return 0; | ||
219 | |||
220 | out_unlock: | ||
217 | up_read(&uts_sem); | 221 | up_read(&uts_sem); |
218 | return err; | 222 | return err; |
219 | } | 223 | } |
diff --git a/arch/sparc/kernel/sys_sparc_64.c b/arch/sparc/kernel/sys_sparc_64.c index 63baa8aa9414..274ed0b9b3e0 100644 --- a/arch/sparc/kernel/sys_sparc_64.c +++ b/arch/sparc/kernel/sys_sparc_64.c | |||
@@ -519,23 +519,27 @@ asmlinkage void sparc_breakpoint(struct pt_regs *regs) | |||
519 | 519 | ||
520 | SYSCALL_DEFINE2(getdomainname, char __user *, name, int, len) | 520 | SYSCALL_DEFINE2(getdomainname, char __user *, name, int, len) |
521 | { | 521 | { |
522 | int nlen, err; | 522 | int nlen, err; |
523 | char tmp[__NEW_UTS_LEN + 1]; | ||
523 | 524 | ||
524 | if (len < 0) | 525 | if (len < 0) |
525 | return -EINVAL; | 526 | return -EINVAL; |
526 | 527 | ||
527 | down_read(&uts_sem); | 528 | down_read(&uts_sem); |
528 | 529 | ||
529 | nlen = strlen(utsname()->domainname) + 1; | 530 | nlen = strlen(utsname()->domainname) + 1; |
530 | err = -EINVAL; | 531 | err = -EINVAL; |
531 | if (nlen > len) | 532 | if (nlen > len) |
532 | goto out; | 533 | goto out_unlock; |
534 | memcpy(tmp, utsname()->domainname, nlen); | ||
535 | |||
536 | up_read(&uts_sem); | ||
533 | 537 | ||
534 | err = -EFAULT; | 538 | if (copy_to_user(name, tmp, nlen)) |
535 | if (!copy_to_user(name, utsname()->domainname, nlen)) | 539 | return -EFAULT; |
536 | err = 0; | 540 | return 0; |
537 | 541 | ||
538 | out: | 542 | out_unlock: |
539 | up_read(&uts_sem); | 543 | up_read(&uts_sem); |
540 | return err; | 544 | return err; |
541 | } | 545 | } |
diff --git a/kernel/sys.c b/kernel/sys.c index 38509dc1f77b..69b9a37ecf0d 100644 --- a/kernel/sys.c +++ b/kernel/sys.c | |||
@@ -1237,18 +1237,19 @@ static int override_release(char __user *release, size_t len) | |||
1237 | 1237 | ||
1238 | SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name) | 1238 | SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name) |
1239 | { | 1239 | { |
1240 | int errno = 0; | 1240 | struct new_utsname tmp; |
1241 | 1241 | ||
1242 | down_read(&uts_sem); | 1242 | down_read(&uts_sem); |
1243 | if (copy_to_user(name, utsname(), sizeof *name)) | 1243 | memcpy(&tmp, utsname(), sizeof(tmp)); |
1244 | errno = -EFAULT; | ||
1245 | up_read(&uts_sem); | 1244 | up_read(&uts_sem); |
1245 | if (copy_to_user(name, &tmp, sizeof(tmp))) | ||
1246 | return -EFAULT; | ||
1246 | 1247 | ||
1247 | if (!errno && override_release(name->release, sizeof(name->release))) | 1248 | if (override_release(name->release, sizeof(name->release))) |
1248 | errno = -EFAULT; | 1249 | return -EFAULT; |
1249 | if (!errno && override_architecture(name)) | 1250 | if (override_architecture(name)) |
1250 | errno = -EFAULT; | 1251 | return -EFAULT; |
1251 | return errno; | 1252 | return 0; |
1252 | } | 1253 | } |
1253 | 1254 | ||
1254 | #ifdef __ARCH_WANT_SYS_OLD_UNAME | 1255 | #ifdef __ARCH_WANT_SYS_OLD_UNAME |
@@ -1257,55 +1258,46 @@ SYSCALL_DEFINE1(newuname, struct new_utsname __user *, name) | |||
1257 | */ | 1258 | */ |
1258 | SYSCALL_DEFINE1(uname, struct old_utsname __user *, name) | 1259 | SYSCALL_DEFINE1(uname, struct old_utsname __user *, name) |
1259 | { | 1260 | { |
1260 | int error = 0; | 1261 | struct old_utsname tmp; |
1261 | 1262 | ||
1262 | if (!name) | 1263 | if (!name) |
1263 | return -EFAULT; | 1264 | return -EFAULT; |
1264 | 1265 | ||
1265 | down_read(&uts_sem); | 1266 | down_read(&uts_sem); |
1266 | if (copy_to_user(name, utsname(), sizeof(*name))) | 1267 | memcpy(&tmp, utsname(), sizeof(tmp)); |
1267 | error = -EFAULT; | ||
1268 | up_read(&uts_sem); | 1268 | up_read(&uts_sem); |
1269 | if (copy_to_user(name, &tmp, sizeof(tmp))) | ||
1270 | return -EFAULT; | ||
1269 | 1271 | ||
1270 | if (!error && override_release(name->release, sizeof(name->release))) | 1272 | if (override_release(name->release, sizeof(name->release))) |
1271 | error = -EFAULT; | 1273 | return -EFAULT; |
1272 | if (!error && override_architecture(name)) | 1274 | if (override_architecture(name)) |
1273 | error = -EFAULT; | 1275 | return -EFAULT; |
1274 | return error; | 1276 | return 0; |
1275 | } | 1277 | } |
1276 | 1278 | ||
1277 | SYSCALL_DEFINE1(olduname, struct oldold_utsname __user *, name) | 1279 | SYSCALL_DEFINE1(olduname, struct oldold_utsname __user *, name) |
1278 | { | 1280 | { |
1279 | int error; | 1281 | struct oldold_utsname tmp = {}; |
1280 | 1282 | ||
1281 | if (!name) | 1283 | if (!name) |
1282 | return -EFAULT; | 1284 | return -EFAULT; |
1283 | if (!access_ok(VERIFY_WRITE, name, sizeof(struct oldold_utsname))) | ||
1284 | return -EFAULT; | ||
1285 | 1285 | ||
1286 | down_read(&uts_sem); | 1286 | down_read(&uts_sem); |
1287 | error = __copy_to_user(&name->sysname, &utsname()->sysname, | 1287 | memcpy(&tmp.sysname, &utsname()->sysname, __OLD_UTS_LEN); |
1288 | __OLD_UTS_LEN); | 1288 | memcpy(&tmp.nodename, &utsname()->nodename, __OLD_UTS_LEN); |
1289 | error |= __put_user(0, name->sysname + __OLD_UTS_LEN); | 1289 | memcpy(&tmp.release, &utsname()->release, __OLD_UTS_LEN); |
1290 | error |= __copy_to_user(&name->nodename, &utsname()->nodename, | 1290 | memcpy(&tmp.version, &utsname()->version, __OLD_UTS_LEN); |
1291 | __OLD_UTS_LEN); | 1291 | memcpy(&tmp.machine, &utsname()->machine, __OLD_UTS_LEN); |
1292 | error |= __put_user(0, name->nodename + __OLD_UTS_LEN); | ||
1293 | error |= __copy_to_user(&name->release, &utsname()->release, | ||
1294 | __OLD_UTS_LEN); | ||
1295 | error |= __put_user(0, name->release + __OLD_UTS_LEN); | ||
1296 | error |= __copy_to_user(&name->version, &utsname()->version, | ||
1297 | __OLD_UTS_LEN); | ||
1298 | error |= __put_user(0, name->version + __OLD_UTS_LEN); | ||
1299 | error |= __copy_to_user(&name->machine, &utsname()->machine, | ||
1300 | __OLD_UTS_LEN); | ||
1301 | error |= __put_user(0, name->machine + __OLD_UTS_LEN); | ||
1302 | up_read(&uts_sem); | 1292 | up_read(&uts_sem); |
1293 | if (copy_to_user(name, &tmp, sizeof(tmp))) | ||
1294 | return -EFAULT; | ||
1303 | 1295 | ||
1304 | if (!error && override_architecture(name)) | 1296 | if (override_architecture(name)) |
1305 | error = -EFAULT; | 1297 | return -EFAULT; |
1306 | if (!error && override_release(name->release, sizeof(name->release))) | 1298 | if (override_release(name->release, sizeof(name->release))) |
1307 | error = -EFAULT; | 1299 | return -EFAULT; |
1308 | return error ? -EFAULT : 0; | 1300 | return 0; |
1309 | } | 1301 | } |
1310 | #endif | 1302 | #endif |
1311 | 1303 | ||
@@ -1319,17 +1311,18 @@ SYSCALL_DEFINE2(sethostname, char __user *, name, int, len) | |||
1319 | 1311 | ||
1320 | if (len < 0 || len > __NEW_UTS_LEN) | 1312 | if (len < 0 || len > __NEW_UTS_LEN) |
1321 | return -EINVAL; | 1313 | return -EINVAL; |
1322 | down_write(&uts_sem); | ||
1323 | errno = -EFAULT; | 1314 | errno = -EFAULT; |
1324 | if (!copy_from_user(tmp, name, len)) { | 1315 | if (!copy_from_user(tmp, name, len)) { |
1325 | struct new_utsname *u = utsname(); | 1316 | struct new_utsname *u; |
1326 | 1317 | ||
1318 | down_write(&uts_sem); | ||
1319 | u = utsname(); | ||
1327 | memcpy(u->nodename, tmp, len); | 1320 | memcpy(u->nodename, tmp, len); |
1328 | memset(u->nodename + len, 0, sizeof(u->nodename) - len); | 1321 | memset(u->nodename + len, 0, sizeof(u->nodename) - len); |
1329 | errno = 0; | 1322 | errno = 0; |
1330 | uts_proc_notify(UTS_PROC_HOSTNAME); | 1323 | uts_proc_notify(UTS_PROC_HOSTNAME); |
1324 | up_write(&uts_sem); | ||
1331 | } | 1325 | } |
1332 | up_write(&uts_sem); | ||
1333 | return errno; | 1326 | return errno; |
1334 | } | 1327 | } |
1335 | 1328 | ||
@@ -1337,8 +1330,9 @@ SYSCALL_DEFINE2(sethostname, char __user *, name, int, len) | |||
1337 | 1330 | ||
1338 | SYSCALL_DEFINE2(gethostname, char __user *, name, int, len) | 1331 | SYSCALL_DEFINE2(gethostname, char __user *, name, int, len) |
1339 | { | 1332 | { |
1340 | int i, errno; | 1333 | int i; |
1341 | struct new_utsname *u; | 1334 | struct new_utsname *u; |
1335 | char tmp[__NEW_UTS_LEN + 1]; | ||
1342 | 1336 | ||
1343 | if (len < 0) | 1337 | if (len < 0) |
1344 | return -EINVAL; | 1338 | return -EINVAL; |
@@ -1347,11 +1341,11 @@ SYSCALL_DEFINE2(gethostname, char __user *, name, int, len) | |||
1347 | i = 1 + strlen(u->nodename); | 1341 | i = 1 + strlen(u->nodename); |
1348 | if (i > len) | 1342 | if (i > len) |
1349 | i = len; | 1343 | i = len; |
1350 | errno = 0; | 1344 | memcpy(tmp, u->nodename, i); |
1351 | if (copy_to_user(name, u->nodename, i)) | ||
1352 | errno = -EFAULT; | ||
1353 | up_read(&uts_sem); | 1345 | up_read(&uts_sem); |
1354 | return errno; | 1346 | if (copy_to_user(name, tmp, i)) |
1347 | return -EFAULT; | ||
1348 | return 0; | ||
1355 | } | 1349 | } |
1356 | 1350 | ||
1357 | #endif | 1351 | #endif |
@@ -1370,17 +1364,18 @@ SYSCALL_DEFINE2(setdomainname, char __user *, name, int, len) | |||
1370 | if (len < 0 || len > __NEW_UTS_LEN) | 1364 | if (len < 0 || len > __NEW_UTS_LEN) |
1371 | return -EINVAL; | 1365 | return -EINVAL; |
1372 | 1366 | ||
1373 | down_write(&uts_sem); | ||
1374 | errno = -EFAULT; | 1367 | errno = -EFAULT; |
1375 | if (!copy_from_user(tmp, name, len)) { | 1368 | if (!copy_from_user(tmp, name, len)) { |
1376 | struct new_utsname *u = utsname(); | 1369 | struct new_utsname *u; |
1377 | 1370 | ||
1371 | down_write(&uts_sem); | ||
1372 | u = utsname(); | ||
1378 | memcpy(u->domainname, tmp, len); | 1373 | memcpy(u->domainname, tmp, len); |
1379 | memset(u->domainname + len, 0, sizeof(u->domainname) - len); | 1374 | memset(u->domainname + len, 0, sizeof(u->domainname) - len); |
1380 | errno = 0; | 1375 | errno = 0; |
1381 | uts_proc_notify(UTS_PROC_DOMAINNAME); | 1376 | uts_proc_notify(UTS_PROC_DOMAINNAME); |
1377 | up_write(&uts_sem); | ||
1382 | } | 1378 | } |
1383 | up_write(&uts_sem); | ||
1384 | return errno; | 1379 | return errno; |
1385 | } | 1380 | } |
1386 | 1381 | ||
diff --git a/kernel/utsname_sysctl.c b/kernel/utsname_sysctl.c index 233cd8fc6910..258033d62cb3 100644 --- a/kernel/utsname_sysctl.c +++ b/kernel/utsname_sysctl.c | |||
@@ -18,7 +18,7 @@ | |||
18 | 18 | ||
19 | #ifdef CONFIG_PROC_SYSCTL | 19 | #ifdef CONFIG_PROC_SYSCTL |
20 | 20 | ||
21 | static void *get_uts(struct ctl_table *table, int write) | 21 | static void *get_uts(struct ctl_table *table) |
22 | { | 22 | { |
23 | char *which = table->data; | 23 | char *which = table->data; |
24 | struct uts_namespace *uts_ns; | 24 | struct uts_namespace *uts_ns; |
@@ -26,21 +26,9 @@ static void *get_uts(struct ctl_table *table, int write) | |||
26 | uts_ns = current->nsproxy->uts_ns; | 26 | uts_ns = current->nsproxy->uts_ns; |
27 | which = (which - (char *)&init_uts_ns) + (char *)uts_ns; | 27 | which = (which - (char *)&init_uts_ns) + (char *)uts_ns; |
28 | 28 | ||
29 | if (!write) | ||
30 | down_read(&uts_sem); | ||
31 | else | ||
32 | down_write(&uts_sem); | ||
33 | return which; | 29 | return which; |
34 | } | 30 | } |
35 | 31 | ||
36 | static void put_uts(struct ctl_table *table, int write, void *which) | ||
37 | { | ||
38 | if (!write) | ||
39 | up_read(&uts_sem); | ||
40 | else | ||
41 | up_write(&uts_sem); | ||
42 | } | ||
43 | |||
44 | /* | 32 | /* |
45 | * Special case of dostring for the UTS structure. This has locks | 33 | * Special case of dostring for the UTS structure. This has locks |
46 | * to observe. Should this be in kernel/sys.c ???? | 34 | * to observe. Should this be in kernel/sys.c ???? |
@@ -50,13 +38,34 @@ static int proc_do_uts_string(struct ctl_table *table, int write, | |||
50 | { | 38 | { |
51 | struct ctl_table uts_table; | 39 | struct ctl_table uts_table; |
52 | int r; | 40 | int r; |
41 | char tmp_data[__NEW_UTS_LEN + 1]; | ||
42 | |||
53 | memcpy(&uts_table, table, sizeof(uts_table)); | 43 | memcpy(&uts_table, table, sizeof(uts_table)); |
54 | uts_table.data = get_uts(table, write); | 44 | uts_table.data = tmp_data; |
45 | |||
46 | /* | ||
47 | * Buffer the value in tmp_data so that proc_dostring() can be called | ||
48 | * without holding any locks. | ||
49 | * We also need to read the original value in the write==1 case to | ||
50 | * support partial writes. | ||
51 | */ | ||
52 | down_read(&uts_sem); | ||
53 | memcpy(tmp_data, get_uts(table), sizeof(tmp_data)); | ||
54 | up_read(&uts_sem); | ||
55 | r = proc_dostring(&uts_table, write, buffer, lenp, ppos); | 55 | r = proc_dostring(&uts_table, write, buffer, lenp, ppos); |
56 | put_uts(table, write, uts_table.data); | ||
57 | 56 | ||
58 | if (write) | 57 | if (write) { |
58 | /* | ||
59 | * Write back the new value. | ||
60 | * Note that, since we dropped uts_sem, the result can | ||
61 | * theoretically be incorrect if there are two parallel writes | ||
62 | * at non-zero offsets to the same sysctl. | ||
63 | */ | ||
64 | down_write(&uts_sem); | ||
65 | memcpy(get_uts(table), tmp_data, sizeof(tmp_data)); | ||
66 | up_write(&uts_sem); | ||
59 | proc_sys_poll_notify(table->poll); | 67 | proc_sys_poll_notify(table->poll); |
68 | } | ||
60 | 69 | ||
61 | return r; | 70 | return r; |
62 | } | 71 | } |