|
29 | 29 | #include <stdarg.h> |
30 | 30 | #include <errno.h> |
31 | 31 | #include <fcntl.h> |
| 32 | +#include <limits.h> |
| 33 | +#include <grp.h> |
32 | 34 | #include <pwd.h> |
| 35 | +#include <stdint.h> |
33 | 36 | #include <stdio.h> |
34 | 37 | #include <stdlib.h> |
35 | 38 | #include <string.h> |
@@ -148,9 +151,9 @@ static int mu_restore_root(FAR struct mu_ctx_s *ctx) |
148 | 151 |
|
149 | 152 | static int mu_set_effective(FAR struct mu_ctx_s *ctx, uid_t uid, gid_t gid) |
150 | 153 | { |
151 | | - /* NuttX grants arbitrary seteuid/setegid only while the effective ID |
152 | | - * is 0. With real UID 0 (flat NSH), restore effective root before |
153 | | - * switching user, matching nsh_switch_credentials(). |
| 154 | + /* seteuid/setegid allow arbitrary IDs only while euid is 0. With |
| 155 | + * real UID 0, restore euid 0 before switching, matching |
| 156 | + * nsh_switch_credentials(). |
154 | 157 | */ |
155 | 158 |
|
156 | 159 | if (getuid() == 0 && (geteuid() != 0 || getegid() != 0)) |
@@ -322,6 +325,200 @@ static int multiuser_effective_test(FAR struct mu_ctx_s *ctx) |
322 | 325 | return ctx->failures; |
323 | 326 | } |
324 | 327 |
|
| 328 | +#if CONFIG_SCHED_NGROUPS > 0 |
| 329 | +static int multiuser_groups_test(FAR struct mu_ctx_s *ctx) |
| 330 | +{ |
| 331 | + gid_t list[NGROUPS_MAX]; |
| 332 | + gid_t got[NGROUPS_MAX]; |
| 333 | + int n; |
| 334 | + int ret; |
| 335 | + |
| 336 | + printf("multiuser: setgroups/getgroups supplementary IDs\n"); |
| 337 | + |
| 338 | + list[0] = MU_GID2; |
| 339 | + list[1] = (gid_t)(MU_GID2 + 1); |
| 340 | + |
| 341 | + ret = setgroups(2, list); |
| 342 | + if (mu_expect_ok(ctx, "setgroups(2)", ret) != 0) |
| 343 | + { |
| 344 | + return ctx->failures; |
| 345 | + } |
| 346 | + |
| 347 | + n = getgroups(0, NULL); |
| 348 | + mu_check_eq(ctx, "getgroups(0) count", n, 2); |
| 349 | + |
| 350 | + n = getgroups(NGROUPS_MAX, got); |
| 351 | + mu_check_eq(ctx, "getgroups() count", n, 2); |
| 352 | + if (n >= 2) |
| 353 | + { |
| 354 | + mu_check_eq(ctx, "getgroups()[0]", got[0], list[0]); |
| 355 | + mu_check_eq(ctx, "getgroups()[1]", got[1], list[1]); |
| 356 | + } |
| 357 | + |
| 358 | + ret = setgroups(0, NULL); |
| 359 | + if (mu_expect_ok(ctx, "setgroups(0) clear", ret) != 0) |
| 360 | + { |
| 361 | + return ctx->failures; |
| 362 | + } |
| 363 | + |
| 364 | + n = getgroups(0, NULL); |
| 365 | + mu_check_eq(ctx, "getgroups after clear", n, 0); |
| 366 | + |
| 367 | + /* setgroups rejects size > NGROUPS_MAX (no silent truncate). */ |
| 368 | + |
| 369 | + ret = setgroups(NGROUPS_MAX + 1, list); |
| 370 | + if (ret != 0 && errno == EINVAL) |
| 371 | + { |
| 372 | + mu_pass("setgroups(NGROUPS_MAX+1) rejected errno=EINVAL"); |
| 373 | + } |
| 374 | + else |
| 375 | + { |
| 376 | + mu_fail(ctx, "setgroups(NGROUPS_MAX+1): ret=%d errno=%d " |
| 377 | + "(expected EINVAL)", ret, errno); |
| 378 | + } |
| 379 | + |
| 380 | +#if defined(CONFIG_LIBC_GROUP_FILE) |
| 381 | + /* getgrouplist/initgroups fail (do not truncate) when membership > |
| 382 | + * NGROUPS_MAX. Temporarily replace the group file for this check. |
| 383 | + */ |
| 384 | + |
| 385 | + { |
| 386 | + char bak[128]; |
| 387 | + FILE *fp; |
| 388 | + int i; |
| 389 | + int need; |
| 390 | + gid_t gbuf[NGROUPS_MAX]; |
| 391 | + int ng; |
| 392 | + int saved_errno; |
| 393 | + |
| 394 | + snprintf(bak, sizeof(bak), "%s.bak", CONFIG_LIBC_GROUP_FILEPATH); |
| 395 | + unlink(bak); |
| 396 | + rename(CONFIG_LIBC_GROUP_FILEPATH, bak); |
| 397 | + |
| 398 | + fp = fopen(CONFIG_LIBC_GROUP_FILEPATH, "w"); |
| 399 | + if (fp == NULL) |
| 400 | + { |
| 401 | + mu_fail(ctx, "fopen(%s) for overflow test", |
| 402 | + CONFIG_LIBC_GROUP_FILEPATH); |
| 403 | + rename(bak, CONFIG_LIBC_GROUP_FILEPATH); |
| 404 | + return ctx->failures; |
| 405 | + } |
| 406 | + |
| 407 | + /* Primary MU_GID1 plus NGROUPS_MAX distinct supplementary gids. */ |
| 408 | + |
| 409 | + for (i = 0; i < NGROUPS_MAX; i++) |
| 410 | + { |
| 411 | + fprintf(fp, "g%d:*:%d:mu_overflow\n", i, (int)(MU_GID2 + i)); |
| 412 | + } |
| 413 | + |
| 414 | + fclose(fp); |
| 415 | + |
| 416 | + ng = NGROUPS_MAX; |
| 417 | + need = getgrouplist("mu_overflow", MU_GID1, gbuf, &ng); |
| 418 | + mu_check_eq(ctx, "getgrouplist overflow returns -1", need, -1); |
| 419 | + mu_check_eq(ctx, "getgrouplist reports required size", ng, |
| 420 | + NGROUPS_MAX + 1); |
| 421 | + |
| 422 | + saved_errno = 0; |
| 423 | + ret = initgroups("mu_overflow", MU_GID1); |
| 424 | + if (ret == 0) |
| 425 | + { |
| 426 | + mu_fail(ctx, "initgroups should fail when groups > NGROUPS_MAX"); |
| 427 | + } |
| 428 | + else |
| 429 | + { |
| 430 | + saved_errno = errno; |
| 431 | + mu_pass("initgroups fails when groups > NGROUPS_MAX (errno=%d)", |
| 432 | + saved_errno); |
| 433 | + } |
| 434 | + |
| 435 | + unlink(CONFIG_LIBC_GROUP_FILEPATH); |
| 436 | + rename(bak, CONFIG_LIBC_GROUP_FILEPATH); |
| 437 | + } |
| 438 | +#endif /* CONFIG_LIBC_GROUP_FILE */ |
| 439 | + |
| 440 | + return ctx->failures; |
| 441 | +} |
| 442 | +#endif /* CONFIG_SCHED_NGROUPS > 0 */ |
| 443 | + |
| 444 | +/**************************************************************************** |
| 445 | + * Name: multiuser_setres_order_test |
| 446 | + * |
| 447 | + * Description: |
| 448 | + * Regression: setresgid requires euid==0. Dropping uid before gid fails; |
| 449 | + * gid-then-uid succeeds (NSH assume_identity order). |
| 450 | + * |
| 451 | + ****************************************************************************/ |
| 452 | + |
| 453 | +static int multiuser_setres_order_test(FAR struct mu_ctx_s *ctx) |
| 454 | +{ |
| 455 | + uid_t ruid; |
| 456 | + uid_t euid; |
| 457 | + uid_t suid; |
| 458 | + gid_t rgid; |
| 459 | + gid_t egid; |
| 460 | + gid_t sgid; |
| 461 | + int ret; |
| 462 | + |
| 463 | + printf("multiuser: setresuid/setresgid drop ordering\n"); |
| 464 | + |
| 465 | + mu_restore_root(ctx); |
| 466 | + |
| 467 | + /* uid-first must fail once euid is no longer 0. */ |
| 468 | + |
| 469 | + ret = setresuid(MU_UID1, MU_UID1, 0); |
| 470 | + if (mu_expect_ok(ctx, "setresuid(1000,1000,0) first", ret) != 0) |
| 471 | + { |
| 472 | + mu_restore_root(ctx); |
| 473 | + return ctx->failures; |
| 474 | + } |
| 475 | + |
| 476 | + ret = setresgid(MU_GID1, MU_GID1, 0); |
| 477 | + mu_expect_denied(ctx, "setresgid after uid drop (wrong order)", ret); |
| 478 | + |
| 479 | + ret = seteuid(0); |
| 480 | + if (mu_expect_ok(ctx, "seteuid(0) via suid after bad order", ret) != 0) |
| 481 | + { |
| 482 | + mu_restore_root(ctx); |
| 483 | + return ctx->failures; |
| 484 | + } |
| 485 | + |
| 486 | + ret = setresuid(0, 0, 0); |
| 487 | + mu_expect_ok(ctx, "setresuid(0,0,0) reset", ret); |
| 488 | + ret = setresgid(0, 0, 0); |
| 489 | + mu_expect_ok(ctx, "setresgid(0,0,0) reset", ret); |
| 490 | + |
| 491 | + /* gid-then-uid (correct order) must succeed. */ |
| 492 | + |
| 493 | + ret = setresgid(MU_GID1, MU_GID1, 0); |
| 494 | + if (mu_expect_ok(ctx, "setresgid(1000,1000,0) first", ret) != 0) |
| 495 | + { |
| 496 | + mu_restore_root(ctx); |
| 497 | + return ctx->failures; |
| 498 | + } |
| 499 | + |
| 500 | + ret = setresuid(MU_UID1, MU_UID1, 0); |
| 501 | + if (mu_expect_ok(ctx, "setresuid(1000,1000,0) second", ret) != 0) |
| 502 | + { |
| 503 | + mu_restore_root(ctx); |
| 504 | + return ctx->failures; |
| 505 | + } |
| 506 | + |
| 507 | + getresuid(&ruid, &euid, &suid); |
| 508 | + getresgid(&rgid, &egid, &sgid); |
| 509 | + mu_check_eq(ctx, "ruid after gid-then-uid", ruid, MU_UID1); |
| 510 | + mu_check_eq(ctx, "euid after gid-then-uid", euid, MU_UID1); |
| 511 | + mu_check_eq(ctx, "suid after gid-then-uid", suid, 0); |
| 512 | + mu_check_eq(ctx, "rgid after gid-then-uid", rgid, MU_GID1); |
| 513 | + mu_check_eq(ctx, "egid after gid-then-uid", egid, MU_GID1); |
| 514 | + mu_check_eq(ctx, "sgid after gid-then-uid", sgid, 0); |
| 515 | + |
| 516 | + mu_restore_root(ctx); |
| 517 | + setresuid(0, 0, 0); |
| 518 | + setresgid(0, 0, 0); |
| 519 | + return ctx->failures; |
| 520 | +} |
| 521 | + |
325 | 522 | static int multiuser_resuid_test(FAR struct mu_ctx_s *ctx) |
326 | 523 | { |
327 | 524 | uid_t ruid; |
@@ -798,7 +995,7 @@ static int multiuser_mqueue_test(FAR struct mu_ctx_s *ctx) |
798 | 995 |
|
799 | 996 | memset(&attr, 0, sizeof(attr)); |
800 | 997 | attr.mq_maxmsg = 4; |
801 | | - attr.mq_msgsize = 64; |
| 998 | + attr.mq_msgsize = CONFIG_MQ_MAXMSGSIZE; |
802 | 999 |
|
803 | 1000 | if (mu_set_effective(ctx, MU_UID1, MU_GID1) != 0) |
804 | 1001 | { |
@@ -1146,6 +1343,13 @@ int multiuser_test(void) |
1146 | 1343 | printf("multiuser_test: start\n"); |
1147 | 1344 |
|
1148 | 1345 | multiuser_effective_test(&ctx); |
| 1346 | +#if CONFIG_SCHED_NGROUPS > 0 |
| 1347 | + multiuser_groups_test(&ctx); |
| 1348 | +#else |
| 1349 | + printf("multiuser: skipping supplementary groups test " |
| 1350 | + "(need CONFIG_SCHED_NGROUPS > 0)\n"); |
| 1351 | +#endif |
| 1352 | + multiuser_setres_order_test(&ctx); |
1149 | 1353 | multiuser_resuid_test(&ctx); |
1150 | 1354 |
|
1151 | 1355 | #if defined(CONFIG_SCHED_WAITPID) && !defined(CONFIG_BUILD_KERNEL) |
|
0 commit comments