pam_modutil_priv.c 4.1 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179
  1. /*
  2. * $Id$
  3. *
  4. * This file provides two functions:
  5. * pam_modutil_drop_priv:
  6. * temporarily lower process fs privileges by switching to another uid/gid,
  7. * pam_modutil_regain_priv:
  8. * regain process fs privileges lowered by pam_modutil_drop_priv().
  9. */
  10. #include "pam_modutil_private.h"
  11. #include <security/pam_ext.h>
  12. #include <unistd.h>
  13. #include <syslog.h>
  14. #include <pwd.h>
  15. #include <grp.h>
  16. #include <sys/fsuid.h>
  17. /*
  18. * Two setfsuid() calls in a row are necessary to check
  19. * whether setfsuid() succeeded or not.
  20. */
  21. static int change_uid(uid_t uid, uid_t *save)
  22. {
  23. uid_t tmp = setfsuid(uid);
  24. if (save)
  25. *save = tmp;
  26. return (uid_t) setfsuid(uid) == uid ? 0 : -1;
  27. }
  28. static int change_gid(gid_t gid, gid_t *save)
  29. {
  30. gid_t tmp = setfsgid(gid);
  31. if (save)
  32. *save = tmp;
  33. return (gid_t) setfsgid(gid) == gid ? 0 : -1;
  34. }
  35. static int cleanup(struct pam_modutil_privs *p)
  36. {
  37. if (p->allocated) {
  38. p->allocated = 0;
  39. free(p->grplist);
  40. }
  41. p->grplist = NULL;
  42. p->number_of_groups = 0;
  43. return -1;
  44. }
  45. #define PRIV_MAGIC 0x1004000a
  46. #define PRIV_MAGIC_DONOTHING 0xdead000a
  47. int pam_modutil_drop_priv(pam_handle_t *pamh,
  48. struct pam_modutil_privs *p,
  49. const struct passwd *pw)
  50. {
  51. int res;
  52. if (p->is_dropped) {
  53. pam_syslog(pamh, LOG_CRIT,
  54. "pam_modutil_drop_priv: called with dropped privileges");
  55. return -1;
  56. }
  57. /*
  58. * If not root, we can do nothing.
  59. * If switching to root, we have nothing to do.
  60. * That is, in both cases, we do not care.
  61. */
  62. if (geteuid() != 0 || pw->pw_uid == 0) {
  63. p->is_dropped = PRIV_MAGIC_DONOTHING;
  64. return 0;
  65. }
  66. if (!p->grplist || p->number_of_groups <= 0) {
  67. pam_syslog(pamh, LOG_CRIT,
  68. "pam_modutil_drop_priv: called without room for supplementary groups");
  69. return -1;
  70. }
  71. res = getgroups(0, NULL);
  72. if (res < 0) {
  73. pam_syslog(pamh, LOG_ERR,
  74. "pam_modutil_drop_priv: getgroups failed: %m");
  75. return -1;
  76. }
  77. p->allocated = 0;
  78. if (res > p->number_of_groups) {
  79. p->grplist = calloc(res, sizeof(gid_t));
  80. if (!p->grplist) {
  81. pam_syslog(pamh, LOG_CRIT, "out of memory");
  82. return cleanup(p);
  83. }
  84. p->allocated = 1;
  85. p->number_of_groups = res;
  86. }
  87. res = getgroups(p->number_of_groups, p->grplist);
  88. if (res < 0) {
  89. pam_syslog(pamh, LOG_ERR,
  90. "pam_modutil_drop_priv: getgroups failed: %m");
  91. return cleanup(p);
  92. }
  93. p->number_of_groups = res;
  94. /*
  95. * We should care to leave process credentials in consistent state.
  96. * That is, e.g. if change_gid() succeeded but change_uid() failed,
  97. * we should try to restore old gid.
  98. *
  99. * We try to add the supplementary groups on a best-effort
  100. * basis. If it fails, it's not fatal: we fall back to using an
  101. * empty list.
  102. */
  103. if (initgroups(pw->pw_name, pw->pw_gid)) {
  104. pam_syslog(pamh, LOG_WARNING,
  105. "pam_modutil_drop_priv: initgroups failed: %m");
  106. if (setgroups(0, NULL)) {
  107. pam_syslog(pamh, LOG_ERR,
  108. "pam_modutil_drop_priv: setgroups failed: %m");
  109. return cleanup(p);
  110. }
  111. }
  112. if (change_gid(pw->pw_gid, &p->old_gid)) {
  113. pam_syslog(pamh, LOG_ERR,
  114. "pam_modutil_drop_priv: change_gid failed: %m");
  115. (void) setgroups(p->number_of_groups, p->grplist);
  116. return cleanup(p);
  117. }
  118. if (change_uid(pw->pw_uid, &p->old_uid)) {
  119. pam_syslog(pamh, LOG_ERR,
  120. "pam_modutil_drop_priv: change_uid failed: %m");
  121. (void) change_gid(p->old_gid, NULL);
  122. (void) setgroups(p->number_of_groups, p->grplist);
  123. return cleanup(p);
  124. }
  125. p->is_dropped = PRIV_MAGIC;
  126. return 0;
  127. }
  128. int pam_modutil_regain_priv(pam_handle_t *pamh,
  129. struct pam_modutil_privs *p)
  130. {
  131. switch (p->is_dropped) {
  132. case PRIV_MAGIC_DONOTHING:
  133. p->is_dropped = 0;
  134. return 0;
  135. case PRIV_MAGIC:
  136. break;
  137. default:
  138. pam_syslog(pamh, LOG_CRIT,
  139. "pam_modutil_regain_priv: called with invalid state");
  140. return -1;
  141. }
  142. if (change_uid(p->old_uid, NULL)) {
  143. pam_syslog(pamh, LOG_ERR,
  144. "pam_modutil_regain_priv: change_uid failed: %m");
  145. return cleanup(p);
  146. }
  147. if (change_gid(p->old_gid, NULL)) {
  148. pam_syslog(pamh, LOG_ERR,
  149. "pam_modutil_regain_priv: change_gid failed: %m");
  150. return cleanup(p);
  151. }
  152. if (setgroups(p->number_of_groups, p->grplist)) {
  153. pam_syslog(pamh, LOG_ERR,
  154. "pam_modutil_regain_priv: setgroups failed: %m");
  155. return cleanup(p);
  156. }
  157. p->is_dropped = 0;
  158. cleanup(p);
  159. return 0;
  160. }