Commit ebb4a4bf authored by David S. Miller's avatar David S. Miller

Merge branch 'net-fix-sysfs-permssions-when-device-changes-network'

Christian Brauner says:

====================
net: fix sysfs permssions when device changes network

/* v7 */
This is v7 with a build warning fixup that slipped past me the last
time. It removes to unused variables in sysfs_group_change_owner(). I
observed no warning when building just now.

/* v6 */
This is v6 with two small fixups. I missed adapting the commit message
to reflect the renamed helper for changing the owner of sysfs files and
I also forgot to make the new dpm helper static inline.

/* v5 */
This is v5 with a small fixup requested by Rafael.

/* v4 */
This is v4 with more documentation and other fixes that Greg requested.

/* v3 */
This is v3 with explicit uid and gid parameters added to functions that
change sysfs object ownership as Greg requested.

(I've tagged this with net-next since it's triggered by a bug for
 network device files but it also touches driver core aspects so it's
 not clear-cut. I can of course split this series into separate
 patchsets.)
We have been struggling with a bug surrounding the ownership of network
device sysfs files when moving network devices between network
namespaces owned by different user namespaces reported by multiple
users.

Currently, when moving network devices between network namespaces the
ownership of the corresponding sysfs entries is not changed. This leads
to problems when tools try to operate on the corresponding sysfs files.

I also causes a bug when creating a network device in a network
namespaces owned by a user namespace and moving that network device back
to the host network namespaces. Because when a network device is created
in a network namespaces it will be owned by the root user of the user
namespace and all its associated sysfs files will also be owned by the
root user of the corresponding user namespace.
If such a network device has to be moved back to the host network
namespace the permissions will still be set to the root user of the
owning user namespaces of the originating network namespace. This means
unprivileged users can e.g. re-trigger uevents for such incorrectly
owned devices on the host or in other network namespaces. They can also
modify the settings of the device itself through sysfs when they
wouldn't be able to do the same through netlink. Both of these things
are unwanted.

For example, quite a few workloads will create network devices in the
host network namespace. Other tools will then proceed to move such
devices between network namespaces owner by other user namespaces. While
the ownership of the device itself is updated in
net/core/net-sysfs.c:dev_change_net_namespace() the corresponding sysfs
entry for the device is not. Below you'll find that moving a network
device (here a veth device) from a network namespace into another
network namespaces owned by a different user namespace with a different
id mapping. As you can see the permissions are wrong even though it is
owned by the userns root user after it has been moved and can be
interacted with through netlink:

drwxr-xr-x 5 nobody nobody    0 Jan 25 18:08 .
drwxr-xr-x 9 nobody nobody    0 Jan 25 18:08 ..
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 addr_assign_type
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 addr_len
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 address
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 broadcast
-rw-r--r-- 1 nobody nobody 4096 Jan 25 18:09 carrier
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 carrier_changes
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 carrier_down_count
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 carrier_up_count
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 dev_id
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 dev_port
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 dormant
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 duplex
-rw-r--r-- 1 nobody nobody 4096 Jan 25 18:09 flags
-rw-r--r-- 1 nobody nobody 4096 Jan 25 18:09 gro_flush_timeout
-rw-r--r-- 1 nobody nobody 4096 Jan 25 18:09 ifalias
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 ifindex
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 iflink
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 link_mode
-rw-r--r-- 1 nobody nobody 4096 Jan 25 18:09 mtu
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 name_assign_type
-rw-r--r-- 1 nobody nobody 4096 Jan 25 18:09 netdev_group
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 operstate
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 phys_port_id
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 phys_port_name
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 phys_switch_id
drwxr-xr-x 2 nobody nobody    0 Jan 25 18:09 power
-rw-r--r-- 1 nobody nobody 4096 Jan 25 18:09 proto_down
drwxr-xr-x 4 nobody nobody    0 Jan 25 18:09 queues
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 speed
drwxr-xr-x 2 nobody nobody    0 Jan 25 18:09 statistics
lrwxrwxrwx 1 nobody nobody    0 Jan 25 18:08 subsystem -> ../../../../class/net
-rw-r--r-- 1 nobody nobody 4096 Jan 25 18:09 tx_queue_len
-r--r--r-- 1 nobody nobody 4096 Jan 25 18:09 type
-rw-r--r-- 1 nobody nobody 4096 Jan 25 18:08 uevent

