linux: Require /dev/shm as the shared memory file system

Previously, glibc would pick an arbitrary tmpfs file system from
/proc/mounts if /dev/shm was not available.  This could lead to
an unsuitable file system being picked for the backing storage for
shm_open, sem_open, and related functions.

This patch introduces a new function, __shm_get_name, which builds
the file name under the appropriate (now hard-coded) directory.  It is
called from the various shm_* and sem_* function.  Unlike the
SHM_GET_NAME macro it replaces, the callers handle the return values
and errno updates.  shm-directory.c is moved directly into the posix
subdirectory because it can be implemented directly using POSIX
functionality.  It resides in libc because it is needed by both
librt and nptl/htl.

In the sem_open implementation, tmpfname is initialized directly
from a string constant.  This happens to remove one alloca call.

Checked on x86_64-linux-gnu.
This commit is contained in:
Florian Weimer 2021-02-03 08:55:13 -03:00 committed by Adhemerval Zanella
parent 7f61f785f1
commit e9fed2438a
19 changed files with 86 additions and 300 deletions

5
NEWS
View File

@ -20,7 +20,10 @@ Deprecated and removed features, and other changes affecting compatibility:
Changes to build and runtime requirements: Changes to build and runtime requirements:
[Add changes to build and runtime requirements here] * On Linux, the shm_open, sem_open, and related functions now expect the
file shared memory file system to be mounted at /dev/shm. These functions
no longer search among the system's mount points for a suitable
replacement if /dev/shm is not available.
Security related changes: Security related changes:

View File

@ -132,8 +132,6 @@ libpthread-routines := pt-attr pt-attr-destroy pt-attr-getdetachstate \
sem-post sem-timedwait sem-trywait sem_unlink \ sem-post sem-timedwait sem-trywait sem_unlink \
sem-wait sem-waitfast \ sem-wait sem-waitfast \
\ \
shm-directory \
\
cancellation \ cancellation \
cthreads-compat \ cthreads-compat \
herrno \ herrno \

View File

@ -168,7 +168,6 @@ libpthread {
GLIBC_PRIVATE { GLIBC_PRIVATE {
__pthread_initialize_minimal; __pthread_initialize_minimal;
__shm_directory;
__pthread_threads; __pthread_threads;
__cthread_detach; __cthread_detach;

View File

@ -1,4 +1,4 @@
/* Header for directory for shm/sem files. NPTL version. /* Header for directory for shm/sem files.
Copyright (C) 2014-2021 Free Software Foundation, Inc. Copyright (C) 2014-2021 Free Software Foundation, Inc.
This file is part of the GNU C Library. This file is part of the GNU C Library.
@ -18,14 +18,24 @@
#ifndef _SHM_DIRECTORY_H #ifndef _SHM_DIRECTORY_H
#include <sysdeps/posix/shm-directory.h> #include <limits.h>
#include <paths.h>
#include <stdbool.h>
/* For NPTL the __shm_directory function lives in libpthread. /* The directory that contains shared POSIX objects. */
We don't want PLT calls from there. But it's also used from #define SHMDIR _PATH_DEV "shm/"
librt, so it cannot just be declared hidden. */
#if IS_IN (libpthread) struct shmdir_name
hidden_proto (__shm_directory) {
#endif /* The combined prefix/name. The sizeof includes the terminating
NUL byte. 4 bytes are needed for the optional "sem." prefix. */
char name[sizeof (SHMDIR) + 4 + NAME_MAX];
};
/* Sets RESULT->name to the constructed name and returns 0 on success,
or -1 on failure. Includes the "sem." prefix in the name if
SEM_PREFIX is true. */
int __shm_get_name (struct shmdir_name *result, const char *name,
bool sem_prefix);
#endif /* shm-directory.h */ #endif /* shm-directory.h */

View File

@ -137,7 +137,6 @@ libpthread-routines = nptl-init nptlfreeres vars events version pt-interp \
pthread_once \ pthread_once \
old_pthread_atfork \ old_pthread_atfork \
pthread_getcpuclockid \ pthread_getcpuclockid \
shm-directory \
sem_init sem_destroy \ sem_init sem_destroy \
sem_open sem_close sem_unlink \ sem_open sem_close sem_unlink \
sem_getvalue \ sem_getvalue \

View File

@ -303,7 +303,6 @@ libpthread {
__pthread_unwind; __pthread_get_minstack; __pthread_unwind; __pthread_get_minstack;
__pthread_barrier_init; __pthread_barrier_wait; __pthread_barrier_init; __pthread_barrier_wait;
__futex_abstimed_wait64; __futex_abstimed_wait_cancelable64; __futex_abstimed_wait64; __futex_abstimed_wait_cancelable64;
__shm_directory;
__libpthread_freeres; __libpthread_freeres;
} }
} }

