nss: look for databases in /usr/share/nss/ if they don't exist in /etc/

Message ID ZvqeBRsAWe14_kJ6@gardel-login
State Under Review
Delegated to: Florian Weimer
Headers
Series nss: look for databases in /usr/share/nss/ if they don't exist in /etc/ |

Checks

Context Check Description
redhat-pt-bot/TryBot-apply_patch success Patch applied to master at the time it was sent
redhat-pt-bot/TryBot-32bit success Build for i686
linaro-tcwg-bot/tcwg_glibc_build--master-arm success Build passed
linaro-tcwg-bot/tcwg_glibc_build--master-aarch64 success Build passed
linaro-tcwg-bot/tcwg_glibc_check--master-arm success Test passed
linaro-tcwg-bot/tcwg_glibc_check--master-aarch64 success Test passed

Commit Message

Lennart Poettering Sept. 30, 2024, 12:48 p.m. UTC
  In order to improve compatibility with systems that implement a hermetic
/usr/, this changes glibc NSS code to always look for its databases
files in /usr/share/nss/ too in case they don't exist (ENOENT) in /etc/.
This allows distributions to move /etc/protocols and similar data files
into /usr/share/nss/. These days various of these files are kinda static
anyway, and hence in many cases are better placed below the /usr/
hierarchy than below the configurable /etc/.

This change should have zero effect on existing systems, as any database
file in /etc/ will be consulted first, and only on ENOENT the matching
counterpart in /usr/share/nss/ will be attempted. This also provides a clear
path how administrators can still modify the files locally if they want
to: just copy them from /usr/share/nss/ to /etc/ and edit them there. In
that case the files in /usr/share/nss/ will have no effect anymore.

Many systems that want to achieve this behaviour currently work around
glibc's behaviour via modules such as nss-altfiles, an excercise that
becomes unnecessary if glibc just directly looks at these fallback
places. (Note that nss-altfiles has different semantics though, it
merges rather then replaces the files)

Also see:

https://github.com/uapi-group/specifications/issues/76

(2nd version of the patch: moved from /usr/share/ to /usr/share/nss/,
and I am now generating these two paths via stpcpy()/strcpy().)

(3rd version of the patch: fallback on more errnos than just ENOENT.)
---
 include/nss_files.h                |  8 ++++--
 nss/Versions                       |  2 +-
 nss/nss_compat/compat-grp.c        |  2 +-
 nss/nss_compat/compat-initgroups.c |  2 +-
 nss/nss_compat/compat-pwd.c        |  2 +-
 nss/nss_compat/compat-spwd.c       |  2 +-
 nss/nss_files/files-XXX.c          |  8 +++---
 nss/nss_files/files-alias.c        |  7 +++---
 nss/nss_files/files-initgroups.c   |  2 +-
 nss/nss_files/files-netgrp.c       |  4 +--
 nss/nss_files_data.c               | 12 ++++-----
 nss/nss_files_fopen.c              | 40 ++++++++++++++++++++++++++++++
 12 files changed, 67 insertions(+), 24 deletions(-)

--
2.46.0
  

Comments

Florian Weimer Sept. 30, 2024, 1:38 p.m. UTC | #1
* Lennart Poettering:

> In order to improve compatibility with systems that implement a hermetic
> /usr/, this changes glibc NSS code to always look for its databases
> files in /usr/share/nss/ too in case they don't exist (ENOENT) in /etc/.
> This allows distributions to move /etc/protocols and similar data files
> into /usr/share/nss/. These days various of these files are kinda static
> anyway, and hence in many cases are better placed below the /usr/
> hierarchy than below the configurable /etc/.
>
> This change should have zero effect on existing systems, as any database
> file in /etc/ will be consulted first, and only on ENOENT the matching
> counterpart in /usr/share/nss/ will be attempted. This also provides a clear
> path how administrators can still modify the files locally if they want
> to: just copy them from /usr/share/nss/ to /etc/ and edit them there. In
> that case the files in /usr/share/nss/ will have no effect anymore.
>
> Many systems that want to achieve this behaviour currently work around
> glibc's behaviour via modules such as nss-altfiles, an excercise that
> becomes unnecessary if glibc just directly looks at these fallback
> places. (Note that nss-altfiles has different semantics though, it
> merges rather then replaces the files)
>
> Also see:
>
> https://github.com/uapi-group/specifications/issues/76
>
> (2nd version of the patch: moved from /usr/share/ to /usr/share/nss/,
> and I am now generating these two paths via stpcpy()/strcpy().)
>
> (3rd version of the patch: fallback on more errnos than just ENOENT.)