Constrast this with creating a device of the same type in the network
namespace directly. In this case the device's sysfs permissions will be
correctly updated.
(Please also note, that in a lot of workloads this strategy of creating
 the network device directly in the network device to workaround this
 issue can not be used. Either because the network device is dedicated
 after it has been created or because it used by a process that is
 heavily sandboxed and couldn't create network devices itself.):

drwxr-xr-x 5 root   root      0 Jan 25 18:12 .
drwxr-xr-x 9 nobody nobody    0 Jan 25 18:08 ..
-r--r--r-- 1 root   root   4096 Jan 25 18:12 addr_assign_type
-r--r--r-- 1 root   root   4096 Jan 25 18:12 addr_len
-r--r--r-- 1 root   root   4096 Jan 25 18:12 address
-r--r--r-- 1 root   root   4096 Jan 25 18:12 broadcast
-rw-r--r-- 1 root   root   4096 Jan 25 18:12 carrier
-r--r--r-- 1 root   root   4096 Jan 25 18:12 carrier_changes
-r--r--r-- 1 root   root   4096 Jan 25 18:12 carrier_down_count
-r--r--r-- 1 root   root   4096 Jan 25 18:12 carrier_up_count
-r--r--r-- 1 root   root   4096 Jan 25 18:12 dev_id
-r--r--r-- 1 root   root   4096 Jan 25 18:12 dev_port
-r--r--r-- 1 root   root   4096 Jan 25 18:12 dormant
-r--r--r-- 1 root   root   4096 Jan 25 18:12 duplex
-rw-r--r-- 1 root   root   4096 Jan 25 18:12 flags
-rw-r--r-- 1 root   root   4096 Jan 25 18:12 gro_flush_timeout
-rw-r--r-- 1 root   root   4096 Jan 25 18:12 ifalias
-r--r--r-- 1 root   root   4096 Jan 25 18:12 ifindex
-r--r--r-- 1 root   root   4096 Jan 25 18:12 iflink
-r--r--r-- 1 root   root   4096 Jan 25 18:12 link_mode
-rw-r--r-- 1 root   root   4096 Jan 25 18:12 mtu
-r--r--r-- 1 root   root   4096 Jan 25 18:12 name_assign_type
-rw-r--r-- 1 root   root   4096 Jan 25 18:12 netdev_group
-r--r--r-- 1 root   root   4096 Jan 25 18:12 operstate
-r--r--r-- 1 root   root   4096 Jan 25 18:12 phys_port_id
-r--r--r-- 1 root   root   4096 Jan 25 18:12 phys_port_name
-r--r--r-- 1 root   root   4096 Jan 25 18:12 phys_switch_id
drwxr-xr-x 2 root   root      0 Jan 25 18:12 power
-rw-r--r-- 1 root   root   4096 Jan 25 18:12 proto_down
drwxr-xr-x 4 root   root      0 Jan 25 18:12 queues
-r--r--r-- 1 root   root   4096 Jan 25 18:12 speed
drwxr-xr-x 2 root   root      0 Jan 25 18:12 statistics
lrwxrwxrwx 1 nobody nobody    0 Jan 25 18:12 subsystem -> ../../../../class/net
-rw-r--r-- 1 root   root   4096 Jan 25 18:12 tx_queue_len
-r--r--r-- 1 root   root   4096 Jan 25 18:12 type
-rw-r--r-- 1 root   root   4096 Jan 25 18:12 uevent

Now, when creating a network device in a network namespace owned by a
user namespace and moving it to the host the permissions will be set to
the id that the user namespace root user has been mapped to on the host
leading to all sorts of permission issues mentioned above:

458752
drwxr-xr-x 5 458752 458752      0 Jan 25 18:12 .
drwxr-xr-x 9 root   root        0 Jan 25 18:08 ..
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 addr_assign_type
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 addr_len
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 address
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 broadcast
-rw-r--r-- 1 458752 458752   4096 Jan 25 18:12 carrier
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 carrier_changes
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 carrier_down_count
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 carrier_up_count
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 dev_id
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 dev_port
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 dormant
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 duplex
-rw-r--r-- 1 458752 458752   4096 Jan 25 18:12 flags
-rw-r--r-- 1 458752 458752   4096 Jan 25 18:12 gro_flush_timeout
-rw-r--r-- 1 458752 458752   4096 Jan 25 18:12 ifalias
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 ifindex
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 iflink
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 link_mode
-rw-r--r-- 1 458752 458752   4096 Jan 25 18:12 mtu
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 name_assign_type
-rw-r--r-- 1 458752 458752   4096 Jan 25 18:12 netdev_group
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 operstate
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 phys_port_id
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 phys_port_name
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 phys_switch_id
drwxr-xr-x 2 458752 458752      0 Jan 25 18:12 power
-rw-r--r-- 1 458752 458752   4096 Jan 25 18:12 proto_down
drwxr-xr-x 4 458752 458752      0 Jan 25 18:12 queues
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 speed
drwxr-xr-x 2 458752 458752      0 Jan 25 18:12 statistics
lrwxrwxrwx 1 root   root        0 Jan 25 18:12 subsystem -> ../../../../class/net
-rw-r--r-- 1 458752 458752   4096 Jan 25 18:12 tx_queue_len
-r--r--r-- 1 458752 458752   4096 Jan 25 18:12 type
-rw-r--r-- 1 458752 458752   4096 Jan 25 18:12 uevent

Fix this by changing the basic sysfs files associated with network
devices when moving them between network namespaces. To this end we add
some infrastructure to sysfs.

The patchset takes care to only do this when the owning user namespaces
changes and the kids differ. So there's only a performance overhead,
when the owning user namespace of the network namespace is different
__and__ the kid mappings for the root user are different for the two
user namespaces:
Assume we have a netdev eth0 which we create in netns1 owned by userns1.
userns1 has an id mapping of 0 100000 100000. Now we move eth0 into
netns2 which is owned by userns2 which also defines an id mapping of 0
100000 100000. In this case sysfs doesn't need updating. The patch will
handle this case and not do any needless work. Now assume eth0 is moved
into netns3 which is owned by userns3 which defines an id mapping of 0
123456 65536. In this case the root user in each namespace corresponds
to different kid and sysfs needs updating.
====================
Signed-off-by: default avatarDavid S. Miller <davem@davemloft.net>
parents d1c73cbd ef6a4c88
...@@ -3458,6 +3458,126 @@ int device_move(struct device *dev, struct device *new_parent, ...@@ -3458,6 +3458,126 @@ int device_move(struct device *dev, struct device *new_parent,
} }
EXPORT_SYMBOL_GPL(device_move); EXPORT_SYMBOL_GPL(device_move);
static int device_attrs_change_owner(struct device *dev, kuid_t kuid,
kgid_t kgid)
{
struct kobject *kobj = &dev->kobj;
struct class *class = dev->class;
const struct device_type *type = dev->type;
int error;
if (class) {
/*
* Change the device groups of the device class for @dev to
* @kuid/@kgid.
*/
error = sysfs_groups_change_owner(kobj, class->dev_groups, kuid,
kgid);
if (error)
return error;
}
if (type) {
/*
* Change the device groups of the device type for @dev to
* @kuid/@kgid.
*/
error = sysfs_groups_change_owner(kobj, type->groups, kuid,
kgid);
if (error)
return error;
}
/* Change the device groups of @dev to @kuid/@kgid. */
error = sysfs_groups_change_owner(kobj, dev->groups, kuid, kgid);
if (error)
return error;
if (device_supports_offline(dev) && !dev->offline_disabled) {
/* Change online device attributes of @dev to @kuid/@kgid. */
error = sysfs_file_change_owner(kobj, dev_attr_online.attr.name,
kuid, kgid);
if (error)
return error;
}
return 0;
}
/**
* device_change_owner - change the owner of an existing device.
* @dev: device.
* @kuid: new owner's kuid
* @kgid: new owner's kgid
*
* This changes the owner of @dev and its corresponding sysfs entries to
* @kuid/@kgid. This function closely mirrors how @dev was added via driver
* core.
*
* Returns 0 on success or error code on failure.
*/
int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
{
int error;
struct kobject *kobj = &dev->kobj;
dev = get_device(dev);
if (!dev)
return -EINVAL;
/*
* Change the kobject and the default attributes and groups of the
* ktype associated with it to @kuid/@kgid.
*/
error = sysfs_change_owner(kobj, kuid, kgid);
if (error)
goto out;
/*
* Change the uevent file for @dev to the new owner. The uevent file
* was created in a separate step when @dev got added and we mirror
* that step here.
*/
error = sysfs_file_change_owner(kobj, dev_attr_uevent.attr.name, kuid,
kgid);
if (error)
goto out;
/*
* Change the device groups, the device groups associated with the
* device class, and the groups associated with the device type of @dev
* to @kuid/@kgid.
*/
error = device_attrs_change_owner(dev, kuid, kgid);
if (error)
goto out;
error = dpm_sysfs_change_owner(dev, kuid, kgid);
if (error)
goto out;
#ifdef CONFIG_BLOCK
if (sysfs_deprecated && dev->class == &block_class)
goto out;
#endif
/*
* Change the owner of the symlink located in the class directory of
* the device class associated with @dev which points to the actual
* directory entry for @dev to @kuid/@kgid. This ensures that the
* symlink shows the same permissions as its target.
*/
error = sysfs_link_change_owner(&dev->class->p->subsys.kobj, &dev->kobj,
dev_name(dev), kuid, kgid);
if (error)
goto out;
out:
put_device(dev);
return error;
}
EXPORT_SYMBOL_GPL(device_change_owner);
/** /**
* device_shutdown - call ->shutdown() on each device to shutdown. * device_shutdown - call ->shutdown() on each device to shutdown.
*/ */
......
...@@ -74,6 +74,7 @@ extern int pm_qos_sysfs_add_flags(struct device *dev); ...@@ -74,6 +74,7 @@ extern int pm_qos_sysfs_add_flags(struct device *dev);
extern void pm_qos_sysfs_remove_flags(struct device *dev); extern void pm_qos_sysfs_remove_flags(struct device *dev);
extern int pm_qos_sysfs_add_latency_tolerance(struct device *dev); extern int pm_qos_sysfs_add_latency_tolerance(struct device *dev);
extern void pm_qos_sysfs_remove_latency_tolerance(struct device *dev); extern void pm_qos_sysfs_remove_latency_tolerance(struct device *dev);
extern int dpm_sysfs_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid);
#else /* CONFIG_PM */ #else /* CONFIG_PM */
...@@ -88,6 +89,8 @@ static inline void pm_runtime_remove(struct device *dev) {} ...@@ -88,6 +89,8 @@ static inline void pm_runtime_remove(struct device *dev) {}
static inline int dpm_sysfs_add(struct device *dev) { return 0; } static inline int dpm_sysfs_add(struct device *dev) { return 0; }
static inline void dpm_sysfs_remove(struct device *dev) {} static inline void dpm_sysfs_remove(struct device *dev) {}
static inline int dpm_sysfs_change_owner(struct device *dev, kuid_t kuid,
kgid_t kgid) { return 0; }
#endif #endif
......
...@@ -480,6 +480,14 @@ static ssize_t wakeup_last_time_ms_show(struct device *dev, ...@@ -480,6 +480,14 @@ static ssize_t wakeup_last_time_ms_show(struct device *dev,
return enabled ? sprintf(buf, "%lld\n", msec) : sprintf(buf, "\n"); return enabled ? sprintf(buf, "%lld\n", msec) : sprintf(buf, "\n");
} }
static inline int dpm_sysfs_wakeup_change_owner(struct device *dev, kuid_t kuid,
kgid_t kgid)
{
if (dev->power.wakeup && dev->power.wakeup->dev)
return device_change_owner(dev->power.wakeup->dev, kuid, kgid);
return 0;
}
static DEVICE_ATTR_RO(wakeup_last_time_ms); static DEVICE_ATTR_RO(wakeup_last_time_ms);
#ifdef CONFIG_PM_AUTOSLEEP #ifdef CONFIG_PM_AUTOSLEEP
...@@ -501,7 +509,13 @@ static ssize_t wakeup_prevent_sleep_time_ms_show(struct device *dev, ...@@ -501,7 +509,13 @@ static ssize_t wakeup_prevent_sleep_time_ms_show(struct device *dev,
static DEVICE_ATTR_RO(wakeup_prevent_sleep_time_ms); static DEVICE_ATTR_RO(wakeup_prevent_sleep_time_ms);
#endif /* CONFIG_PM_AUTOSLEEP */ #endif /* CONFIG_PM_AUTOSLEEP */
#endif /* CONFIG_PM_SLEEP */ #else /* CONFIG_PM_SLEEP */
static inline int dpm_sysfs_wakeup_change_owner(struct device *dev, kuid_t kuid,
kgid_t kgid)
{
return 0;
}
#endif
#ifdef CONFIG_PM_ADVANCED_DEBUG #ifdef CONFIG_PM_ADVANCED_DEBUG
static ssize_t runtime_usage_show(struct device *dev, static ssize_t runtime_usage_show(struct device *dev,
...@@ -684,6 +698,45 @@ int dpm_sysfs_add(struct device *dev) ...@@ -684,6 +698,45 @@ int dpm_sysfs_add(struct device *dev)
return rc; return rc;
} }
int dpm_sysfs_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid)
{
int rc;
if (device_pm_not_required(dev))
return 0;
rc = sysfs_group_change_owner(&dev->kobj, &pm_attr_group, kuid, kgid);
if (rc)
return rc;
if (pm_runtime_callbacks_present(dev)) {
rc = sysfs_group_change_owner(
&dev->kobj, &pm_runtime_attr_group, kuid, kgid);
if (rc)
return rc;
}
if (device_can_wakeup(dev)) {
rc = sysfs_group_change_owner(&dev->kobj, &pm_wakeup_attr_group,
kuid, kgid);
if (rc)
return rc;
rc = dpm_sysfs_wakeup_change_owner(dev, kuid, kgid);
if (rc)
return rc;
}
if (dev->power.set_latency_tolerance) {
rc = sysfs_group_change_owner(
&dev->kobj, &pm_qos_latency_tolerance_attr_group, kuid,
kgid);
if (rc)
return rc;
}
return 0;
}
int wakeup_sysfs_add(struct device *dev) int wakeup_sysfs_add(struct device *dev)
{ {
return sysfs_merge_group(&dev->kobj, &pm_wakeup_attr_group); return sysfs_merge_group(&dev->kobj, &pm_wakeup_attr_group);
......
...@@ -558,3 +558,151 @@ void sysfs_remove_bin_file(struct kobject *kobj, ...@@ -558,3 +558,151 @@ void sysfs_remove_bin_file(struct kobject *kobj,
kernfs_remove_by_name(kobj->sd, attr->attr.name); kernfs_remove_by_name(kobj->sd, attr->attr.name);
} }
EXPORT_SYMBOL_GPL(sysfs_remove_bin_file); EXPORT_SYMBOL_GPL(sysfs_remove_bin_file);
static int internal_change_owner(struct kernfs_node *kn, kuid_t kuid,
kgid_t kgid)
{
struct iattr newattrs = {
.ia_valid = ATTR_UID | ATTR_GID,
.ia_uid = kuid,
.ia_gid = kgid,
};
return kernfs_setattr(kn, &newattrs);
}
/**
* sysfs_link_change_owner - change owner of a sysfs file.
* @kobj: object of the kernfs_node the symlink is located in.
* @targ: object of the kernfs_node the symlink points to.
* @name: name of the link.
* @kuid: new owner's kuid
* @kgid: new owner's kgid
*
* This function looks up the sysfs symlink entry @name under @kobj and changes
* the ownership to @kuid/@kgid. The symlink is looked up in the namespace of
* @targ.
*
* Returns 0 on success or error code on failure.
*/
int sysfs_link_change_owner(struct kobject *kobj, struct kobject *targ,
const char *name, kuid_t kuid, kgid_t kgid)
{
struct kernfs_node *kn = NULL;
int error;
if (!name || !kobj->state_in_sysfs || !targ->state_in_sysfs)
return -EINVAL;
error = -ENOENT;
kn = kernfs_find_and_get_ns(kobj->sd, name, targ->sd->ns);
if (!kn)
goto out;
error = -EINVAL;
if (kernfs_type(kn) != KERNFS_LINK)
goto out;
if (kn->symlink.target_kn->priv != targ)
goto out;
error = internal_change_owner(kn, kuid, kgid);
out:
kernfs_put(kn);
return error;
}
/**
* sysfs_file_change_owner - change owner of a sysfs file.
* @kobj: object.
* @name: name of the file to change.
* @kuid: new owner's kuid
* @kgid: new owner's kgid
*
* This function looks up the sysfs entry @name under @kobj and changes the
* ownership to @kuid/@kgid.
*
* Returns 0 on success or error code on failure.
*/
int sysfs_file_change_owner(struct kobject *kobj, const char *name, kuid_t kuid,
kgid_t kgid)
{
struct kernfs_node *kn;
int error;
if (!name)
return -EINVAL;
if (!kobj->state_in_sysfs)
return -EINVAL;
kn = kernfs_find_and_get(kobj->sd, name);
if (!kn)
return -ENOENT;
error = internal_change_owner(kn, kuid, kgid);
kernfs_put(kn);
return error;
}
EXPORT_SYMBOL_GPL(sysfs_file_change_owner);
/**
* sysfs_change_owner - change owner of the given object.
* @kobj: object.
* @kuid: new owner's kuid
* @kgid: new owner's kgid
*
* Change the owner of the default directory, files, groups, and attributes of
* @kobj to @kuid/@kgid. Note that sysfs_change_owner mirrors how the sysfs
* entries for a kobject are added by driver core. In summary,
* sysfs_change_owner() takes care of the default directory entry for @kobj,
* the default attributes associated with the ktype of @kobj and the default
* attributes associated with the ktype of @kobj.
* Additional properties not added by driver core have to be changed by the
* driver or subsystem which created them. This is similar to how
* driver/subsystem specific entries are removed.
*
* Returns 0 on success or error code on failure.
*/
int sysfs_change_owner(struct kobject *kobj, kuid_t kuid, kgid_t kgid)
{
int error;
const struct kobj_type *ktype;
if (!kobj->state_in_sysfs)
return -EINVAL;
/* Change the owner of the kobject itself. */
error = internal_change_owner(kobj->sd, kuid, kgid);
if (error)
return error;
ktype = get_ktype(kobj);
if (ktype) {
struct attribute **kattr;
/*
* Change owner of the default attributes associated with the
* ktype of @kobj.
*/
for (kattr = ktype->default_attrs; kattr && *kattr; kattr++) {
error = sysfs_file_change_owner(kobj, (*kattr)->name,
kuid, kgid);
if (error)
return error;
}
/*
* Change owner of the default groups associated with the
* ktype of @kobj.
*/
error = sysfs_groups_change_owner(kobj, ktype->default_groups,
kuid, kgid);
if (error)
return error;
}
return 0;
}
EXPORT_SYMBOL_GPL(sysfs_change_owner);
...@@ -13,6 +13,7 @@ ...@@ -13,6 +13,7 @@
#include <linux/dcache.h> #include <linux/dcache.h>
#include <linux/namei.h> #include <linux/namei.h>
#include <linux/err.h> #include <linux/err.h>
#include <linux/fs.h>
#include "sysfs.h" #include "sysfs.h"
...@@ -457,3 +458,117 @@ int __compat_only_sysfs_link_entry_to_kobj(struct kobject *kobj, ...@@ -457,3 +458,117 @@ int __compat_only_sysfs_link_entry_to_kobj(struct kobject *kobj,
return PTR_ERR_OR_ZERO(link); return PTR_ERR_OR_ZERO(link);
} }
EXPORT_SYMBOL_GPL(__compat_only_sysfs_link_entry_to_kobj); EXPORT_SYMBOL_GPL(__compat_only_sysfs_link_entry_to_kobj);
static int sysfs_group_attrs_change_owner(struct kernfs_node *grp_kn,
const struct attribute_group *grp,
struct iattr *newattrs)
{
struct kernfs_node *kn;
int error;
if (grp->attrs) {
struct attribute *const *attr;
for (attr = grp->attrs; *attr; attr++) {
kn = kernfs_find_and_get(grp_kn, (*attr)->name);
if (!kn)
return -ENOENT;
error = kernfs_setattr(kn, newattrs);
kernfs_put(kn);
if (error)
return error;
}
}
if (grp->bin_attrs) {
struct bin_attribute *const *bin_attr;
for (bin_attr = grp->bin_attrs; *bin_attr; bin_attr++) {
kn = kernfs_find_and_get(grp_kn, (*bin_attr)->attr.name);
if (!kn)
return -ENOENT;
error = kernfs_setattr(kn, newattrs);
kernfs_put(kn);
if (error)
return error;
}
}
return 0;
}
/**
* sysfs_group_change_owner - change owner of an attribute group.
* @kobj: The kobject containing the group.
* @grp: The attribute group.
* @kuid: new owner's kuid
* @kgid: new owner's kgid
*
* Returns 0 on success or error code on failure.
*/
int sysfs_group_change_owner(struct kobject *kobj,
const struct attribute_group *grp, kuid_t kuid,
kgid_t kgid)
{
struct kernfs_node *grp_kn;
int error;
struct iattr newattrs = {
.ia_valid = ATTR_UID | ATTR_GID,
.ia_uid = kuid,
.ia_gid = kgid,
};
if (!kobj->state_in_sysfs)
return -EINVAL;
if (grp->name) {
grp_kn = kernfs_find_and_get(kobj->sd, grp->name);
} else {
kernfs_get(kobj->sd);
grp_kn = kobj->sd;
}
if (!grp_kn)
return -ENOENT;
error = kernfs_setattr(grp_kn, &newattrs);
if (!error)
error = sysfs_group_attrs_change_owner(grp_kn, grp, &newattrs);
kernfs_put(grp_kn);
return error;
}
EXPORT_SYMBOL_GPL(sysfs_group_change_owner);
/**
* sysfs_groups_change_owner - change owner of a set of attribute groups.
* @kobj: The kobject containing the groups.
* @groups: The attribute groups.
* @kuid: new owner's kuid
* @kgid: new owner's kgid
*
* Returns 0 on success or error code on failure.
*/
int sysfs_groups_change_owner(struct kobject *kobj,
const struct attribute_group **groups,
kuid_t kuid, kgid_t kgid)
{
int error = 0, i;
if (!kobj->state_in_sysfs)
return -EINVAL;
if (!groups)
return 0;
for (i = 0; groups[i]; i++) {
error = sysfs_group_change_owner(kobj, groups[i], kuid, kgid);
if (error)
break;
}
return error;
}
EXPORT_SYMBOL_GPL(sysfs_groups_change_owner);
...@@ -817,6 +817,7 @@ extern struct device *device_find_child_by_name(struct device *parent, ...@@ -817,6 +817,7 @@ extern struct device *device_find_child_by_name(struct device *parent,
extern int device_rename(struct device *dev, const char *new_name); extern int device_rename(struct device *dev, const char *new_name);
extern int device_move(struct device *dev, struct device *new_parent, extern int device_move(struct device *dev, struct device *new_parent,
enum dpm_order dpm_order); enum dpm_order dpm_order);
extern int device_change_owner(struct device *dev, kuid_t kuid, kgid_t kgid);
extern const char *device_get_devnode(struct device *dev, extern const char *device_get_devnode(struct device *dev,
umode_t *mode, kuid_t *uid, kgid_t *gid, umode_t *mode, kuid_t *uid, kgid_t *gid,
const char **tmp); const char **tmp);
......
...@@ -310,6 +310,18 @@ static inline void sysfs_enable_ns(struct kernfs_node *kn) ...@@ -310,6 +310,18 @@ static inline void sysfs_enable_ns(struct kernfs_node *kn)
return kernfs_enable_ns(kn); return kernfs_enable_ns(kn);
} }
int sysfs_file_change_owner(struct kobject *kobj, const char *name, kuid_t kuid,
kgid_t kgid);
int sysfs_change_owner(struct kobject *kobj, kuid_t kuid, kgid_t kgid);
int sysfs_link_change_owner(struct kobject *kobj, struct kobject *targ,
const char *name, kuid_t kuid, kgid_t kgid);
int sysfs_groups_change_owner(struct kobject *kobj,
const struct attribute_group **groups,
kuid_t kuid, kgid_t kgid);
int sysfs_group_change_owner(struct kobject *kobj,
const struct attribute_group *groups, kuid_t kuid,
kgid_t kgid);
#else /* CONFIG_SYSFS */ #else /* CONFIG_SYSFS */
static inline int sysfs_create_dir_ns(struct kobject *kobj, const void *ns) static inline int sysfs_create_dir_ns(struct kobject *kobj, const void *ns)
...@@ -522,6 +534,40 @@ static inline void sysfs_enable_ns(struct kernfs_node *kn) ...@@ -522,6 +534,40 @@ static inline void sysfs_enable_ns(struct kernfs_node *kn)
{ {
} }
static inline int sysfs_file_change_owner(struct kobject *kobj,
const char *name, kuid_t kuid,
kgid_t kgid)
{
return 0;
}
static inline int sysfs_link_change_owner(struct kobject *kobj,
struct kobject *targ,
const char *name, kuid_t kuid,
kgid_t kgid)
{
return 0;
}
static inline int sysfs_change_owner(struct kobject *kobj, kuid_t kuid, kgid_t kgid)
{
return 0;
}
static inline int sysfs_groups_change_owner(struct kobject *kobj,
const struct attribute_group **groups,
kuid_t kuid, kgid_t kgid)
{
return 0;
}
static inline int sysfs_group_change_owner(struct kobject *kobj,
const struct attribute_group **groups,
kuid_t kuid, kgid_t kgid)
{
return 0;
}
#endif /* CONFIG_SYSFS */ #endif /* CONFIG_SYSFS */
static inline int __must_check sysfs_create_file(struct kobject *kobj, static inline int __must_check sysfs_create_file(struct kobject *kobj,
......
...@@ -10003,6 +10003,7 @@ EXPORT_SYMBOL(unregister_netdev); ...@@ -10003,6 +10003,7 @@ EXPORT_SYMBOL(unregister_netdev);
int dev_change_net_namespace(struct net_device *dev, struct net *net, const char *pat) int dev_change_net_namespace(struct net_device *dev, struct net *net, const char *pat)
{ {
struct net *net_old = dev_net(dev);
int err, new_nsid, new_ifindex; int err, new_nsid, new_ifindex;
ASSERT_RTNL(); ASSERT_RTNL();
...@@ -10018,7 +10019,7 @@ int dev_change_net_namespace(struct net_device *dev, struct net *net, const char ...@@ -10018,7 +10019,7 @@ int dev_change_net_namespace(struct net_device *dev, struct net *net, const char
/* Get out if there is nothing todo */ /* Get out if there is nothing todo */
err = 0; err = 0;
if (net_eq(dev_net(dev), net)) if (net_eq(net_old, net))
goto out; goto out;
/* Pick the destination device name, and ensure /* Pick the destination device name, and ensure
...@@ -10094,6 +10095,12 @@ int dev_change_net_namespace(struct net_device *dev, struct net *net, const char ...@@ -10094,6 +10095,12 @@ int dev_change_net_namespace(struct net_device *dev, struct net *net, const char
err = device_rename(&dev->dev, dev->name); err = device_rename(&dev->dev, dev->name);
WARN_ON(err); WARN_ON(err);
/* Adapt owner in case owning user namespace of target network
* namespace is different from the original one.
*/
err = netdev_change_owner(dev, net_old, net);
WARN_ON(err);
/* Add the device back in the hashes */ /* Add the device back in the hashes */
list_netdevice(dev); list_netdevice(dev);
......
...@@ -944,6 +944,24 @@ static int rx_queue_add_kobject(struct net_device *dev, int index) ...@@ -944,6 +944,24 @@ static int rx_queue_add_kobject(struct net_device *dev, int index)
kobject_put(kobj); kobject_put(kobj);
return error; return error;
} }
static int rx_queue_change_owner(struct net_device *dev, int index, kuid_t kuid,
kgid_t kgid)
{
struct netdev_rx_queue *queue = dev->_rx + index;
struct kobject *kobj = &queue->kobj;
int error;
error = sysfs_change_owner(kobj, kuid, kgid);
if (error)
return error;
if (dev->sysfs_rx_queue_group)
error = sysfs_group_change_owner(
kobj, dev->sysfs_rx_queue_group, kuid, kgid);
return error;
}
#endif /* CONFIG_SYSFS */ #endif /* CONFIG_SYSFS */
int int
...@@ -981,6 +999,29 @@ net_rx_queue_update_kobjects(struct net_device *dev, int old_num, int new_num) ...@@ -981,6 +999,29 @@ net_rx_queue_update_kobjects(struct net_device *dev, int old_num, int new_num)
#endif #endif
} }
static int net_rx_queue_change_owner(struct net_device *dev, int num,
kuid_t kuid, kgid_t kgid)
{
#ifdef CONFIG_SYSFS
int error = 0;
int i;
#ifndef CONFIG_RPS
if (!dev->sysfs_rx_queue_group)
return 0;
#endif
for (i = 0; i < num; i++) {
error = rx_queue_change_owner(dev, i, kuid, kgid);
if (error)
break;
}
return error;
#else
return 0;
#endif
}
#ifdef CONFIG_SYSFS #ifdef CONFIG_SYSFS
/* /*
* netdev_queue sysfs structures and functions. * netdev_queue sysfs structures and functions.
...@@ -1486,6 +1527,23 @@ static int netdev_queue_add_kobject(struct net_device *dev, int index) ...@@ -1486,6 +1527,23 @@ static int netdev_queue_add_kobject(struct net_device *dev, int index)
kobject_put(kobj); kobject_put(kobj);
return error; return error;
} }
static int tx_queue_change_owner(struct net_device *ndev, int index,
kuid_t kuid, kgid_t kgid)
{
struct netdev_queue *queue = ndev->_tx + index;
struct kobject *kobj = &queue->kobj;
int error;
error = sysfs_change_owner(kobj, kuid, kgid);
if (error)
return error;
#ifdef CONFIG_BQL
error = sysfs_group_change_owner(kobj, &dql_group, kuid, kgid);
#endif
return error;
}
#endif /* CONFIG_SYSFS */ #endif /* CONFIG_SYSFS */
int int
...@@ -1520,6 +1578,25 @@ netdev_queue_update_kobjects(struct net_device *dev, int old_num, int new_num) ...@@ -1520,6 +1578,25 @@ netdev_queue_update_kobjects(struct net_device *dev, int old_num, int new_num)
#endif /* CONFIG_SYSFS */ #endif /* CONFIG_SYSFS */
} }
static int net_tx_queue_change_owner(struct net_device *dev, int num,
kuid_t kuid, kgid_t kgid)
{
#ifdef CONFIG_SYSFS
int error = 0;
int i;
for (i = 0; i < num; i++) {
error = tx_queue_change_owner(dev, i, kuid, kgid);
if (error)
break;
}
return error;
#else
return 0;
#endif /* CONFIG_SYSFS */
}
static int register_queue_kobjects(struct net_device *dev) static int register_queue_kobjects(struct net_device *dev)
{ {
int error = 0, txq = 0, rxq = 0, real_rx = 0, real_tx = 0; int error = 0, txq = 0, rxq = 0, real_rx = 0, real_tx = 0;
...@@ -1554,6 +1631,31 @@ static int register_queue_kobjects(struct net_device *dev) ...@@ -1554,6 +1631,31 @@ static int register_queue_kobjects(struct net_device *dev)
return error; return error;
} }
static int queue_change_owner(struct net_device *ndev, kuid_t kuid, kgid_t kgid)
{
int error = 0, real_rx = 0, real_tx = 0;
#ifdef CONFIG_SYSFS
if (ndev->queues_kset) {
error = sysfs_change_owner(&ndev->queues_kset->kobj, kuid, kgid);
if (error)
return error;
}
real_rx = ndev->real_num_rx_queues;
#endif
real_tx = ndev->real_num_tx_queues;
error = net_rx_queue_change_owner(ndev, real_rx, kuid, kgid);
if (error)
return error;
error = net_tx_queue_change_owner(ndev, real_tx, kuid, kgid);
if (error)
return error;
return 0;
}
static void remove_queue_kobjects(struct net_device *dev) static void remove_queue_kobjects(struct net_device *dev)
{ {
int real_rx = 0, real_tx = 0; int real_rx = 0, real_tx = 0;
...@@ -1767,6 +1869,37 @@ int netdev_register_kobject(struct net_device *ndev) ...@@ -1767,6 +1869,37 @@ int netdev_register_kobject(struct net_device *ndev)
return error; return error;
} }
/* Change owner for sysfs entries when moving network devices across network
* namespaces owned by different user namespaces.
*/
int netdev_change_owner(struct net_device *ndev, const struct net *net_old,
const struct net *net_new)
{
struct device *dev = &ndev->dev;
kuid_t old_uid, new_uid;
kgid_t old_gid, new_gid;
int error;
net_ns_get_ownership(net_old, &old_uid, &old_gid);
net_ns_get_ownership(net_new, &new_uid, &new_gid);
/* The network namespace was changed but the owning user namespace is
* identical so there's no need to change the owner of sysfs entries.
*/
if (uid_eq(old_uid, new_uid) && gid_eq(old_gid, new_gid))
return 0;
error = device_change_owner(dev, new_uid, new_gid);
if (error)
return error;
error = queue_change_owner(ndev, new_uid, new_gid);
if (error)
return error;
return 0;
}
int netdev_class_create_file_ns(const struct class_attribute *class_attr, int netdev_class_create_file_ns(const struct class_attribute *class_attr,
const void *ns) const void *ns)
{ {
......
...@@ -8,5 +8,7 @@ void netdev_unregister_kobject(struct net_device *); ...@@ -8,5 +8,7 @@ void netdev_unregister_kobject(struct net_device *);
int net_rx_queue_update_kobjects(struct net_device *, int old_num, int new_num); int net_rx_queue_update_kobjects(struct net_device *, int old_num, int new_num);
int netdev_queue_update_kobjects(struct net_device *net, int netdev_queue_update_kobjects(struct net_device *net,
int old_num, int new_num); int old_num, int new_num);
int netdev_change_owner(struct net_device *, const struct net *net_old,
const struct net *net_new);
#endif #endif
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment