Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support time namespace #3876

Merged
merged 1 commit into from
Aug 10, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions libcontainer/configs/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -216,6 +216,9 @@ type Config struct {
// Do not try to remount a bind mount again after the first attempt failed on source
// filesystems that have nodev, noexec, nosuid, noatime, relatime, strictatime, nodiratime set
NoMountFallback bool `json:"no_mount_fallback,omitempty"`

// TimeOffsets specifies the offset for supporting time namespaces.
TimeOffsets map[string]specs.LinuxTimeOffset `json:"time_offsets,omitempty"`
}

type (
Expand Down
4 changes: 4 additions & 0 deletions libcontainer/configs/namespaces_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ const (
NEWIPC NamespaceType = "NEWIPC"
NEWUSER NamespaceType = "NEWUSER"
NEWCGROUP NamespaceType = "NEWCGROUP"
NEWTIME NamespaceType = "NEWTIME"
)

var (
Expand All @@ -38,6 +39,8 @@ func NsName(ns NamespaceType) string {
return "uts"
case NEWCGROUP:
return "cgroup"
case NEWTIME:
return "time"
}
return ""
}
Expand Down Expand Up @@ -72,6 +75,7 @@ func NamespaceTypes() []NamespaceType {
NEWPID,
NEWNS,
NEWCGROUP,
NEWTIME,
}
}

Expand Down
1 change: 1 addition & 0 deletions libcontainer/configs/namespaces_syscall.go
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,7 @@ var namespaceInfo = map[NamespaceType]int{
NEWUTS: unix.CLONE_NEWUTS,
NEWPID: unix.CLONE_NEWPID,
NEWCGROUP: unix.CLONE_NEWCGROUP,
NEWTIME: unix.CLONE_NEWTIME,
}

// CloneFlags parses the container's Namespaces options to set the correct
Expand Down
6 changes: 6 additions & 0 deletions libcontainer/configs/validate/validator.go
Original file line number Diff line number Diff line change
Expand Up @@ -106,6 +106,12 @@ func namespaces(config *configs.Config) error {
}
}

if config.Namespaces.Contains(configs.NEWTIME) {
if _, err := os.Stat("/proc/self/timens_offsets"); os.IsNotExist(err) {
return errors.New("time namespaces aren't enabled in the kernel")
}
}

return nil
}

Expand Down
12 changes: 12 additions & 0 deletions libcontainer/container_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -2321,6 +2321,18 @@ func (c *Container) bootstrapData(cloneFlags uintptr, nsMaps map[configs.Namespa
})
}

// write boottime and monotonic time ns offsets.
if c.config.Namespaces.Contains(configs.NEWTIME) && c.config.TimeOffsets != nil {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I suspect we want this check to be done in the validator (at is stands, the code will silently ignore timensOffsets being configured without CLONE_NEWTIME enabled). But I can do this in a separate PR when I add the necessary integration tests for this feature.

var offsetSpec bytes.Buffer
cyphar marked this conversation as resolved.
Show resolved Hide resolved
for clock, offset := range c.config.TimeOffsets {
fmt.Fprintf(&offsetSpec, "%s %d %d\n", clock, offset.Secs, offset.Nanosecs)
}
r.AddData(&Bytemsg{
Type: TimeOffsetsAttr,
Value: offsetSpec.Bytes(),
})
}

return bytes.NewReader(r.Serialize()), nil
}

Expand Down
1 change: 1 addition & 0 deletions libcontainer/message_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ const (
GidmapPathAttr uint16 = 27289
MountSourcesAttr uint16 = 27290
IdmapSourcesAttr uint16 = 27291
TimeOffsetsAttr uint16 = 27292
)

