16

I'm working on a LAMP web app and there is a scheduled process somewhere which keeps creating a folder called shop in the root of the site. Every time this appears it causes conflicts with rewrite rules in the app, not good.

Until I find the offending script, is there a way to prevent any folder called shop being created in the root? I know that I can change the permissions on a folder to prevent it's contents being changed, but I have not found a way to prevent a folder of a certain name being created.

30

You can't, given the user creating the directory has sufficient permission to write on the parent directory.

You can instead leverage the inotify family of system calls provided by the Linux kernel, to watch for the creation (and optionally mv-ing) of directory shop in the given directory, if created (or optionally mv-ed), rm the directory.

The userspace program you need in this case is inotifywait (comes with inotify-tools, install it first if needed).


Assuming the directory shop would be residing in /foo/bar directory, let's set a monitoring for /foo/bar/shop creation, and rm instantly if created:

inotifywait -qme create /foo/bar | \
             awk '/,ISDIR shop$/ { system("rm -r -- /foo/bar/shop") }'
  • inotifywait -qme create /foo/bar watches /foo/bar directory for any file/directory that might be created i.e. watch for any create event

  • If created, awk '/,ISDIR shop$/ { system("rm -r -- /foo/bar/shop") }' checks if the file happens to be a directory and the name is shop (/,ISDIR shop$/), if so rm the directory (system("rm -r -- /foo/bar/shop"))

You need to run the command as a user that has write permission on directory /foo/bar for removal of shop from the directory.


If you want to monitor mv-ing operations too, add watch for moved_to event too:

inotifywait -qme create,moved_to /foo/bar | \
             awk '/,ISDIR shop$/ { system("rm -r -- /foo/bar/shop") }'

Just to note, if you are looking for a file, not directory, named shop:

inotifywait -qme create /foo/bar | \
                 awk '$NF == "shop" { system("rm -- /foo/bar/shop") }'

inotifywait -qme create,moved_to /foo/bar | \
                 awk '$NF == "shop" { system("rm -- /foo/bar/shop") }'
  • 2
    Moreover if you're using inotifywait to do this it's also possible that the trigger might also be able to catch the process with ps -ef – roaima Oct 3 '16 at 5:43
30

To answer literally based on the question of preventing a folder of a certain name to be created.

touch shop

You can't create a directory if a file with a identical name existing

mkdir: cannot create directory ‘shop’: File exists

  • 6
    This * may not* be enough. mkdir may not do it, but it can be done. It's a good first try though. – coteyr Oct 3 '16 at 10:15
  • 9
    On Linux systems, use chattr +i shop to make it immutable. Until the immutable flag is removed, it can't even be renamed/deleted. – R.. Oct 3 '16 at 19:19
  • 1
    This is a smart little trick, not quite right for my situation but a clever idea for certain circumstances 👍 – Andrew Oct 3 '16 at 20:15
  • 1
    @R.. interesting. I expected rename(2) to still work, since name is not a part of inode, but it doesn't. Quick internet search doesn't reveal why. Any hints? – domen Oct 4 '16 at 8:37
  • @domen: Because that's part of the purpose of the "immutable" attribute. I suspect it takes nontrivial extra work at the implementation level to prevent rename from working. – R.. Oct 4 '16 at 15:10
4

What about hijacking mkdir syscall with LD_PRELOAD...?

$ ls
test.c
$ cat test.c 
#define _GNU_SOURCE

#include <string.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dlfcn.h>

typedef int (*orig_mkdir_func_type)(const char *path, mode_t mode);

int mkdir(const char *path, mode_t mode) {
    if(!strcmp(path, "shop")) return 1;

    orig_mkdir_func_type orig_func;
    orig_func = (orig_mkdir_func_type)dlsym(RTLD_NEXT, "mkdir");
    return orig_func(path, mode);
}
$ gcc -shared -fPIC test.c -o test.so
$ LD_PRELOAD='./test.so' mkdir test
$ LD_PRELOAD='./test.so' mkdir shop
mkdir: cannot create directory ‘shop’: No such file or directory
$ ls
test  test.c  test.so