Sorry, could you spin out __nss_files_fopen_database into a separate
patch because it's just a refactoring?  I can do it for you if it's too
much work.  This can go in right away while we wait if there's some more
feedback for the actual fallback code.

Thanks,
Florian
  

Patch

diff --git a/include/nss_files.h b/include/nss_files.h
index adf934f3ea..c224dabe5f 100644
--- a/include/nss_files.h
+++ b/include/nss_files.h
@@ -25,6 +25,10 @@ 
 #include <libc-lock.h>
 #endif

+/* Open DATABASE for reading.  */
+FILE *__nss_files_fopen_database (const char *database);
+libc_hidden_proto (__nss_files_fopen_database)
+
 /* Open PATH for reading, as a data source for nss_files.  */
 FILE *__nss_files_fopen (const char *path);
 libc_hidden_proto (__nss_files_fopen)
@@ -89,7 +93,7 @@  enum nss_files_file
    null.  */
 enum nss_status __nss_files_data_open (struct nss_files_per_file_data **pdata,
                                        enum nss_files_file file,
-                                       const char *path,
+                                       const char *database,
                                        int *errnop, int *herrnop);
 libc_hidden_proto (__nss_files_data_open)

@@ -101,7 +105,7 @@  libc_hidden_proto (__nss_files_data_put)
 /* Performs the set*ent operation for FILE.  PATH is the file to
    open.  */
 enum nss_status __nss_files_data_setent (enum nss_files_file file,
-                                           const char *path);
+                                         const char *database);
 libc_hidden_proto (__nss_files_data_setent)

 /* Performs the end*ent operation for FILE.  */