View File

@ -27,6 +27,5 @@ __libpthread_freeres (void)
{ {
call_function_static_weak (__default_pthread_attr_freeres); call_function_static_weak (__default_pthread_attr_freeres);
call_function_static_weak (__nptl_stacks_freeres); call_function_static_weak (__nptl_stacks_freeres);
call_function_static_weak (__shm_directory_freeres);
call_function_static_weak (__nptl_unwind_freeres); call_function_static_weak (__nptl_unwind_freeres);
} }

View File

@ -629,7 +629,6 @@ extern void __nptl_set_robust (struct pthread *self);
#endif #endif
extern void __nptl_stacks_freeres (void) attribute_hidden; extern void __nptl_stacks_freeres (void) attribute_hidden;
extern void __shm_directory_freeres (void) attribute_hidden;
extern void __wait_lookup_done (void) attribute_hidden; extern void __wait_lookup_done (void) attribute_hidden;

View File

@ -65,7 +65,8 @@ routines := \
spawnattr_setsigmask spawnattr_setschedpolicy spawnattr_setschedparam \ spawnattr_setsigmask spawnattr_setschedpolicy spawnattr_setschedparam \
posix_madvise \ posix_madvise \
get_child_max sched_cpucount sched_cpualloc sched_cpufree \ get_child_max sched_cpucount sched_cpualloc sched_cpufree \
streams-compat streams-compat \
shm-directory
aux := init-posix environ aux := init-posix environ
tests := test-errno tstgetopt testfnm runtests runptests \ tests := test-errno tstgetopt testfnm runtests runptests \

View File

@ -150,5 +150,6 @@ libc {
GLIBC_PRIVATE { GLIBC_PRIVATE {
__libc_fork; __libc_pread; __libc_pwrite; __libc_fork; __libc_pread; __libc_pwrite;
__nanosleep_nocancel; __pause_nocancel; __nanosleep_nocancel; __pause_nocancel;
__shm_get_name;
} }
} }

View File

@ -16,23 +16,31 @@
License along with the GNU C Library; if not, see License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */ <https://www.gnu.org/licenses/>. */
#include <shm-directory.h>
#include <unistd.h> #include <unistd.h>
#if _POSIX_MAPPED_FILES #if _POSIX_MAPPED_FILES
# include <paths.h> #include <alloc_buffer.h>
#include <shm-directory.h>
#include <string.h>
# define SHMDIR (_PATH_DEV "shm/") int
__shm_get_name (struct shmdir_name *result, const char *name, bool sem_prefix)
const char *
__shm_directory (size_t *len)
{ {
*len = sizeof SHMDIR - 1; while (name[0] == '/')
return SHMDIR; ++name;
size_t namelen = strlen (name);
struct alloc_buffer buffer
= alloc_buffer_create (result->name, sizeof (result->name));
alloc_buffer_copy_bytes (&buffer, SHMDIR, strlen (SHMDIR));
if (sem_prefix)
alloc_buffer_copy_bytes (&buffer, "sem.", strlen ("sem."));
alloc_buffer_copy_bytes (&buffer, name, namelen + 1);
if (namelen == 0 || memchr (name, '/', namelen) != NULL
|| alloc_buffer_has_failed (&buffer))
return -1;
return 0;
} }
# if IS_IN (libpthread)
hidden_def (__shm_directory)
# endif
#endif #endif

