Skip to contentSkip to conversation

namei: revert old behaviour for filename_lookup with LOOKUP_PARENT flag

PATCH In review Vineeth Remanan Pillai 3 participants 10 messages Oct 13, 2016 – Oct 14, 2016 VFS

lore
Share & tools
lore
#0 lore raw

PATCH 1/1 namei: revert old behaviour for filename_lookup with LOOKUP_PARENT flag

filename_lookup used to return success for non-existing file when called with LOOKUP_PARENT flag. This behaviour was changed with commit 8bcb77fabd7c ("namei: split off filename_lookupat() with LOOKUP_PARENT")

The above patch split parent lookup functionality to a different function filename_parentat and changed all calls to filename_lookup(LOOKUP_PARENT) to the new function filename_parentat. But functions like kern_path which passed the flags directly to filename_lookup regressed due to this.

This patch aims to fix the regressed behaviour by calling filename_parentat from filename_lookup if the flags contain LOOKUP_PARENT.

---
 fs/namei.c | 15 +++++++++++++++
 1 file changed, 15 insertions(+)
fs/namei.c +15 -0
index adb0414..e16ab09 100644
@@ -2292,6 +2292,21 @@ static int filename_lookup(int dfd, struct filename *name, unsigned flags,
 {
 	int retval;
 	struct nameidata nd;
+
+	if (flags & LOOKUP_PARENT) {
+		struct qstr last;
+		struct filename *filename;
+		int type;
+
+		filename = filename_parentat(dfd, name, flags ^ LOOKUP_PARENT,
+					    path, &last, &type);
+		if (IS_ERR(filename))
+			return PTR_ERR(filename);
+
+		putname(filename);
+		return 0;
+	}
+
 	if (IS_ERR(name))
 		return PTR_ERR(name);
 	if (unlikely(root)) {
Signature · 1 line
2.1.2.AMZN
maintainer
#1 lore raw
Quoted 5 lines · Vineeth Remanan Pillai

filename_lookup used to return success for non-existing file when called with LOOKUP_PARENT flag. This behaviour was changed with commit 8bcb77fabd7c ("namei: split off filename_lookupat() with LOOKUP_PARENT")

Quoted 7 lines

The above patch split parent lookup functionality to a different function filename_parentat and changed all calls to filename_lookup(LOOKUP_PARENT) to the new function filename_parentat. But functions like kern_path which passed the flags directly to filename_lookup regressed due to this.

This patch aims to fix the regressed behaviour by calling filename_parentat from filename_lookup if the flags contain LOOKUP_PARENT.

What would we want that for? Details, please. In particular, I would like to know how to use that in non-racy way. What are you doing with it?

PS: "regressed" assumes that there had been any promise of API stability; no such thing has ever existed.

#2 lore raw
In reply to Vineeth Remanan Pillai
Quoted 5 lines · Vineeth Remanan Pillai

filename_lookup used to return success for non-existing file when called with LOOKUP_PARENT flag. This behaviour was changed with commit 8bcb77fabd7c ("namei: split off filename_lookupat() with LOOKUP_PARENT")

Quoted 7 lines

The above patch split parent lookup functionality to a different function filename_parentat and changed all calls to filename_lookup(LOOKUP_PARENT) to the new function filename_parentat. But functions like kern_path which passed the flags directly to filename_lookup regressed due to this.

This patch aims to fix the regressed behaviour by calling filename_parentat from filename_lookup if the flags contain LOOKUP_PARENT.

What callers shows te problems? That's probaby were the fix need to got in, and even if not that's still part of a good bug report.

maintainer
#3 lore raw
Quoted 11 lines · Christoph Hellwig
Quoted 10 lines · Vineeth Remanan Pillai

filename_lookup used to return success for non-existing file when called with LOOKUP_PARENT flag. This behaviour was changed with commit 8bcb77fabd7c ("namei: split off filename_lookupat() with LOOKUP_PARENT")

The above patch split parent lookup functionality to a different function filename_parentat and changed all calls to filename_lookup(LOOKUP_PARENT) to the new function filename_parentat. But functions like kern_path which passed the flags directly to filename_lookup regressed due to this.

Quoted 5 lines
Quoted 2 lines

This patch aims to fix the regressed behaviour by calling filename_parentat from filename_lookup if the flags contain LOOKUP_PARENT.

What callers shows te problems? That's probaby were the fix need to got in, and even if not that's still part of a good bug report.

Out-of-tree, at a guess... Incidentally, since filename_lookup() is not exported, it's probably kern_path() that gets used wherever it is. Depending on the details of what's being attempted, kern_path_locked() might or might not be the right primitive to use, but I would probably start with checking if that's what the code in question really wants.

Use:

	// kernel_string is an kernel pointer to NUL-terminated array of char

	struct path path;
	struct dentry *dentry;

	dentry = kern_path_locked(kernel_string, &path);
	if (IS_ERR(dentry))
		// failed while getting the parent, or not a regular last
		// component (/, ., .., <something>/., <something>/..)
		sod off // no cleanup needed

	// now path contains the resolved parent and dentry points to the
	// dentry of child, possibly negative; the last component of the
	// name can be determined from dentry->d_name.  Parent directory
	// is locked, making sure that directory entry won't be changed
	// until you are done.

	if (d_is_really_negative(dentry)) {
		// parent exists, but child doesn't
	} else {
		// child exists
	}

	// clean up: drop dentry, unlock parent, drop dentry/vfsmount of parent
	dput(dentry);
	inode_unlock(path.dentry->d_inode);
	path_put(&path);
#4 lore raw
Quoted 42 lines · Al Viro
Quoted 11 lines · Christoph Hellwig
Quoted 10 lines · Vineeth Remanan Pillai

filename_lookup used to return success for non-existing file when called with LOOKUP_PARENT flag. This behaviour was changed with commit 8bcb77fabd7c ("namei: split off filename_lookupat() with LOOKUP_PARENT")

The above patch split parent lookup functionality to a different function filename_parentat and changed all calls to filename_lookup(LOOKUP_PARENT) to the new function filename_parentat. But functions like kern_path which passed the flags directly to filename_lookup regressed due to this.

Quoted 4 lines
Quoted 2 lines

This patch aims to fix the regressed behaviour by calling filename_parentat from filename_lookup if the flags contain LOOKUP_PARENT.

What callers shows te problems? That's probaby were the fix need to got in, and even if not that's still part of a good bug report.

Out-of-tree, at a guess... Incidentally, since filename_lookup() is not exported, it's probably kern_path() that gets used wherever it is. Depending on the details of what's being attempted, kern_path_locked() might or might not be the right primitive to use, but I would probably start with checking if that's what the code in question really wants.

Use:

	// kernel_string is an kernel pointer to NUL-terminated array of char

	struct path path;
	struct dentry *dentry;

	dentry = kern_path_locked(kernel_string, &path);
	if (IS_ERR(dentry))
		// failed while getting the parent, or not a regular last
		// component (/, ., .., <something>/., <something>/..)
		sod off // no cleanup needed

	// now path contains the resolved parent and dentry points to the
	// dentry of child, possibly negative; the last component of the
	// name can be determined from dentry->d_name.  Parent directory
	// is locked, making sure that directory entry won't be changed
	// until you are done.

	if (d_is_really_negative(dentry)) {
		// parent exists, but child doesn't
Quoted 9 lines
	} else {
		// child exists
	}

	// clean up: drop dentry, unlock parent, drop dentry/vfsmount of parent
	dput(dentry);
	inode_unlock(path.dentry->d_inode);
	path_put(&path);

Yes, the use case is out-of-tree and the code snippet above depicts the use . Since kern_path_locked is also not exported, out-of-tree code used kern_path for the existence check for directories.

One reference about this issue can be seen here.

http://www.gossamer-threads.com/lists/linux/kernel/2459690?do=post_view_flat#2459690

We also have a customer who complained about this functionality change.

I understand that there has been no API promises been made to this API. But since this is an exported function, the change in function could cause break in out-of-tree kernel code. I will rephrase the commit message to say "change in functionality" instead of regression

#5 lore raw
Quoted 1 line · Vineeth Remanan Pillai

Yes, the use case is out-of-tree and the code snippet above depicts the use

Case closed then. If it doesn't affect any in-tree code please don't bother spamming linux lists with your self-inflicted wounds.

maintainer
#6 lore raw
In reply to Vineeth Remanan Pillai
Quoted 8 lines · Vineeth Remanan Pillai

Yes, the use case is out-of-tree and the code snippet above depicts the use .
Since kern_path_locked is also not exported, out-of-tree code used kern_path for the existence check for directories.

One reference about this issue can be seen here.

http://www.gossamer-threads.com/lists/linux/kernel/2459690?do=post_view_flat#2459690

... and in that thread I have asked for details and got no reply whatsoever.

Quoted 8 lines

We also have a customer who complained about this functionality change.

I understand that there has been no API promises been made to this API. But since this is an
exported function, the change in function could cause break in out-of-tree kernel code. I will
rephrase the commit message to say "change in functionality" instead of regression

In principle, I have no strong objections against exporting kern_path_locked, provided it really matches what they (whoever they are) need. I'm still curious about the context, though - what is that code trying to do? Depending on the actual stuff it wants to implement, there might be better primitives for doing that *and* there might be something worth adding and exporting that would be a better match.

It's not that kern_path_locked() isn't a sane interface, but... using it might be a sign of trying to work around something missing in API. So again, please post more details.

#7 lore raw
Quoted 27 lines · Al Viro
Quoted 8 lines · Vineeth Remanan Pillai

Yes, the use case is out-of-tree and the code snippet above depicts the use .
Since kern_path_locked is also not exported, out-of-tree code used kern_path for the existence check for directories.

One reference about this issue can be seen here.

http://www.gossamer-threads.com/lists/linux/kernel/2459690?do=post_view_flat#2459690

... and in that thread I have asked for details and got no reply whatsoever.

Quoted 8 lines

We also have a customer who complained about this functionality change.

I understand that there has been no API promises been made to this API. But since this is an
exported function, the change in function could cause break in out-of-tree kernel code. I will
rephrase the commit message to say "change in functionality" instead of regression

In principle, I have no strong objections against exporting kern_path_locked, provided it really matches what they (whoever they are) need. I'm still curious about the context, though - what is that code trying to do? Depending on the actual stuff it wants to implement, there might be better primitives for doing that *and* there might be something worth adding and exporting that would be a better match.

Quoted 3 lines

It's not that kern_path_locked() isn't a sane interface, but... using it might be a sign of trying to work around something missing in API. So again, please post more details.

Unfortunately, I also do not have enough context about the customer code that uses it. Since kern_path was exported function and the behavior changed across releases, this patch is just trying to revert to the old behavior.
I clearly get what you are trying to say. It would have been really beneficial to get the context so as to understand use case and figure out alternative or better approach. But I think we should have the functionality as before and if kern_path is not the right API for this purpose, probably we should deprecate it in a phased manner.

maintainer
#8 lore raw
Quoted 10 lines · Vineeth Remanan Pillai

Unfortunately, I also do not have enough context about the customer code that uses it. Since kern_path was exported function and the behavior changed across releases, this patch is just trying to revert to the old behavior.
I clearly get what you are trying to say. It would have been really beneficial to get the context so as to understand use case and figure out alternative or better approach. But I think we should have the functionality as before and if kern_path is not the right API for this purpose, probably we
should deprecate it in a phased manner.

kern_path() is not the right API for this purpose. Never had been. It is, OTOH, the right API for other purposes, so it's not going to disappear. If you check the history of mainline tree, you'll see no such call sites. All way back to 2008 when kern_path() had been introduced, it had never been given LOOKUP_PARENT in arguments:

; git log -p -Skern_path | grep ^\+.*\<kern_path\>
+ err = kern_path(mtd_dev, LOOKUP_FOLLOW, &path);
+ error = kern_path(pathname, LOOKUP_FOLLOW, &path);
+ ret = kern_path(filename, LOOKUP_FOLLOW, &path);
+ ret = kern_path(pathname->name, LOOKUP_FOLLOW, &path);
+ err = kern_path(name, LOOKUP_FOLLOW, path);
+ error = kern_path(journal_path, LOOKUP_FOLLOW, &path);
+ error = kern_path(journal_path, LOOKUP_FOLLOW, &path);
+ rc = kern_path(name, LOOKUP_FOLLOW, &path);
+ ret = kern_path(filename, LOOKUP_FOLLOW, &path);
+ status = kern_path(nfs4_recoverydir(), LOOKUP_FOLLOW, &path);
+static struct ctl_path kern_path[] = { { .procname = "kernel", }, { } };
+ register_sysctl_paths(kern_path, pid_ns_ctl_table);
+ r = kern_path(syspath, LOOKUP_FOLLOW, &path);
+ status = kern_path(recdir, LOOKUP_FOLLOW, &path);
+ /* Drop refcount obtained by kern_path(). */
+ rc = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path);
+ ecryptfs_printk(KERN_WARNING, "kern_path() failed\n");
+ if (kern_path(tomoyo_loader, LOOKUP_FOLLOW, &path)) {
+ if (kern_path(dev_name, LOOKUP_FOLLOW, &path)) {
+ err = kern_path(mtd_dev, LOOKUP_FOLLOW, &path);
+ ret = kern_path(cache->rootdirname, LOOKUP_DIRECTORY, &path);
+ error = kern_path(pathname, LOOKUP_FOLLOW, &path);
+ err = kern_path(mountpoint, LOOKUP_FOLLOW, &path);
+ int err = kern_path(pathname, 0, &path);
+ err = kern_path(name, LOOKUP_FOLLOW, &path);
+ error = kern_path(name, LOOKUP_FOLLOW, &path);
+ error = kern_path(dev_name, LOOKUP_FOLLOW, &path);
+ if (kern_path(tomoyo_loader, LOOKUP_FOLLOW, &path)) {
+ if (pathname && kern_path(pathname, LOOKUP_FOLLOW, &path) == 0) {
+ if (pathname && kern_path(pathname, 0, &path) == 0) {
+ err = kern_path(tree->pathname, 0, &path);
+ err = kern_path(tree->pathname, 0, &path);
+ err = kern_path(new, 0, &path);
+ err = kern_path(old, 0, &path);
+ err = kern_path(tree->pathname, 0, &path);
+ error = kern_path(pathname, LOOKUP_FOLLOW, &path);
+ ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, path);
+ rc = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path);
+ err = kern_path(sunname->sun_path, LOOKUP_FOLLOW, &path);
+ err = kern_path(buf, 0, &key.ek_path);
+ err = kern_path(nxp->ex_path, 0, &path);
+ err = kern_path(nxp->ex_path, 0, &path);
+ if (kern_path(name, 0, &path)) {
+ status = kern_path(rec_dirname, LOOKUP_FOLLOW | LOOKUP_DIRECTORY,
+ status = kern_path(recdir, LOOKUP_FOLLOW, &path);
+ error = kern_path(fo_path, 0, &path);
+ err = kern_path(buf, 0, &exp.ex_path);
+ error = kern_path(name, LOOKUP_FOLLOW, &path);
+ err = kern_path(name, LOOKUP_FOLLOW, &path);
+ err = kern_path(name, LOOKUP_FOLLOW, &path);
+ err = kern_path(name, LOOKUP_FOLLOW, &path);
+ err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
+ err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
+ retval = kern_path(dir_name, LOOKUP_FOLLOW, &path);
+int kern_path(const char *name, unsigned int flags, struct path *path) +EXPORT_SYMBOL(kern_path);
+extern int kern_path(const char *, unsigned, struct path *); ;

As you can see, it had only been given LOOKUP_FOLLOW | LOOKUP_DIRECTORY, LOOKUP_FOLLOW or 0. Old behaviour in a sense of kern_path() accepting LOOKUP_PARENT is not going to come back - it would have been inherently racy all along *and* nothing in mainline kernel had ever attempted that stupidity.

LOOKUP_PARENT had always been only for primitives that had left nameidata to the caller, so that it could do something about the last component. kern_path() is nothing of that kind and no, I'm not going to reexpose nameidata to anything outside of fs/namei.c.

Out of existing primitives kern_path_locked() is the closest sane approximation. It could be exported, if your customer cares to give details. If they do not, tell them that their abuse of kern_path() accidental behaviour had been wrong all along and the old versions of their out-of-tree code are almost certainly racy. If they are interested in safe replacement, they'd better provide details.

BTW, see 15a9155fe3e8 (fix race in audit_get_nd()) for an example of similar race. That was the one and only bug of that sort that made it into the mainline. That was path_lookup() with LOOKUP_PARENT (in principle legitimate) with nd simply discarded by the caller.

#9 lore raw

Thanks for the detailed explanation. I understand the reasoning now.

Thanks,
Vineeth
________________________________________
From: Al Viro [[email protected]] on behalf of Al Viro [[email protected]]
Sent: Thursday, October 13, 2016 4:31 PM
To: Remanan Pillai, Vineeth
Cc: Christoph Hellwig; [email protected]; [email protected]; Kamata, Munehisa; Liguori, Anthony
Subject: Re: [PATCH] namei: revert old behaviour for filename_lookup with LOOKUP_PARENT flag

Quoted 10 lines · Vineeth Remanan Pillai

Unfortunately, I also do not have enough context about the customer code that uses it. Since kern_path was exported function and the behavior changed across releases, this patch is just trying to revert to the old behavior.
I clearly get what you are trying to say. It would have been really beneficial to get the context so as to understand use case and figure out alternative or better approach. But I think we should have the functionality as before and if kern_path is not the right API for this purpose, probably we
should deprecate it in a phased manner.

kern_path() is not the right API for this purpose. Never had been. It is, OTOH, the right API for other purposes, so it's not going to disappear. If you check the history of mainline tree, you'll see no such call sites. All way back to 2008 when kern_path() had been introduced, it had never been given LOOKUP_PARENT in arguments:

; git log -p -Skern_path | grep ^\+.*\<kern_path\>
+ err = kern_path(mtd_dev, LOOKUP_FOLLOW, &path);
+ error = kern_path(pathname, LOOKUP_FOLLOW, &path);
+ ret = kern_path(filename, LOOKUP_FOLLOW, &path);
+ ret = kern_path(pathname->name, LOOKUP_FOLLOW, &path);
+ err = kern_path(name, LOOKUP_FOLLOW, path);
+ error = kern_path(journal_path, LOOKUP_FOLLOW, &path);
+ error = kern_path(journal_path, LOOKUP_FOLLOW, &path);
+ rc = kern_path(name, LOOKUP_FOLLOW, &path);
+ ret = kern_path(filename, LOOKUP_FOLLOW, &path);
+ status = kern_path(nfs4_recoverydir(), LOOKUP_FOLLOW, &path);
+static struct ctl_path kern_path[] = { { .procname = "kernel", }, { } };
+ register_sysctl_paths(kern_path, pid_ns_ctl_table);
+ r = kern_path(syspath, LOOKUP_FOLLOW, &path);

+       status = kern_path(recdir, LOOKUP_FOLLOW, &path);
+       /* Drop refcount obtained by kern_path(). */
+       rc = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path);
+               ecryptfs_printk(KERN_WARNING, "kern_path() failed\n");
+       if (kern_path(tomoyo_loader, LOOKUP_FOLLOW, &path)) {
+               if (kern_path(dev_name, LOOKUP_FOLLOW, &path)) {
+       err = kern_path(mtd_dev, LOOKUP_FOLLOW, &path);
+       ret = kern_path(cache->rootdirname, LOOKUP_DIRECTORY, &path);
+       error = kern_path(pathname, LOOKUP_FOLLOW, &path);
+       err = kern_path(mountpoint, LOOKUP_FOLLOW, &path);
+       int err = kern_path(pathname, 0, &path);
+                       err = kern_path(name, LOOKUP_FOLLOW, &path);
+       error = kern_path(name, LOOKUP_FOLLOW, &path);
+       error = kern_path(dev_name, LOOKUP_FOLLOW, &path);
+       if (kern_path(tomoyo_loader, LOOKUP_FOLLOW, &path)) {
+       if (pathname && kern_path(pathname, LOOKUP_FOLLOW, &path) == 0) {
+       if (pathname && kern_path(pathname, 0, &path) == 0) {
+               err = kern_path(tree->pathname, 0, &path);
+       err = kern_path(tree->pathname, 0, &path);
+       err = kern_path(new, 0, &path);
+       err = kern_path(old, 0, &path);
+               err = kern_path(tree->pathname, 0, &path);
+       error = kern_path(pathname, LOOKUP_FOLLOW, &path);
+       ret = kern_path(symname, LOOKUP_FOLLOW|LOOKUP_DIRECTORY, path);
+       rc = kern_path(dev_name, LOOKUP_FOLLOW | LOOKUP_DIRECTORY, &path);
+               err = kern_path(sunname->sun_path, LOOKUP_FOLLOW, &path);
+               err = kern_path(buf, 0, &key.ek_path);
+       err = kern_path(nxp->ex_path, 0, &path);
+       err = kern_path(nxp->ex_path, 0, &path);
+       if (kern_path(name, 0, &path)) {
+       status = kern_path(rec_dirname, LOOKUP_FOLLOW | LOOKUP_DIRECTORY,
+       status = kern_path(recdir, LOOKUP_FOLLOW, &path);
+       error = kern_path(fo_path, 0, &path);
+       err = kern_path(buf, 0, &exp.ex_path);
+       error = kern_path(name, LOOKUP_FOLLOW, &path);
+       err = kern_path(name, LOOKUP_FOLLOW, &path);
+       err = kern_path(name, LOOKUP_FOLLOW, &path);
+       err = kern_path(name, LOOKUP_FOLLOW, &path);
+       err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
+       err = kern_path(old_name, LOOKUP_FOLLOW, &old_path);
+       retval = kern_path(dir_name, LOOKUP_FOLLOW, &path);

+int kern_path(const char *name, unsigned int flags, struct path *path) +EXPORT_SYMBOL(kern_path);
+extern int kern_path(const char *, unsigned, struct path *); ;

As you can see, it had only been given LOOKUP_FOLLOW | LOOKUP_DIRECTORY, LOOKUP_FOLLOW or 0. Old behaviour in a sense of kern_path() accepting LOOKUP_PARENT is not going to come back - it would have been inherently racy all along *and* nothing in mainline kernel had ever attempted that stupidity.

LOOKUP_PARENT had always been only for primitives that had left nameidata to the caller, so that it could do something about the last component. kern_path() is nothing of that kind and no, I'm not going to reexpose nameidata to anything outside of fs/namei.c.

Out of existing primitives kern_path_locked() is the closest sane approximation. It could be exported, if your customer cares to give details. If they do not, tell them that their abuse of kern_path() accidental behaviour had been wrong all along and the old versions of their out-of-tree code are almost certainly racy. If they are interested in safe replacement, they'd better provide details.

BTW, see 15a9155fe3e8 (fix race in audit_get_nd()) for an example of similar race. That was the one and only bug of that sort that made it into the mainline. That was path_lookup() with LOOKUP_PARENT (in principle legitimate) with nd simply discarded by the caller.