diff --git a/nss/Versions b/nss/Versions
index d765e1d3b6..9ea298cec0 100644
--- a/nss/Versions
+++ b/nss/Versions
@@ -108,7 +108,7 @@  libc {
     __nss_passwd_lookup2; __nss_group_lookup2; __nss_hosts_lookup2;
     __nss_services_lookup2; __nss_next2; __nss_lookup;
     __nss_hash; __nss_database_get;
-    __nss_files_fopen; __nss_readline; __nss_parse_line_result;
+    __nss_files_fopen; __nss_files_fopen_database; __nss_readline; __nss_parse_line_result;
     __nss_files_data_endent;
     __nss_files_data_open;
     __nss_files_data_put;
diff --git a/nss/nss_compat/compat-grp.c b/nss/nss_compat/compat-grp.c
index ede7503de7..3a9dcedb6a 100644
--- a/nss/nss_compat/compat-grp.c
+++ b/nss/nss_compat/compat-grp.c
@@ -108,7 +108,7 @@  internal_setgrent (ent_t *ent, int stayopen, int needent)

   if (ent->stream == NULL)
     {
-      ent->stream = __nss_files_fopen ("/etc/group");
+      ent->stream = __nss_files_fopen_database ("group");

       if (ent->stream == NULL)
 	status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
diff --git a/nss/nss_compat/compat-initgroups.c b/nss/nss_compat/compat-initgroups.c
index 2598cfbc9a..2acef020ef 100644
--- a/nss/nss_compat/compat-initgroups.c
+++ b/nss/nss_compat/compat-initgroups.c
@@ -122,7 +122,7 @@  internal_setgrent (ent_t *ent)
   else
     ent->blacklist.current = 0;

-  ent->stream = __nss_files_fopen ("/etc/group");
+  ent->stream = __nss_files_fopen_database ("group");

   if (ent->stream == NULL)
     status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
diff --git a/nss/nss_compat/compat-pwd.c b/nss/nss_compat/compat-pwd.c
index 2f37e0f621..a7220dcd58 100644
--- a/nss/nss_compat/compat-pwd.c
+++ b/nss/nss_compat/compat-pwd.c
@@ -223,7 +223,7 @@  internal_setpwent (ent_t *ent, int stayopen, int needent)

   if (ent->stream == NULL)
     {
-      ent->stream = __nss_files_fopen ("/etc/passwd");
+      ent->stream = __nss_files_fopen_database ("passwd");

       if (ent->stream == NULL)
 	status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
diff --git a/nss/nss_compat/compat-spwd.c b/nss/nss_compat/compat-spwd.c
index fac1e766cb..a67edb0ea8 100644
--- a/nss/nss_compat/compat-spwd.c
+++ b/nss/nss_compat/compat-spwd.c
@@ -178,7 +178,7 @@  internal_setspent (ent_t *ent, int stayopen, int needent)

   if (ent->stream == NULL)
     {
-      ent->stream = __nss_files_fopen ("/etc/shadow");
+      ent->stream = __nss_files_fopen_database ("shadow");

       if (ent->stream == NULL)
 	status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
diff --git a/nss/nss_files/files-XXX.c b/nss/nss_files/files-XXX.c
index 558b2c364f..8537bb4410 100644
--- a/nss/nss_files/files-XXX.c
+++ b/nss/nss_files/files-XXX.c
@@ -39,8 +39,6 @@ 

 #define ENTNAME_r	CONCAT(ENTNAME,_r)

-#define DATAFILE	"/etc/" DATABASE
-
 #ifdef NEED_H_ERRNO
 # include <netdb.h>
 # define H_ERRNO_PROTO	, int *herrnop
@@ -73,7 +71,7 @@  internal_setent (FILE **stream)

   if (*stream == NULL)
     {
-      *stream = __nss_files_fopen (DATAFILE);
+      *stream = __nss_files_fopen_database (DATABASE);

       if (*stream == NULL)
 	status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
@@ -89,7 +87,7 @@  internal_setent (FILE **stream)
 enum nss_status
 CONCAT(_nss_files_set,ENTNAME) (int stayopen)
 {
-  return __nss_files_data_setent (CONCAT (nss_file_, ENTNAME), DATAFILE);
+  return __nss_files_data_setent (CONCAT (nss_file_, ENTNAME), DATABASE);
 }
 libc_hidden_def (CONCAT (_nss_files_set,ENTNAME))

@@ -170,7 +168,7 @@  CONCAT(_nss_files_get,ENTNAME_r) (struct STRUCTURE *result, char *buffer,
   struct nss_files_per_file_data *data;
   enum nss_status status = __nss_files_data_open (&data,
 						  CONCAT (nss_file_, ENTNAME),
-						  DATAFILE,
+						  DATABASE,
 						  errnop, H_ERRNO_ARG_OR_NULL);
   if (status != NSS_STATUS_SUCCESS)
     return status;
diff --git a/nss/nss_files/files-alias.c b/nss/nss_files/files-alias.c
index 14a59b4655..961e248317 100644
--- a/nss/nss_files/files-alias.c
+++ b/nss/nss_files/files-alias.c
@@ -42,7 +42,7 @@  internal_setent (FILE **stream)

   if (*stream == NULL)
     {
-      *stream = __nss_files_fopen ("/etc/aliases");
+      *stream = __nss_files_fopen_database ("aliases");

       if (*stream == NULL)
 	status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
@@ -58,7 +58,7 @@  internal_setent (FILE **stream)
 enum nss_status
 _nss_files_setaliasent (void)
 {
-  return __nss_files_data_setent (nss_file_aliasent, "/etc/aliases");
+  return __nss_files_data_setent (nss_file_aliasent, "aliases");
 }
 libc_hidden_def (_nss_files_setaliasent)

@@ -338,7 +338,8 @@  _nss_files_getaliasent_r (struct aliasent *result, char *buffer, size_t buflen,

   struct nss_files_per_file_data *data;
   enum nss_status status = __nss_files_data_open (&data, nss_file_aliasent,
-						  "/etc/aliases", errnop, NULL);
+						  "aliases",
+                                                  errnop, NULL);
   if (status != NSS_STATUS_SUCCESS)
     return status;

diff --git a/nss/nss_files/files-initgroups.c b/nss/nss_files/files-initgroups.c
index 65189d3929..bec3a293c5 100644
--- a/nss/nss_files/files-initgroups.c
+++ b/nss/nss_files/files-initgroups.c
@@ -33,7 +33,7 @@  _nss_files_initgroups_dyn (const char *user, gid_t group, long int *start,
 			   long int *size, gid_t **groupsp, long int limit,
 			   int *errnop)
 {
-  FILE *stream = __nss_files_fopen ("/etc/group");
+  FILE *stream = __nss_files_fopen_database ("group");
   if (stream == NULL)
     {
       *errnop = errno;
diff --git a/nss/nss_files/files-netgrp.c b/nss/nss_files/files-netgrp.c
index 92d8062e43..8c8ad6c21f 100644
--- a/nss/nss_files/files-netgrp.c
+++ b/nss/nss_files/files-netgrp.c
@@ -27,7 +27,7 @@ 
 #include "netgroup.h"
 #include <nss_files.h>

-#define DATAFILE	"/etc/netgroup"
+#define DATABASE        "netgroup"

 libc_hidden_proto (_nss_files_endnetgrent)

@@ -62,7 +62,7 @@  _nss_files_setnetgrent (const char *group, struct __netgrent *result)
     return NSS_STATUS_UNAVAIL;

   /* Find the netgroups file and open it.  */
-  fp = __nss_files_fopen (DATAFILE);
+  fp = __nss_files_fopen_database (DATABASE);
   if (fp == NULL)
     status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
   else
diff --git a/nss/nss_files_data.c b/nss/nss_files_data.c
index 261a01a775..1fab3b03b5 100644
--- a/nss/nss_files_data.c
+++ b/nss/nss_files_data.c
@@ -74,13 +74,13 @@  __nss_files_data_get (struct nss_files_per_file_data **pdata,
 /* Helper function for opening the backing file at PATH.  */
 static enum nss_status
 __nss_files_data_internal_open (struct nss_files_per_file_data *data,
-                                const char *path)
+                                const char *database)
 {
   enum nss_status status = NSS_STATUS_SUCCESS;

   if (data->stream == NULL)
     {
-      data->stream = __nss_files_fopen (path);
+      data->stream = __nss_files_fopen_database (database);

       if (data->stream == NULL)
         status = errno == EAGAIN ? NSS_STATUS_TRYAGAIN : NSS_STATUS_UNAVAIL;
@@ -92,7 +92,7 @@  __nss_files_data_internal_open (struct nss_files_per_file_data *data,

 enum nss_status
 __nss_files_data_open (struct nss_files_per_file_data **pdata,
-                       enum nss_files_file file, const char *path,
+                       enum nss_files_file file, const char *database,
                        int *errnop, int *herrnop)
 {
   enum nss_status status = __nss_files_data_get (pdata, file, errnop, herrnop);
@@ -103,7 +103,7 @@  __nss_files_data_open (struct nss_files_per_file_data **pdata,
   if ((*pdata)->stream == NULL)
     {
       int saved_errno = errno;
-      status = __nss_files_data_internal_open (*pdata, path);
+      status = __nss_files_data_internal_open (*pdata, database);
       __set_errno (saved_errno);
       if (status != NSS_STATUS_SUCCESS)
         __nss_files_data_put (*pdata);
@@ -122,7 +122,7 @@  __nss_files_data_put (struct nss_files_per_file_data *data)
 libc_hidden_def (__nss_files_data_put)

 enum nss_status
-__nss_files_data_setent (enum nss_files_file file, const char *path)
+__nss_files_data_setent (enum nss_files_file file, const char *database)
 {
   struct nss_files_per_file_data *data;
   enum nss_status status = __nss_files_data_get (&data, file, NULL, NULL);
@@ -130,7 +130,7 @@  __nss_files_data_setent (enum nss_files_file file, const char *path)
     return status;

   if (data->stream == NULL)
-    status = __nss_files_data_internal_open (data, path);
+    status = __nss_files_data_internal_open (data, database);
   else
     rewind (data->stream);

diff --git a/nss/nss_files_fopen.c b/nss/nss_files_fopen.c
index e7c48d7bd8..307d1f61d4 100644
--- a/nss/nss_files_fopen.c
+++ b/nss/nss_files_fopen.c
@@ -21,6 +21,9 @@ 
 #include <errno.h>
 #include <stdio_ext.h>

+#define PRIMARY_PREFIX "/etc/"
+#define FALLBACK_PREFIX "/usr/share/nss/"
+
 FILE *
 __nss_files_fopen (const char *path)
 {
@@ -45,3 +48,40 @@  __nss_files_fopen (const char *path)
   return fp;
 }
 libc_hidden_def (__nss_files_fopen)
+
+FILE *
+__nss_files_fopen_database (const char *database)
+{
+  char buf[sizeof(FALLBACK_PREFIX) + NAME_MAX];
+
+  /* Protect against string overflow */
+  size_t n = strlen (database);
+  if (n > NAME_MAX)
+    {
+      __set_errno (EINVAL);
+      return NULL;
+    }
+
+  strcpy (stpcpy (buf, PRIMARY_PREFIX), database);
+  FILE *fp = __nss_files_fopen (buf);
+  if (fp != NULL)
+    return fp;
+  switch (errno)
+    {
+    case ENOENT:
+    case EISDIR:
+    case ENOTDIR:
+    case ELOOP:
+      /* If the path does not exist or is incorrectly set up, let's try to use the fallback database */
+      break;
+
+    default:
+      /* Propagate all other errors (in particular EACCESS) to the caller, since we shouldn't accidentally use
+       * the wrong database just because some MAC (or so) prohibited access to the primary database. */
+      return NULL;
+    }
+
+  strcpy (stpcpy (buf, FALLBACK_PREFIX), database);
+  return __nss_files_fopen (buf);
+}
+libc_hidden_def (__nss_files_fopen_database)