View File

@ -1,30 +0,0 @@
/* Header for directory for shm/sem files. libpthread version.
Copyright (C) 2014-2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#ifndef _SHM_DIRECTORY_H
#include <sysdeps/posix/shm-directory.h>
/* For libpthread the __shm_directory function lives in libpthread.
We don't want PLT calls from there. But it's also used from
librt, so it cannot just be declared hidden. */
#if IS_IN (libpthread)
hidden_proto (__shm_directory)
#endif
#endif /* shm-directory.h */

View File

@ -3,9 +3,3 @@ L_tmpnam = 20
TMP_MAX = 238328 TMP_MAX = 238328
L_ctermid = 9 L_ctermid = 9
L_cuserid = 9 L_cuserid = 9
ifeq ($(subdir)|$(have-thread-library),rt|no)
# With NPTL, this lives in libpthread so it can be used for sem_open too.
# Without NPTL, it's just private in librt.
librt-routines += shm-directory
endif

View File

@ -1,66 +0,0 @@
/* Header for directory for shm/sem files.
Copyright (C) 2014-2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#ifndef _SHM_DIRECTORY_H
#include <errno.h>
#include <limits.h>
#include <stdbool.h>
#include <stdlib.h>
#include <string.h>
extern const char *__shm_directory (size_t *len);
/* This defines local variables SHM_DIR and SHM_DIRLEN, giving the
directory prefix (with trailing slash) and length (not including '\0'
terminator) of the directory used for shm files. If that cannot be
determined, it sets errno to ENOSYS and returns RETVAL_FOR_INVALID.
This uses the local variable NAME as an lvalue, and increments it past
any leading slashes. It then defines the local variable NAMELEN, giving
strlen (NAME) + 1. If NAME is invalid, it sets errno to
ERRNO_FOR_INVALID and returns RETVAL_FOR_INVALID. Finally, it defines
the local variable SHM_NAME, giving the absolute file name of the shm
file corresponding to NAME. PREFIX is a string constant used as a
prefix on NAME. */
#define SHM_GET_NAME(errno_for_invalid, retval_for_invalid, prefix) \
size_t shm_dirlen; \
const char *shm_dir = __shm_directory (&shm_dirlen); \
/* If we don't know what directory to use, there is nothing we can do. */ \
if (__glibc_unlikely (shm_dir == NULL)) \
{ \
__set_errno (ENOSYS); \
return retval_for_invalid; \
} \
/* Construct the filename. */ \
while (name[0] == '/') \
++name; \
size_t namelen = strlen (name) + 1; \
/* Validate the filename. */ \
if (namelen == 1 || namelen >= NAME_MAX || strchr (name, '/') != NULL) \
{ \
__set_errno (errno_for_invalid); \
return retval_for_invalid; \
} \
char *shm_name = __alloca (shm_dirlen + sizeof prefix - 1 + namelen); \
__mempcpy (__mempcpy (__mempcpy (shm_name, shm_dir, shm_dirlen), \
prefix, sizeof prefix - 1), \
name, namelen)
#endif /* shm-directory.h */

View File

