unshare: support the time namespace

This adds support to unshare for time namespaces. With the newly added
options '-t, --time' and '--monotonic' and '--boottime' it is now
possible to change CLOCK_MONOTONIC and CLOCK_BOOTTIME in a new time
namespace.

The time namespace has been merged in kernel version 5.6 and an easy way
to test it is using CLOCK_BOOTTIME and the uptime command:

 # uptime
 11:08:26 up 20:28,  1 user,  load average: 0.00, 0.00, 0.00
 # ./unshare --fork --time --boottime 100000000 uptime
 11:08:29 up 1158 days,  6:15,  1 user,  load average: 0.00, 0.00, 0.00

Signed-off-by: Adrian Reber <areber@redhat.com>
This commit is contained in:
Adrian Reber 2020-03-06 12:05:00 +01:00
parent 6671501cfa
commit be7df01a62
No known key found for this signature in database
GPG Key ID: 82C9378ED3C4906A
4 changed files with 89 additions and 1 deletions

View File

@ -26,6 +26,7 @@ _unshare_module()
--pid
--user
--cgroup
--time
--fork
--kill-child
--keep-caps
@ -38,6 +39,8 @@ _unshare_module()
--version
--root
--wd
--monotonic
--boottime
--setuid
--setgid"
COMPREPLY=( $(compgen -W "${OPTS[*]}" -- $cur) )

View File

@ -31,6 +31,9 @@
# ifndef CLONE_NEWPID
# define CLONE_NEWPID 0x20000000
# endif
# ifndef CLONE_NEWTIME
# define CLONE_NEWTIME 0x00000080
# endif
# if !defined(HAVE_UNSHARE) || !defined(HAVE_SETNS)
# include <sys/syscall.h>

View File

@ -100,6 +100,13 @@ and the discussion of the
.B CLONE_NEWUSER
flag in
.BR clone (2).
.TP
.B time namespace
The process can have a distinct view of
.B CLOCK_MONOTONIC
and/or
.B CLOCK_BOOTTIME
which can be changed using \fI/proc/self/timens_offsets\fP.
.SH OPTIONS
.TP
.BR \-i , " \-\-ipc" [ =\fIfile ]
@ -134,6 +141,12 @@ namespace is created by a bind mount.
Unshare the cgroup namespace. If \fIfile\fP is specified then persistent namespace is created
by bind mount.
.TP
.BR \-t , " \-\-time"[=\fIfile\fP]
Unshare the time namespace. If \fIfile\fP is specified then a persistent
namespace is created by a bind mount. The \fB\-\-monotonic\fP and
\fB\-\-boottime\fP options can be used to specify the corresponding
offset in the time namespace.
.TP
.BR \-f , " \-\-fork"
Fork the specified \fIprogram\fR as a child process of \fBunshare\fR rather than
running it directly. This is useful when creating a new PID namespace.
@ -208,6 +221,18 @@ Set the user ID which will be used in the entered namespace.
Set the group ID which will be used in the entered namespace and drop
supplementary groups.
.TP
.BR "\-\-monotonic \fIoffset"
Set the offset of
.B CLOCK_MONOTONIC
which will be used in the entered time namespace. This option requires
unsharing a time namespace with \fB\-\-time\fP.
.TP
.BR "\-\-boottime \fIoffset"
Set the offset of
.B CLOCK_BOOTTIME
which will be used in the entered time namespace. This option requires
unsharing a time namespace with \fB\-\-time\fP.
.TP
.BR \-V , " \-\-version"
Display version information and exit.
.TP
@ -269,6 +294,10 @@ Reliable killing of subprocesses of the \fIprogram\fR.
When \fBunshare\fR gets killed, everything below it gets killed as well.
Without it, the children of \fIprogram\fR would have orphaned and
been re-parented to PID 1.
.TP
.B # unshare \-\-fork \-\-time \-\-boottime 100000000 uptime
.TQ
10:58:48 up 1158 days, 6:05, 1 user, load average: 0.00, 0.00, 0.00
.SH SEE ALSO
.BR clone (2),

View File

