Files
bubblewrap/bind-mount.c
2016-03-07 20:03:54 +01:00

282 lines
6.9 KiB
C

/* bubblewrap
* Copyright (C) 2016 Alexander Larsson
*
* This program 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 of the License, or (at your option) any later version.
*
* This 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 this library. If not, see <http://www.gnu.org/licenses/>.
*
*/
#include "config.h"
#include <sys/mount.h>
#include "utils.h"
#include "bind-mount.h"
static char *
skip_line (char *line)
{
while (*line != 0 && *line != '\n')
line++;
if (*line == '\n')
line++;
return line;
}
static char *
skip_token (char *line, bool eat_whitespace)
{
while (*line != ' ' && *line != '\n')
line++;
if (eat_whitespace && *line == ' ')
line++;
return line;
}
static char *
unescape_mountpoint (const char *escaped, ssize_t len)
{
char *unescaped, *res;
const char *end;
if (len < 0)
len = strlen (escaped);
end = escaped + len;
unescaped = res = xmalloc (len + 1);
while (escaped < end)
{
if (*escaped == '\\')
{
*unescaped++ =
((escaped[1] - '0') << 6) |
((escaped[2] - '0') << 3) |
((escaped[3] - '0') << 0);
escaped += 4;
}
else
*unescaped++ = *escaped++;
}
*unescaped = 0;
return res;
}
static char *
get_mountinfo (int proc_fd,
const char *mountpoint)
{
char *line_mountpoint, *line_mountpoint_end;
cleanup_free char *mountinfo = NULL;
cleanup_free char *free_me = NULL;
char *line, *line_start;
char *res = NULL;
int i;
if (mountpoint[0] != '/')
{
cleanup_free char *cwd = getcwd (NULL, 0);
if (cwd == NULL)
die_oom ();
mountpoint = free_me = strconcat3 (cwd, "/", mountpoint);
}
mountinfo = load_file_at (proc_fd, "self/mountinfo");
if (mountinfo == NULL)
return NULL;
line = mountinfo;
while (*line != 0)
{
cleanup_free char *unescaped = NULL;
line_start = line;
for (i = 0; i < 4; i++)
line = skip_token (line, TRUE);
line_mountpoint = line;
line = skip_token (line, FALSE);
line_mountpoint_end = line;
line = skip_line (line);
unescaped = unescape_mountpoint (line_mountpoint, line_mountpoint_end - line_mountpoint);
if (strcmp (mountpoint, unescaped) == 0)
{
res = line_start;
line[-1] = 0;
/* Keep going, because we want to return the *last* match */
}
}
if (res)
return xstrdup (res);
return NULL;
}
static unsigned long
get_mountflags (int proc_fd,
const char *mountpoint)
{
cleanup_free char *line = NULL;
char *token, *end_token;
int i;
unsigned long flags = 0;
static const struct { int flag; char *name; } flags_data[] = {
{ 0, "rw" },
{ MS_RDONLY, "ro" },
{ MS_NOSUID, "nosuid" },
{ MS_NODEV, "nodev" },
{ MS_NOEXEC, "noexec" },
{ MS_NOATIME, "noatime" },
{ MS_NODIRATIME, "nodiratime" },
{ MS_RELATIME, "relatime" },
{ 0, NULL }
};
line = get_mountinfo (proc_fd, mountpoint);
if (line == NULL)
return 0;
token = line;
for (i = 0; i < 5; i++)
token = skip_token (token, TRUE);
end_token = skip_token (token, FALSE);
*end_token = 0;
do {
end_token = strchr (token, ',');
if (end_token != NULL)
*end_token = 0;
for (i = 0; flags_data[i].name != NULL; i++)
{
if (strcmp (token, flags_data[i].name) == 0)
flags |= flags_data[i].flag;
}
if (end_token)
token = end_token + 1;
else
token = NULL;
} while (token != NULL);
return flags;
}
static char **
get_submounts (int proc_fd,
const char *parent_mount)
{
char *mountpoint, *mountpoint_end;
char **submounts;
int i, n_submounts, submounts_size;
cleanup_free char *mountinfo = NULL;
char *line;
mountinfo = load_file_at (proc_fd, "self/mountinfo");
if (mountinfo == NULL)
return NULL;
submounts_size = 8;
n_submounts = 0;
submounts = xmalloc (sizeof (char *) * submounts_size);
line = mountinfo;
while (*line != 0)
{
cleanup_free char *unescaped = NULL;
for (i = 0; i < 4; i++)
line = skip_token (line, TRUE);
mountpoint = line;
line = skip_token (line, FALSE);
mountpoint_end = line;
line = skip_line (line);
*mountpoint_end = 0;
unescaped = unescape_mountpoint (mountpoint, -1);
if (has_path_prefix (unescaped, parent_mount))
{
if (n_submounts + 1 >= submounts_size)
{
submounts_size *= 2;
submounts = xrealloc (submounts, sizeof (char *) * submounts_size);
}
submounts[n_submounts++] = xstrdup (unescaped);
}
}
submounts[n_submounts] = NULL;
return submounts;
}
int
bind_mount (int proc_fd,
const char *src,
const char *dest,
bind_option_t options)
{
bool readonly = (options & BIND_READONLY) != 0;
bool devices = (options & BIND_DEVICES) != 0;
bool recursive = (options & BIND_RECURSIVE) != 0;
unsigned long current_flags, new_flags;
int i;
if (mount (src, dest, NULL, MS_MGC_VAL|MS_BIND|(recursive?MS_REC:0), NULL) != 0)
return 1;
current_flags = get_mountflags (proc_fd, dest);
new_flags = current_flags|(devices?0:MS_NODEV)|MS_NOSUID|(readonly?MS_RDONLY:0);
if (new_flags != current_flags &&
mount ("none", dest,
NULL, MS_MGC_VAL|MS_BIND|MS_REMOUNT|new_flags, NULL) != 0)
return 3;
/* We need to work around the fact that a bind mount does not apply the flags, so we need to manually
* apply the flags to all submounts in the recursive case.
* Note: This does not apply the flags to mounts which are later propagated into this namespace.
*/
if (recursive)
{
cleanup_strv char **submounts = get_submounts (proc_fd, dest);
if (submounts == NULL)
return 4;
for (i = 0; submounts[i] != NULL; i++)
{
current_flags = get_mountflags (proc_fd, submounts[i]);
new_flags = current_flags|(devices?0:MS_NODEV)|MS_NOSUID|(readonly?MS_RDONLY:0);
if (new_flags != current_flags &&
mount ("none", submounts[i],
NULL, MS_MGC_VAL|MS_BIND|MS_REMOUNT|new_flags, NULL) != 0)
{
/* If we can't read the mountpoint we can't remount it, but that should
be safe to ignore because its not something the user can access. */
if (errno != EACCES)
return 5;
}
}
}
return 0;
}