@ -24,6 +24,7 @@
#else #else
# include <errno.h>
# include <fcntl.h> # include <fcntl.h>
# include <pthread.h> # include <pthread.h>
# include <shm-directory.h> # include <shm-directory.h>
@ -33,7 +34,12 @@
int int
shm_open (const char *name, int oflag, mode_t mode) shm_open (const char *name, int oflag, mode_t mode)
{ {
SHM_GET_NAME (EINVAL, -1, ""); struct shmdir_name dirname;
if (__shm_get_name (&dirname, name, false) != 0)
{
__set_errno (EINVAL);
return -1;
}
oflag |= O_NOFOLLOW | O_CLOEXEC; oflag |= O_NOFOLLOW | O_CLOEXEC;
@ -41,7 +47,7 @@ shm_open (const char *name, int oflag, mode_t mode)
int state; int state;
pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &state); pthread_setcancelstate (PTHREAD_CANCEL_DISABLE, &state);
int fd = open (shm_name, oflag, mode); int fd = open (dirname.name, oflag, mode);
if (fd == -1 && __glibc_unlikely (errno == EISDIR)) if (fd == -1 && __glibc_unlikely (errno == EISDIR))
/* It might be better to fold this error with EINVAL since /* It might be better to fold this error with EINVAL since
directory names are just another example for unsuitable shared directory names are just another example for unsuitable shared

View File

@ -25,16 +25,21 @@
#include <errno.h> #include <errno.h>
#include <string.h> #include <string.h>
#include "shm-directory.h" #include <shm-directory.h>
/* Remove shared memory object. */ /* Remove shared memory object. */
int int
shm_unlink (const char *name) shm_unlink (const char *name)
{ {
SHM_GET_NAME (ENOENT, -1, ""); struct shmdir_name dirname;
if (__shm_get_name (&dirname, name, false) != 0)
{
__set_errno (ENOENT);
return -1;
}
int result = unlink (shm_name); int result = unlink (dirname.name);
if (result < 0 && errno == EPERM) if (result < 0 && errno == EPERM)
__set_errno (EACCES); __set_errno (EACCES);
return result; return result;

View File

@ -62,8 +62,9 @@ int __sem_mappings_lock attribute_hidden = LLL_LOCK_INITIALIZER;
/* Search for existing mapping and if possible add the one provided. */ /* Search for existing mapping and if possible add the one provided. */
static sem_t * static sem_t *
check_add_mapping (const char *name, size_t namelen, int fd, sem_t *existing) check_add_mapping (const char *name, int fd, sem_t *existing)
{ {
size_t namelen = strlen (name);
sem_t *result = SEM_FAILED; sem_t *result = SEM_FAILED;
/* Get the information about the file. */ /* Get the information about the file. */
@ -150,8 +151,12 @@ sem_open (const char *name, int oflag, ...)
return SEM_FAILED; return SEM_FAILED;
} }
/* Create the name of the final file in local variable SHM_NAME. */ struct shmdir_name dirname;
SHM_GET_NAME (EINVAL, SEM_FAILED, SEM_SHM_PREFIX); if (__shm_get_name (&dirname, name, true) != 0)
{
__set_errno (EINVAL);
return SEM_FAILED;
}
/* Disable asynchronous cancellation. */ /* Disable asynchronous cancellation. */
#ifdef __libc_ptf_call #ifdef __libc_ptf_call
@ -164,7 +169,7 @@ sem_open (const char *name, int oflag, ...)
if ((oflag & O_CREAT) == 0 || (oflag & O_EXCL) == 0) if ((oflag & O_CREAT) == 0 || (oflag & O_EXCL) == 0)
{ {
try_again: try_again:
fd = __libc_open (shm_name, fd = __libc_open (dirname.name,
(oflag & ~(O_CREAT|O_ACCMODE)) | O_NOFOLLOW | O_RDWR); (oflag & ~(O_CREAT|O_ACCMODE)) | O_NOFOLLOW | O_RDWR);
if (fd == -1) if (fd == -1)
@ -178,13 +183,12 @@ sem_open (const char *name, int oflag, ...)
else else
/* Check whether we already have this semaphore mapped and /* Check whether we already have this semaphore mapped and
create one if necessary. */ create one if necessary. */
result = check_add_mapping (name, namelen, fd, SEM_FAILED); result = check_add_mapping (name, fd, SEM_FAILED);
} }
else else
{ {
/* We have to open a temporary file first since it must have the /* We have to open a temporary file first since it must have the
correct form before we can start using it. */ correct form before we can start using it. */
char *tmpfname;
mode_t mode; mode_t mode;
unsigned int value; unsigned int value;
va_list ap; va_list ap;
@ -217,16 +221,11 @@ sem_open (const char *name, int oflag, ...)
memset ((char *) &sem.initsem + sizeof (struct new_sem), '\0', memset ((char *) &sem.initsem + sizeof (struct new_sem), '\0',
sizeof (sem_t) - sizeof (struct new_sem)); sizeof (sem_t) - sizeof (struct new_sem));
tmpfname = __alloca (shm_dirlen + sizeof SEM_SHM_PREFIX + 6); char tmpfname[] = SHMDIR "sem.XXXXXX";
char *xxxxxx = __mempcpy (tmpfname, shm_dir, shm_dirlen);
int retries = 0; int retries = 0;
#define NRETRIES 50 #define NRETRIES 50
while (1) while (1)
{ {
/* Add the suffix for mktemp. */
strcpy (xxxxxx, "XXXXXX");
/* We really want to use mktemp here. We cannot use mkstemp /* We really want to use mktemp here. We cannot use mkstemp
since the file must be opened with a specific mode. The since the file must be opened with a specific mode. The
mode cannot later be set since then we cannot apply the mode cannot later be set since then we cannot apply the
@ -244,7 +243,12 @@ sem_open (const char *name, int oflag, ...)
if (errno == EEXIST) if (errno == EEXIST)
{ {
if (++retries < NRETRIES) if (++retries < NRETRIES)
continue; {
/* Restore the six placeholder bytes before the
null terminator before the next attempt. */
memcpy (tmpfname + sizeof (tmpfname) - 7, "XXXXXX", 6);
continue;
}
__set_errno (EAGAIN); __set_errno (EAGAIN);
} }
@ -265,7 +269,7 @@ sem_open (const char *name, int oflag, ...)
fd, 0)) != MAP_FAILED) fd, 0)) != MAP_FAILED)
{ {
/* Create the file. Don't overwrite an existing file. */ /* Create the file. Don't overwrite an existing file. */
if (link (tmpfname, shm_name) != 0) if (link (tmpfname, dirname.name) != 0)
{ {
/* Undo the mapping. */ /* Undo the mapping. */
(void) munmap (result, sizeof (sem_t)); (void) munmap (result, sizeof (sem_t));
@ -290,7 +294,7 @@ sem_open (const char *name, int oflag, ...)
/* Insert the mapping into the search tree. This also /* Insert the mapping into the search tree. This also
determines whether another thread sneaked by and already determines whether another thread sneaked by and already
added such a mapping despite the fact that we created it. */ added such a mapping despite the fact that we created it. */
result = check_add_mapping (name, namelen, fd, result); result = check_add_mapping (name, fd, result);
} }
/* Now remove the temporary name. This should never fail. If /* Now remove the temporary name. This should never fail. If

View File

@ -27,11 +27,15 @@
int int
sem_unlink (const char *name) sem_unlink (const char *name)
{ {
/* Construct the filename. */ struct shmdir_name dirname;
SHM_GET_NAME (ENOENT, -1, SEM_SHM_PREFIX); if (__shm_get_name (&dirname, name, true) != 0)
{
__set_errno (ENOENT);
return -1;
}
/* Now try removing it. */ /* Now try removing it. */
int ret = unlink (shm_name); int ret = unlink (dirname.name);
if (ret < 0 && errno == EPERM) if (ret < 0 && errno == EPERM)
__set_errno (EACCES); __set_errno (EACCES);
return ret; return ret;

View File

@ -1,147 +0,0 @@
/* Determine directory for shm/sem files. Linux version.
Copyright (C) 2000-2021 Free Software Foundation, Inc.
This file is part of the GNU C Library.
The GNU C Library is free software; you can redistribute it and/or
modify it under the terms of the GNU Lesser General Public
License as published by the Free Software Foundation; either
version 2.1 of the License, or (at your option) any later version.
The GNU C Library is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
Lesser General Public License for more details.
You should have received a copy of the GNU Lesser General Public
License along with the GNU C Library; if not, see
<https://www.gnu.org/licenses/>. */
#include "shm-directory.h"
#include <errno.h>
#include <mntent.h>
#include <paths.h>
#include <stdio.h>
#include <string.h>
#include <sys/statfs.h>
#include <libc-lock.h>
#include "linux_fsinfo.h"
/* Mount point of the shared memory filesystem. */
static struct
{
char *dir;
size_t dirlen;
} mountpoint;
/* This is the default directory. */
static const char defaultdir[] = "/dev/shm/";
/* Protect the `mountpoint' variable above. */
__libc_once_define (static, once);
/* Determine where the shmfs is mounted (if at all). */
static void
where_is_shmfs (void)
{
char buf[512];
struct statfs f;
struct mntent resmem;
struct mntent *mp;
FILE *fp;
/* The canonical place is /dev/shm. This is at least what the
documentation tells everybody to do. */
if (__statfs (defaultdir, &f) == 0 && (f.f_type == SHMFS_SUPER_MAGIC
|| f.f_type == RAMFS_MAGIC))
{
/* It is in the normal place. */
mountpoint.dir = (char *) defaultdir;
mountpoint.dirlen = sizeof (defaultdir) - 1;
return;
}
/* OK, do it the hard way. Look through the /proc/mounts file and if
this does not exist through /etc/fstab to find the mount point. */
fp = __setmntent ("/proc/mounts", "r");
if (__glibc_unlikely (fp == NULL))
{
fp = __setmntent (_PATH_MNTTAB, "r");
if (__glibc_unlikely (fp == NULL))
/* There is nothing we can do. Blind guesses are not helpful. */
return;
}
/* Now read the entries. */
while ((mp = __getmntent_r (fp, &resmem, buf, sizeof buf)) != NULL)
/* The original name is "shm" but this got changed in early Linux
2.4.x to "tmpfs". */
if (strcmp (mp->mnt_type, "tmpfs") == 0
|| strcmp (mp->mnt_type, "shm") == 0)
{
/* Found it. There might be more than one place where the
filesystem is mounted but one is enough for us. */
size_t namelen;
/* First make sure this really is the correct entry. At least
some versions of the kernel give wrong information because
of the implicit mount of the shmfs for SysV IPC. */
if (__statfs (mp->mnt_dir, &f) != 0 || (f.f_type != SHMFS_SUPER_MAGIC
&& f.f_type != RAMFS_MAGIC))
continue;
namelen = strlen (mp->mnt_dir);
if (namelen == 0)
/* Hum, maybe some crippled entry. Keep on searching. */
continue;
mountpoint.dir = (char *) malloc (namelen + 2);
if (mountpoint.dir != NULL)
{
char *cp = __mempcpy (mountpoint.dir, mp->mnt_dir, namelen);
if (cp[-1] != '/')
*cp++ = '/';
*cp = '\0';
mountpoint.dirlen = cp - mountpoint.dir;
}
break;
}
/* Close the stream. */
__endmntent (fp);
}
const char *
__shm_directory (size_t *len)
{
/* Determine where the shmfs is mounted. */
__libc_once (once, where_is_shmfs);
/* If we don't know the mount points there is nothing we can do. Ever. */
if (__glibc_unlikely (mountpoint.dir == NULL))
{
__set_errno (ENOSYS);
return NULL;
}
*len = mountpoint.dirlen;
return mountpoint.dir;
}
#if IS_IN (libpthread)
hidden_def (__shm_directory)
/* Make sure the table is freed if we want to free everything before
exiting. */
void
__shm_directory_freeres (void)
{
if (mountpoint.dir != defaultdir)
free (mountpoint.dir);
}
#endif