Note that inside this handler you can log PID of process that want to create this directory instead:

$ cat test.c 
#define _GNU_SOURCE

#include <string.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/stat.h>
#include <sys/types.h>
#include <dlfcn.h>

typedef int (*orig_mkdir_func_type)(const char *path, mode_t mode);

int mkdir(const char *path, mode_t mode) {
    if(!strcmp(path, "shop")) {
        FILE* fp = fopen("/tmp/log.txt", "w");
        fprintf(fp, "PID of evil script: %d\n", (int)getpid());
        fclose(fp);
    }

    orig_mkdir_func_type orig_func;
    orig_func = (orig_mkdir_func_type)dlsym(RTLD_NEXT, "mkdir");
    return orig_func(path, mode);
}
$ gcc -shared -fPIC test.c -o test.so
$ LD_PRELOAD='./test.so' mkdir shop
$ cat /tmp/log.txt 
PID of evil script: 8706

You need to place this in ~/.bashrc of root (or whoever is running your app) to ensure this will be used:

export LD_PRELOAD=/path/to/test.so
  • 2
    this shouldn't have a downvote, it's a solution and a good one – cat Oct 3 '16 at 15:01
  • 4
    It's using a suitcase nuke to open a tin of beans. But hey at least they'll be cooked... – Lightness Races in Orbit Oct 3 '16 at 16:42
  • 2
    @cat: I disagree. LD_PRELOAD hacks like this are basically always wrong, and unless you know what you're doing, they can badly break the program you load them into by breaking properties like the async-signal-safety of the function you're replacing. – R.. Oct 3 '16 at 19:20
  • 1
    Also, I think fopen should have "a" instead of "w", so it can preserve previous logs – cat Oct 3 '16 at 20:01
  • 2
    This also won't work if bad code calls the syscall directly, not through libc. Or if the bad code is statically linked. – domen Oct 4 '16 at 8:40
3

(Would have commented on Miati's answer but can't remember my old account and don't have enough reputation on this new one...)

You can block creation by creating a file and then changing the file attributes.

$ sudo touch shop
$ sudo chattr +i shop

Then any attempt to do anything with that file will be blocked -- even if the user becomes root.

$ rm shop
rm: remove write-protected regular empty file ‘shop’? y
rm: cannot remove ‘shop’: Operation not permitted
$ sudo rm shop
rm: cannot remove ‘shop’: Operation not permitted
  • 2
    If you know the name / email address of your old account, you can request an account merge from the contact form linked at the bottom of every page. – wizzwizz4 Oct 3 '16 at 20:53
  • Also, why not tell us how this is related to the other answer, rather than why you decided to post? – jpaugh Oct 4 '16 at 18:21
2

Create a symlink pointing to a non-existing location inside a non-existing directory. This has some fun implications:

$ ln -s non-existent/foobar foo
$ ls -ln
total 0
lrwxrwxrwx 1 1000 1000 19 Okt  4 17:17 foo -> non-existent/foobar
$ mkdir foo
mkdir: cannot create directory ‘foo’: File exists
$ cat foo
cat: foo: No such file or directory
$ echo foo > foo
zsh: no such file or directory: foo
  1. mkdir, link and others will fail with EEXIST (File exists).
  2. Attempting to open the path for reading, writing or appending will fail with ENOENT (No such file or directory)
  3. Using stat(2) (not lstat(2) or stat(1)) on the location also fails with ENOENT. lstat will of course return the information about the symlink.

This has two advantages over some of the other solutions proposed here: (a) you don’t need a service running which tracks the creation of the directory and (b) the name appears to be nonexisting to most commands.

You will have to give it a shot, but I suspect that whatever rewrite rules you have, they don’t use lstat or other non-dereferencing commands, making them fail.

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service, privacy policy and cookie policy

Not the answer you're looking for? Browse other questions tagged or ask your own question.