@ -65,6 +65,7 @@ static struct namespace_file {
{ .type = CLONE_NEWNET, .name = "ns/net" },
{ .type = CLONE_NEWPID, .name = "ns/pid" },
{ .type = CLONE_NEWNS, .name = "ns/mnt" },
{ .type = CLONE_NEWTIME, .name = "ns/time" },
{ .name = NULL }
};
@ -213,6 +214,23 @@ static ino_t get_mnt_ino(pid_t pid)
return st.st_ino;
}
static void settime(time_t offset, clockid_t clk_id)
{
char buf[sizeof(stringify_value(ULONG_MAX)) * 3];
int fd, len;
len = snprintf(buf, sizeof(buf), "%d %ld 0", clk_id, offset);
fd = open("/proc/self/timens_offsets", O_WRONLY);
if (fd < 0)
err(EXIT_FAILURE, _("failed to open /proc/self/timens_offsets"));
if (write(fd, buf, len) != len)
err(EXIT_FAILURE, _("failed to write to /proc/self/timens_offsets"));
close(fd);
}
static void bind_ns_files_from_child(pid_t *child, int fds[2])
{
char ch;
@ -267,6 +285,7 @@ static void __attribute__((__noreturn__)) usage(void)
fputs(_(" -p, --pid[=<file>] unshare pid namespace\n"), out);
fputs(_(" -U, --user[=<file>] unshare user namespace\n"), out);
fputs(_(" -C, --cgroup[=<file>] unshare cgroup namespace\n"), out);
fputs(_(" -t, --time[=<file>] unshare time namespace\n"), out);
fputs(USAGE_SEPARATOR, out);
fputs(_(" -f, --fork fork before launching <program>\n"), out);
fputs(_(" -r, --map-root-user map current user to root (implies --user)\n"), out);
@ -284,6 +303,8 @@ static void __attribute__((__noreturn__)) usage(void)
fputs(_(" -w, --wd=<dir> change working directory to <dir>\n"), out);
fputs(_(" -S, --setuid <uid> set uid in entered namespace\n"), out);
fputs(_(" -G, --setgid <gid> set gid in entered namespace\n"), out);
fputs(_(" --monotonic <offset> set clock monotonic offset (seconds) in time namespaces\n"), out);
fputs(_(" --boottime <offset> set clock boottime offset (seconds) in time namespaces\n"), out);
fputs(USAGE_SEPARATOR, out);
printf(USAGE_HELP_OPTIONS(27));
@ -300,6 +321,8 @@ int main(int argc, char *argv[])
OPT_SETGROUPS,
OPT_KILLCHILD,
OPT_KEEPCAPS,
OPT_MONOTONIC,
OPT_BOOTTIME,
};
static const struct option longopts[] = {
{ "help", no_argument, NULL, 'h' },
@ -312,6 +335,7 @@ int main(int argc, char *argv[])
{ "pid", optional_argument, NULL, 'p' },
{ "user", optional_argument, NULL, 'U' },
{ "cgroup", optional_argument, NULL, 'C' },
{ "time", optional_argument, NULL, 't' },
{ "fork", no_argument, NULL, 'f' },
{ "kill-child", optional_argument, NULL, OPT_KILLCHILD },
@ -325,6 +349,8 @@ int main(int argc, char *argv[])
{ "setgid", required_argument, NULL, 'G' },
{ "root", required_argument, NULL, 'R' },
{ "wd", required_argument, NULL, 'w' },
{ "monotonic", required_argument, NULL, OPT_MONOTONIC },
{ "boottime", required_argument, NULL, OPT_BOOTTIME },
{ NULL, 0, NULL, 0 }
};
@ -343,13 +369,17 @@ int main(int argc, char *argv[])
uid_t uid = 0, real_euid = geteuid();
gid_t gid = 0, real_egid = getegid();
int keepcaps = 0;
time_t monotonic = 0;
time_t boottime = 0;
int force_monotonic = 0;
int force_boottime = 0;
setlocale(LC_ALL, "");
bindtextdomain(PACKAGE, LOCALEDIR);
textdomain(PACKAGE);
close_stdout_atexit();
while ((c = getopt_long(argc, argv, "+fhVmuinpCUrR:w:S:G:c", longopts, NULL)) != -1) {
while ((c = getopt_long(argc, argv, "+fhVmuinpCtUrR:w:S:G:c", longopts, NULL)) != -1) {
switch (c) {
case 'f':
forkit = 1;
@ -389,6 +419,11 @@ int main(int argc, char *argv[])
if (optarg)
set_ns_target(CLONE_NEWCGROUP, optarg);
break;
case 't':
unshare_flags |= CLONE_NEWTIME;
if (optarg)
set_ns_target(CLONE_NEWTIME, optarg);
break;
case OPT_MOUNTPROC:
unshare_flags |= CLONE_NEWNS;
procmnt = optarg ? optarg : "/proc";
@ -443,6 +478,14 @@ int main(int argc, char *argv[])
case 'w':
newdir = optarg;
break;
case OPT_MONOTONIC:
monotonic = strtoul_or_err(optarg, _("failed to parse monotonic offset"));
force_monotonic = 1;
break;
case OPT_BOOTTIME:
boottime = strtoul_or_err(optarg, _("failed to parse boottime offset"));
force_boottime = 1;
break;
case 'h':
usage();
@ -453,6 +496,10 @@ int main(int argc, char *argv[])
}
}
if ((force_monotonic || force_boottime) && !(unshare_flags & CLONE_NEWTIME))
errx(EXIT_FAILURE, _("options --monotonic and --boottime require "
"unsharing of a time namespace (-t)"));
if (npersists && (unshare_flags & CLONE_NEWNS))
bind_ns_files_from_child(&pid, fds);
@ -486,6 +533,12 @@ int main(int argc, char *argv[])
bind_ns_files(getpid());
}
if (force_boottime)
settime(boottime, CLOCK_BOOTTIME);
if (force_monotonic)
settime(monotonic, CLOCK_MONOTONIC);
if (forkit) {
pid = fork();