From 77bf2ef39f8a918244243407ad35beb5e6d967f2 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Tue, 3 May 2016 16:22:41 -0500 Subject: Check paths before removal/install --- kerl | 61 +++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 55 insertions(+), 6 deletions(-) diff --git a/kerl b/kerl index 1af46b4..e459bd5 100755 --- a/kerl +++ b/kerl @@ -519,8 +519,7 @@ do_install() exit 1 fi mkdir -p "$2" - if [ ! -d "$2" ]; then - echo "Destination is not a directory" + if ! is_valid_install_path "$2"; then exit 1 fi absdir=$(cd "$2" && pwd) @@ -617,7 +616,7 @@ ACTIVATE fi fi fi - + if [ -n "$KERL_BUILD_PLT" ]; then echo "Building Dialyzer PLT..." build_plt "$absdir" @@ -693,6 +692,9 @@ do_deploy() host="$1" assert_valid_installation "$2" + if ! is_valid_install_path "$2"; then + exit 1 + fi rel="$(get_name_from_install_path "$2")" path="$2" remotepath="$path" @@ -733,6 +735,53 @@ do_deploy() echo "kerl_deactivate" } +is_valid_install_path() +{ + if [ ! -d "$1" ]; then + echo "ERROR: $1 is not a directory." + return 1 + fi + + # don't allow installs into home directory + if [ "$1" = "$HOME" ]; then + echo "ERROR: You cannot install a build into $HOME. It's a really bad idea." + return 1 + fi + + # don't allow installs into .erlang because + # it's a special configuration file location + # for OTP + if [ "$1" = "$HOME/.erlang" ]; then + echo "ERROR: You cannot install a build into $HOME/.erlang. (It's a special configuration file location for OTP.)" + return 1 + fi + + if [ "$1" = "$HOME/.kerl" ]; then + echo "ERROR: You cannot install a build into $HOME/.kerl." + return 1 + fi + + # do not allow installs into directories + # that are non-empty + count=$(ls -l "$1" | wc -l) + if [ $count -ne 3 ]; then + echo "ERROR: $1 does not appear to be an empty directory." + return 1 + fi + + return 0 +} + +maybe_remove() +{ + if [ "$1" = "$HOME" ]; then + echo "WARNING: You cannot remove an install from $HOME; it's your home directory." + return 0 + fi + + rm -Rf "$1" +} + list_print() { if [ -f "$KERL_BASE_DIR/otp_$1" ]; then @@ -970,7 +1019,7 @@ case "$1" in build) rel="$(get_release_from_name "$3")" if [ -d "${KERL_BUILD_DIR:?}/$3" ]; then - rm -Rf "${KERL_BUILD_DIR:?}/$3" + maybe_remove "${KERL_BUILD_DIR:?}/$3" else if [ -z "$rel" ]; then echo "No build named $3" @@ -982,7 +1031,7 @@ case "$1" in ;; installation) assert_valid_installation "$3" - rm -Rf "$3" + maybe_remove "$3" escaped="$(echo "$3" | sed $SED_OPT -e 's#/$##' -e 's#\/#\\\/#g')" list_remove "$2"s "$escaped" echo "The installation in $3 has been deleted" @@ -1049,7 +1098,7 @@ case "$1" in *) echo "Cleaning up compilation products for $3" rm -rf "${KERL_BUILD_DIR:?}/$3" - echo "Cleaned up all compilation products under $KERL_BUILD_DIR" + echo "Cleaned up compilation products for $3 under $KERL_BUILD_DIR" ;; esac ;; -- cgit v1.2.3 From d7a6709aeb63345d1fc6d86e058df7fd6e33dd4c Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Wed, 4 May 2016 01:41:33 -0500 Subject: Be even more thorough checking paths - Never assume mkdir returns successfully - Check for bad install locations *before* creating directories - Do not permit the active erlang installation to be deleted - Do not allow someone to delete her home directory ever --- kerl | 76 ++++++++++++++++++++++++++++++++++++++------------------------------ 1 file changed, 42 insertions(+), 34 deletions(-) diff --git a/kerl b/kerl index e459bd5..4129423 100755 --- a/kerl +++ b/kerl @@ -72,7 +72,7 @@ KERL_INSTALL_MANPAGES= KERL_BUILD_PLT= KERL_BUILD_DOCS= -# ensure the base dir exsists +# ensure the base dir exists mkdir -p "$KERL_BASE_DIR" || exit 1 # source the config file if available @@ -276,8 +276,16 @@ get_newest_valid_release() is_valid_installation() { - if [ -f "$1"/activate ]; then - return 0 + if [ -f "$KERL_BASE_DIR"/otp_installations ]; then + while read -r l; do + name=$(echo "$l" | cut -d " " -f 1) + path=$(echo "$l" | cut -d " " -f 2) + if [ "$path" = "$1" ]; then + if [ -f "$1"/activate ]; then + return 0 + fi + fi + done < "$KERL_BASE_DIR"/otp_installations fi return 1 } @@ -309,7 +317,7 @@ do_git_build() assert_build_name_unused "$3" GIT=$(echo -n "$1" | $MD5SUM | cut -d ' ' -f $MD5SUM_FIELD) - mkdir -p "$KERL_GIT_DIR" + mkdir -p "$KERL_GIT_DIR" || exit 1 cd "$KERL_GIT_DIR" || exit 1 echo "Checking Erlang/OTP git repository from $1..." if [ ! -d "$GIT" ]; then @@ -327,7 +335,7 @@ do_git_build() fi rm -Rf "${KERL_BUILD_DIR:?}/$3" - mkdir -p "$KERL_BUILD_DIR/$3" + mkdir -p "$KERL_BUILD_DIR/$3" || exit 1 cd "$KERL_BUILD_DIR/$3" || exit 1 git clone -l "$KERL_GIT_DIR/$GIT" otp_src_git > /dev/null 2>&1 if [ $? -ne 0 ]; then @@ -377,12 +385,12 @@ do_normal_build() assert_build_name_unused "$2" FILENAME="" download $1 - mkdir -p "$KERL_BUILD_DIR/$2" + mkdir -p "$KERL_BUILD_DIR/$2" || exit 1 if [ ! -d "$KERL_BUILD_DIR/$2/$FILENAME" ]; then echo "Extracting source code" UNTARDIRNAME="$KERL_BUILD_DIR/$2/$FILENAME-kerluntar-$$" rm -rf "$UNTARDIRNAME" - mkdir -p "$UNTARDIRNAME" + mkdir -p "$UNTARDIRNAME" || exit 1 # github tarballs have a directory in the form of "otp-TAGNAME" # Ericsson tarballs have the classic otp_src_RELEASE pattern # Standardize on Ericsson format because that's what the rest of the script expects @@ -518,10 +526,10 @@ do_install() echo "No build named $1" exit 1 fi - mkdir -p "$2" if ! is_valid_install_path "$2"; then exit 1 fi + mkdir -p "$2" || exit 1 absdir=$(cd "$2" && pwd) echo "Installing Erlang/OTP $rel ($1) in $absdir..." ERL_TOP="$KERL_BUILD_DIR/$1/otp_src_$rel" @@ -596,7 +604,7 @@ ACTIVATE DOC_DIR="$KERL_BUILD_DIR/$1/release_$rel/lib/erlang" if [ -d "$DOC_DIR" ]; then echo "Installing docs..." - mkdir -p "$absdir/lib/erlang" + mkdir -p "$absdir/lib/erlang" || exit 1 cp $CP_OPT "$DOC_DIR/" "$absdir/lib/erlang" ln -s "$absdir/lib/erlang/man" "$absdir/man" ln -s "$absdir/lib/erlang/doc" "$absdir/html" @@ -648,7 +656,7 @@ download_htmldocs() build_plt() { dialyzerd=$1/dialyzer - mkdir -p $dialyzerd + mkdir -p $dialyzerd || exit 1 plt=$dialyzerd/plt build_log=$dialyzerd/build.log dialyzer=$1/bin/dialyzer @@ -737,11 +745,6 @@ do_deploy() is_valid_install_path() { - if [ ! -d "$1" ]; then - echo "ERROR: $1 is not a directory." - return 1 - fi - # don't allow installs into home directory if [ "$1" = "$HOME" ]; then echo "ERROR: You cannot install a build into $HOME. It's a really bad idea." @@ -756,17 +759,26 @@ is_valid_install_path() return 1 fi + # don't install into .kerl either. if [ "$1" = "$HOME/.kerl" ]; then echo "ERROR: You cannot install a build into $HOME/.kerl." return 1 fi - # do not allow installs into directories - # that are non-empty - count=$(ls -l "$1" | wc -l) - if [ $count -ne 3 ]; then - echo "ERROR: $1 does not appear to be an empty directory." - return 1 + # if the install directory exists, + # do not allow installs into a directory + # that is not empty + if [ -e "$1" ]; then + if [ -d "$1" ]; then + count=$(ls -la "$1" | wc -l) + if [ $count -ne 3 ]; then + echo "ERROR: $1 does not appear to be an empty directory." + return 1 + fi + else + echo "ERROR: $1 is not a directory." + return 1 + fi fi return 0 @@ -779,6 +791,12 @@ maybe_remove() return 0 fi + ACTIVE_PATH="$(get_active_path)" + if [ "$1" = "$ACTIVE_PATH" ]; then + echo "ERROR: You cannot delete the active installation. Deactivate it first." + exit 1 + fi + rm -Rf "$1" } @@ -877,7 +895,7 @@ do_active() download() { - mkdir -p "$KERL_DOWNLOAD_DIR" + mkdir -p "$KERL_DOWNLOAD_DIR" || exit 1 if [ "$KERL_BUILD_BACKEND" = "git" ]; then FILENAME="OTP-$1" github_download "$FILENAME.tar.gz" @@ -934,20 +952,10 @@ case "$1" in exit 1 fi if [ $# -eq 3 ]; then - if [ "$3" = "$HOME" ]; then - echo "Refusing to install in $HOME, this is a bad idea." - exit 1 - else - do_install "$2" "$3" - fi + do_install "$2" "$3" else if [ -z "$KERL_DEFAULT_INSTALL_DIR" ]; then - if [ "$PWD" = "$HOME" ]; then - echo "Refusing to install in $HOME, this is a bad idea." - exit 1 - else - do_install "$2" . - fi + do_install "$2" "$PWD" else do_install "$2" "$KERL_DEFAULT_INSTALL_DIR/$2" fi -- cgit v1.2.3 From 0154d1fe54a68ca2443d959317f7601e57c2bf76 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Wed, 11 May 2016 13:26:30 -0500 Subject: Canonicalize paths before checking validity --- kerl | 94 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++------ 1 file changed, 86 insertions(+), 8 deletions(-) diff --git a/kerl b/kerl index 4129423..15390c4 100755 --- a/kerl +++ b/kerl @@ -743,24 +743,102 @@ do_deploy() echo "kerl_deactivate" } + +# Quoted from https://github.com/mkropat/sh-realpath +# LICENSE: MIT + +realpath() { + canonicalize_path "$(resolve_symlinks "$1")" +} + +resolve_symlinks() { + _resolve_symlinks "$1" +} + +_resolve_symlinks() { + _assert_no_path_cycles "$@" || return + + local dir_context path + path=$(readlink -- "$1") + if [ $? -eq 0 ]; then + dir_context=$(dirname -- "$1") + _resolve_symlinks "$(_prepend_dir_context_if_necessary "$dir_context" "$path")" "$@" + else + printf '%s\n' "$1" + fi +} + +_prepend_dir_context_if_necessary() { + if [ "$1" = . ]; then + printf '%s\n' "$2" + else + _prepend_path_if_relative "$1" "$2" + fi +} + +_prepend_path_if_relative() { + case "$2" in + /* ) printf '%s\n' "$2" ;; + * ) printf '%s\n' "$1/$2" ;; + esac +} + +_assert_no_path_cycles() { + local target path + + target=$1 + shift + + for path in "$@"; do + if [ "$path" = "$target" ]; then + return 1 + fi + done +} + +canonicalize_path() { + if [ -d "$1" ]; then + _canonicalize_dir_path "$1" + else + _canonicalize_file_path "$1" + fi +} + +_canonicalize_dir_path() { + (cd "$1" 2>/dev/null && pwd -P) +} + +_canonicalize_file_path() { + local dir file + dir=$(dirname -- "$1") + file=$(basename -- "$1") + (cd "$dir" 2>/dev/null && printf '%s/%s\n' "$(pwd -P)" "$file") +} + +# END QUOTE + is_valid_install_path() { - # don't allow installs into home directory - if [ "$1" = "$HOME" ]; then - echo "ERROR: You cannot install a build into $HOME. It's a really bad idea." - return 1 - fi # don't allow installs into .erlang because # it's a special configuration file location # for OTP - if [ "$1" = "$HOME/.erlang" ]; then - echo "ERROR: You cannot install a build into $HOME/.erlang. (It's a special configuration file location for OTP.)" + if [ $(basename -- "$1") = ".erlang" ]; then + echo "ERROR: You cannot install a build into '.erlang'. (It's a special configuration file location for OTP.)" + return 1 + fi + + candidate=$(realpath "$1") + canonical_home=$(realpath "$HOME") + + # don't allow installs into home directory + if [ "$candidate" = "$canonical_home" ]; then + echo "ERROR: You cannot install a build into $HOME. It's a really bad idea." return 1 fi # don't install into .kerl either. - if [ "$1" = "$HOME/.kerl" ]; then + if [ "$candidate" = "$canonical_home/.kerl" ]; then echo "ERROR: You cannot install a build into $HOME/.kerl." return 1 fi -- cgit v1.2.3 From 1f7543f617a592c639ea60f0226575dabc676f80 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Wed, 11 May 2016 14:09:44 -0500 Subject: Canonicalize during deletes too --- kerl | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/kerl b/kerl index 15390c4..154ec2a 100755 --- a/kerl +++ b/kerl @@ -864,13 +864,16 @@ is_valid_install_path() maybe_remove() { - if [ "$1" = "$HOME" ]; then + candidate=$(realpath "$1") + canonical_home=$(realpath "$HOME") + + if [ "$candidate" = "$canonical_home" ]; then echo "WARNING: You cannot remove an install from $HOME; it's your home directory." return 0 fi ACTIVE_PATH="$(get_active_path)" - if [ "$1" = "$ACTIVE_PATH" ]; then + if [ "$candidate" = "$ACTIVE_PATH" ]; then echo "ERROR: You cannot delete the active installation. Deactivate it first." exit 1 fi -- cgit v1.2.3 From c3b80fdd9bde6b2c2cd312a0749c24926ef54699 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Wed, 11 May 2016 14:24:47 -0500 Subject: Canonicalize $KERL_BASE_DIR --- kerl | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/kerl b/kerl index 154ec2a..8ee95ca 100755 --- a/kerl +++ b/kerl @@ -830,6 +830,7 @@ is_valid_install_path() candidate=$(realpath "$1") canonical_home=$(realpath "$HOME") + canonical_base_dir=$(realpath "$KERL_BASE_DIR") # don't allow installs into home directory if [ "$candidate" = "$canonical_home" ]; then @@ -837,9 +838,9 @@ is_valid_install_path() return 1 fi - # don't install into .kerl either. - if [ "$candidate" = "$canonical_home/.kerl" ]; then - echo "ERROR: You cannot install a build into $HOME/.kerl." + # don't install into our base directory either. + if [ "$candidate" = "$canonical_base_dir" ]; then + echo "ERROR: You cannot install a build into $KERL_BASE_DIR." return 1 fi -- cgit v1.2.3 From 6d2daf42967e3b04ba39aaa8430ef2db0539bcab Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Wed, 11 May 2016 20:24:06 -0500 Subject: Update README --- README.md | 16 ++++++++++++++-- 1 file changed, 14 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 02a809e..8b4c699 100644 --- a/README.md +++ b/README.md @@ -285,9 +285,21 @@ Installs a named build to the specified filesystem location. $ kerl install r14b02 /srv/otp/r14b02 -If path is omitted the current working directory will be used. However, if `KERL_DEFAULT_INSTALL_DIR` is defined in ~/.kerlrc, `KERL_DEFAULT_INSTALL_DIR/` will be used instead. +If path is omitted the current working directory will be used. However, if +`KERL_DEFAULT_INSTALL_DIR` is defined in ~/.kerlrc, +`KERL_DEFAULT_INSTALL_DIR/` will be used instead. -*Note*: kerl assumes the specified directory is for its sole use. If you later delete it with the kerl delete command, the whole directory will be deleted, along with anything you may have added to it! +#### Install location restrictions + +**WARNING**: kerl assumes the given installation directory is for its sole use. +If you later delete it with the `kerl delete` command, the whole directory will +be deleted, along with anything you may have added to it! + +So please only install kerl in an empty (or non-existant) directory. + +If you attempt to install kerl in `$HOME` or `.erlang` or `$KERL_BASE_DIR`, +then kerl will give you an error and refuse to proceed. If you try to install +kerl in a directory that exists and is not empty, kerl will give you an error. #### Tuning -- cgit v1.2.3 From d8315197f0c2801e5c2d42b7f51dcd28c752b256 Mon Sep 17 00:00:00 2001 From: Mark Allen Date: Thu, 12 May 2016 16:14:21 -0500 Subject: On deletes, check build name or path --- kerl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/kerl b/kerl index 8ee95ca..0329f7d 100755 --- a/kerl +++ b/kerl @@ -280,7 +280,7 @@ is_valid_installation() while read -r l; do name=$(echo "$l" | cut -d " " -f 1) path=$(echo "$l" | cut -d " " -f 2) - if [ "$path" = "$1" ]; then + if [ "$name" = "$1" -o "$path" = "$1" ]; then if [ -f "$1"/activate ]; then return 0 fi -- cgit v1.2.3