type Int32msg struct {
Expand Down
3 changes: 3 additions & 0 deletions libcontainer/nsenter/namespace.h
Original file line number Diff line number Diff line change
Expand Up @@ -28,5 +28,8 @@
#ifndef CLONE_NEWNET
# define CLONE_NEWNET 0x40000000 /* New network namespace */
#endif
#ifndef CLONE_NEWTIME
# define CLONE_NEWTIME 0x00000080 /* New time namespace */
#endif

#endif /* NSENTER_NAMESPACE_H */
27 changes: 27 additions & 0 deletions libcontainer/nsenter/nsexec.c
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ struct nlconfig_t {
/* Idmap sources opened outside the container userns which will be id mapped. */
char *idmapsources;
size_t idmapsources_len;

/* Time NS offsets. */
char *timensoffset;
size_t timensoffset_len;
};

/*
Expand All @@ -122,6 +126,7 @@ struct nlconfig_t {
#define GIDMAPPATH_ATTR 27289
#define MOUNT_SOURCES_ATTR 27290
#define IDMAP_SOURCES_ATTR 27291
#define TIMENSOFFSET_ATTR 27292

chethanah marked this conversation as resolved.
Show resolved Hide resolved
/*
* Use the raw syscall for versions of glibc which don't include a function for
Expand Down Expand Up @@ -351,6 +356,8 @@ static int nsflag(char *name)
return CLONE_NEWUSER;
else if (!strcmp(name, "uts"))
return CLONE_NEWUTS;
else if (!strcmp(name, "time"))
return CLONE_NEWTIME;

/* If we don't recognise a name, fallback to 0. */
return 0;
Expand Down Expand Up @@ -445,6 +452,10 @@ static void nl_parse(int fd, struct nlconfig_t *config)
config->idmapsources = current;
config->idmapsources_len = payload_len;
break;
case TIMENSOFFSET_ATTR:
config->timensoffset = current;
config->timensoffset_len = payload_len;
break;
default:
bail("unknown netlink message type %d", nlattr->nla_type);
}
Expand Down Expand Up @@ -747,6 +758,17 @@ void receive_idmapsources(int sockfd)
receive_fd_sources(sockfd, "_LIBCONTAINER_IDMAP_FDS");
}

static void update_timens(char *map, size_t map_len)
{
if (map == NULL || map_len == 0)
return;
write_log(DEBUG, "update /proc/self/timens_offsets to '%s'", map);
if (write_file(map, map_len, "/proc/self/timens_offsets") < 0) {
if (errno != EPERM)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

EPERM means The caller does not have the CAP_SYS_TIME capability.
Why ignore this error?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Good catch. I think this was mistakenly copied from the /proc/self/uid_map and gid_map helpers (which call the setuid newuidmap/newgidmap helper programs in case of -EPERM).

bail("failed to update /proc/self/timens_offsets");
}
Comment on lines +766 to +769
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
if (write_file(map, map_len, "/proc/self/timens_offsets") < 0) {
if (errno != EPERM)
bail("failed to update /proc/self/timens_offsets");
}
if (write_file(map, map_len, "/proc/self/timens_offsets") < 0)
bail("failed to update /proc/self/timens_offsets");

}

void nsexec(void)
{
int pipenum;
Expand Down Expand Up @@ -1185,6 +1207,11 @@ void nsexec(void)
bail("failed to sync with parent: SYNC_MOUNT_IDMAP_ACK: got %u", s);
}

/*
* set boottime and monotonic timens offsets.
*/
update_timens(config.timensoffset, config.timensoffset_len);

/*
* TODO: What about non-namespace clone flags that we're dropping here?
*
Expand Down
4 changes: 4 additions & 0 deletions libcontainer/specconv/spec_linux.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@ func initMaps() {
specs.IPCNamespace: configs.NEWIPC,
specs.UTSNamespace: configs.NEWUTS,
specs.CgroupNamespace: configs.NEWCGROUP,
specs.TimeNamespace: configs.NEWTIME,
}

mountPropagationMapping = map[string]int{
Expand Down Expand Up @@ -435,6 +436,9 @@ func CreateLibcontainerConfig(opts *CreateOpts) (*configs.Config, error) {
MemBwSchema: spec.Linux.IntelRdt.MemBwSchema,
}
}

// update timens offsets
config.TimeOffsets = spec.Linux.TimeOffsets
Comment on lines +439 to +441
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nit: perhaps you can move this to be right after these lines:

                  config.MaskPaths = spec.Linux.MaskedPaths
                  config.ReadonlyPaths = spec.Linux.ReadonlyPaths
                  config.MountLabel = spec.Linux.MountLabel
                  config.Sysctl = spec.Linux.Sysctl

and drop the comment since it's kind of obvious what we do here.

}

// Set the host UID that should own the container's cgroup.
Expand Down