4

I'm currently writing a bash script that uses inotifywait to perform certain actions on a user-supplied list of files and directories.

It has come to my attention that unlike a lot of shell tools, inotifywait is unable to separate output records with \0. This leaves the possibility of injection attacks with specifically crafted, but legal filenames (containing newlines).

I would like to work around this to ensure my script does not introduce any unnecessary vulnerabilities. My approach is as folllows:

  • Ensure all files/paths passed for inotifywait to watch have trailing backslashes removed
  • Format inotifywait output with --format "%e %w%f//" to produce output as follows:
    • <EVENT LIST> <FILE PATH>//
  • Pipe inotifywait output to sed; any // found at the ends of lines with \0
  • Use bash while read loop to read \0-separated records
  • This means after the first record, all following records will have an extra leading newline. This is stripped off
  • Each record may then be split at the first space - before the space is the event list (comma separated as per inotifywait) - and after the space the full pathname associated with the event
#!/bin/bash

shopt -s extglob
watchlist=("${@}")

# Remove trailing slashes from any watchlist elements
watchlist=("${watchlist[@]%%+(/)}")

# Reduce multiple consecutive slashes to singles as per @meuh
watchlist=("${watchlist[@]//+(\/)/\/}")

printf -vnewline "\n"

inotifywait -qrm "${watchlist[@]}" --format "%e %w%f//" | \
    sed -u 's%//$%\x00%' | \
    while IFS= read -r -d '' line; do
        line="${line#${newline}}"
        events="${line%% *}"
        filepath="${line#* }"
        printf "events=%s\nfilepath=%q\n" "$events" "$filepath"
    done

As far as I can tell, this handles file/path names containing funny characters - spaces, newlines, quotes, etc. But it seems like a rather inelegant kludge.

For the purposes of this question, the ${watchlist[]} array is just copied from command-line parameters, but this array may be build otherwise and may contain "funny" characters.

  1. Are there any malicious paths that could break this? i.e. make the contents of the $events and $filepath variables be incorrect for any given event?

  2. If this is water-tight, is there any cleaner way to do this?

Note I know I could easily write a program to call inotify_add_watch() and friends to get around this. But for now due to other dependencies I am working in bash.


I've been conflicted on whether to post this here or codereview.SE or even the main so.SE.

2

You would need to sanitize watchlist to replace any // with /. Consider a directory named \nabc (where \n is a newline):

$ mkdir t
$ mkdir t/$'\nabc'
$ touch t/$'\nabc'/x

If passed the directory t//$'\nabc' you will see output with bogus // at the end of lines:

$ inotifywait -m -r t//$'\nabc' --format "%e %w%f//" 
Setting up watches.  Beware: since -r was given, this may take a while!
Watches established.
OPEN t//
abc/x//
ATTRIB t//
abc/x//
CLOSE_WRITE,CLOSE t//
abc/x//

Note, you could also use -c instead of --format to get csv style output, which double-quotes filenames with newlines, but it is harder to parse, and in my case core dumps on the above example.

Example output for -c and touch t/$'new\nfile':

t/,CREATE,"new
file"
  • Thanks, good catch. I've edited this suggestion into my question. – Digital Trauma May 8 '18 at 0:47
  • As for CSV, the inotifywait version I have segfaults for filenames containing newlines in CSV mode. E.g. touch new$'\n'line inotifywait -c new$'\n'line & touch new$'\n'line. Non-CSV mode does not crash with this filename. I built the latest from source and it looks like this crash has been fixed, but unfortunately I'm restricted to an older version on my embedded platform. – Digital Trauma May 8 '18 at 0:56
2

There is fork that supports NUL-delimited output, You can use it like this:

#!/bin/bash

shopt -s extglob
watchlist=("${@}")

# Remove trailing slashes from any watchlist elements
watchlist=("${watchlist[@]%%+(/)}")

printf -vnewline "\n"

inotifywait -qrm "${watchlist[@]}" --format "%e %w%f%0" | \
while IFS= read -r -d '' line; do
    events="${line%% *}"
    filepath="${line#* }"
    printf "events=%s\nfilepath=%q\n" "$events" "$filepath"
done

Note this version does not add newline by default when --format is used.

  • Thanks - unfortunately for now I need to work with the existing inotifywait version in the embedded platform I am working on. Good info though. – Digital Trauma May 8 '18 at 0:37

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.