├── contrib ├── i3 │ └── i3blocks-deny-new-usb ├── systemd │ └── deny-new-usb.service └── completion │ ├── bash │ └── usbctl │ └── zsh │ └── _usbctl ├── .github └── workflows │ └── ci.yml ├── Makefile ├── LICENSE ├── doc └── usbctl.1 └── usbctl /contrib/i3/i3blocks-deny-new-usb: -------------------------------------------------------------------------------- 1 | #!/bin/sh 2 | if usbctl check; then 3 | echo protected 4 | echo protected 5 | else 6 | echo unprotected 7 | echo unprotected 8 | echo '#FF0000' 9 | fi 10 | -------------------------------------------------------------------------------- /contrib/systemd/deny-new-usb.service: -------------------------------------------------------------------------------- 1 | [Unit] 2 | Description=deny new usb devices 3 | ConditionPathExists=/proc/sys/kernel/deny_new_usb 4 | 5 | [Service] 6 | Type=oneshot 7 | ExecStart=/usr/bin/sysctl -q kernel.deny_new_usb=1 8 | 9 | [Install] 10 | WantedBy=multi-user.target 11 | -------------------------------------------------------------------------------- /contrib/completion/bash/usbctl: -------------------------------------------------------------------------------- 1 | _usbctl_completions() { 2 | if [ "${#COMP_WORDS[@]}" != "2" ]; then 3 | return 4 | fi 5 | 6 | COMPREPLY=($(compgen -W "protect unprotect enable disable on off temporary check status list ls log version" "${COMP_WORDS[1]}")) 7 | } 8 | complete -F _usbctl_completions usbctl 9 | -------------------------------------------------------------------------------- /.github/workflows/ci.yml: -------------------------------------------------------------------------------- 1 | name: CI 2 | 3 | on: 4 | push: 5 | branches: [ master ] 6 | pull_request: 7 | branches: [ master ] 8 | schedule: 9 | - cron: '0 0 * * 0' # once a week 10 | 11 | jobs: 12 | build: 13 | name: CI on ${{ matrix.os }} 14 | runs-on: ${{ matrix.os }} 15 | 16 | strategy: 17 | fail-fast: false 18 | matrix: 19 | os: [ubuntu-latest] 20 | 21 | steps: 22 | - uses: actions/checkout@v2 23 | 24 | - name: shellcheck 25 | run: | 26 | sudo apt update 27 | sudo apt install -y shellcheck 28 | shellcheck usbctl 29 | -------------------------------------------------------------------------------- /contrib/completion/zsh/_usbctl: -------------------------------------------------------------------------------- 1 | #compdef _usbctl usbctl 2 | 3 | _usbctl() { 4 | local -a _1st_arguments 5 | _1st_arguments=( 6 | {protect,disable,off}':disallow new usb devices (protected)' 7 | {unprotect,enable,on}':allow new usb devices (unprotected)' 8 | temporary':temporarily disable protection' 9 | check':exits with 1 if usb is unprotected' 10 | status':print current protection status' 11 | {list,ls}':list currently connected usb devices' 12 | log':display usb events in the kernel ring buffer' 13 | version':display version information and exit' 14 | ) 15 | _arguments '*:: :->command' 16 | if (( CURRENT == 1 )); then 17 | _describe -t commands "usbctl command" _1st_arguments 18 | return 19 | fi 20 | } 21 | -------------------------------------------------------------------------------- /Makefile: -------------------------------------------------------------------------------- 1 | DESTDIR= 2 | PREFIX=/usr 3 | BINDIR=/bin 4 | MANDIR=/share/man/man1 5 | ZSHCOMPDIR=/share/zsh/site-functions 6 | BASHCOMPDIR=/share/bash/bash-completion/completions 7 | SYSTEMDUNITDIR=/lib/systemd/system 8 | 9 | INSTALL=install 10 | RM=rm 11 | SHELLCHECK=shellcheck 12 | 13 | .PHONY: test check install uninstall 14 | 15 | test check: 16 | $(SHELLCHECK) usbctl 17 | 18 | install: 19 | $(INSTALL) -Dm 755 usbctl -t "${DESTDIR}${PREFIX}${BINDIR}" 20 | $(INSTALL) -Dm 644 contrib/systemd/deny-new-usb.service -t "${DESTDIR}${PREFIX}${SYSTEMDUNITDIR}" 21 | $(INSTALL) -Dm 644 contrib/completion/bash/usbctl -t "${DESTDIR}${PREFIX}${BASHCOMPDIR}" 22 | $(INSTALL) -Dm 644 contrib/completion/zsh/_usbctl -t "${DESTDIR}${PREFIX}${ZSHCOMPDIR}" 23 | $(INSTALL) -Dm 644 doc/usbctl.1 -t "${DESTDIR}${PREFIX}${MANDIR}" 24 | 25 | uninstall: 26 | $(RM) -f "${DESTDIR}${PREFIX}${BINDIR}/usbctl" 27 | $(RM) -f "${DESTDIR}${PREFIX}${SYSTEMDUNITDIR}/deny-new-usb.service" 28 | $(RM) -f "${DESTDIR}${PREFIX}${BASHCOMPDIR}/usbctl" 29 | $(RM) -f "${DESTDIR}${PREFIX}${ZSHCOMPDIR}/_usbctl" 30 | $(RM) -f "${DESTDIR}${PREFIX}${MANDIR}/usbctl.1" 31 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2018 Levente Polyak and kpcyrd 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /doc/usbctl.1: -------------------------------------------------------------------------------- 1 | 2 | .TH "USBCTL" "1" "07/24/2018" "" "usbctl manual" 3 | .SH "NAME" 4 | usbctl \- Linux-hardened deny_new_usb control 5 | .SH "SYNOPSIS" 6 | \fBusbctl\fR [\fIOPTIONS\fR]... [\fICOMMAND\fR] 7 | .SH "DESCRIPTION" 8 | Control usb device and protection settings. 9 | .SH "OPTIONS" 10 | .PP 11 | \fB--machine-readable\fR 12 | .RS 4 13 | enables machine readable output mode 14 | .RE 15 | .PP 16 | .SH "COMMANDS" 17 | .PP 18 | \fBprotect\fR, \fBdisable\fR, \fBoff\fR 19 | .RS 4 20 | disallow new usb devices (protected) 21 | .RE 22 | .PP 23 | \fBunprotect\fR, \fBenable\fR, \fBon\fR 24 | .RS 4 25 | allow new usb devices (unprotected) 26 | .RE 27 | .PP 28 | \fBtemporary\fR, \fBtemp\fR, \fBtmp\fR [\fIseconds\fR] 29 | .RS 4 30 | temporarily disable protection (default 60 sec) 31 | .RE 32 | .PP 33 | \fBcheck\fR 34 | .RS 4 35 | exit with 1 if usb is unprotected 36 | .RE 37 | .PP 38 | \fBstatus\fR 39 | .RS 4 40 | print current protection status 41 | .RE 42 | .PP 43 | \fBlist\fR, \fBls\fR 44 | .RS 4 45 | list currently connected usb devices 46 | .RE 47 | .PP 48 | \fBlog\fR 49 | .RS 4 50 | display usb events in the kernel ring buffer 51 | .RE 52 | .PP 53 | \fBversion\fR 54 | .RS 4 55 | display version information and exit 56 | .RE 57 | .SH "VERSION" 58 | 1.3-dev 59 | .SH "HOMEPAGE" 60 | https://github.com/anthraxx/usbctl 61 | .RE 62 | 63 | Please report bugs and feature requests in the issue tracker. 64 | .RE 65 | .SH "AUTHORS" 66 | Levente Polyak 67 | .RE 68 | kpcyrd 69 | -------------------------------------------------------------------------------- /usbctl: -------------------------------------------------------------------------------- 1 | #!/bin/bash -p 2 | set -e 3 | 4 | VERSION="1.3-dev" 5 | SYSCTL_VAR=kernel.deny_new_usb 6 | NEW_DEVICES= 7 | OLD_DEVICES= 8 | TEMPORARY_WAIT_DEFAULT=60 9 | SUDO="sudo" 10 | 11 | # curated PATH to sanitize executables in a portable way 12 | PATH="/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin" 13 | export PATH 14 | 15 | if [[ ${EUID} -eq 0 ]]; then 16 | SUDO="" 17 | fi 18 | 19 | # prefer terminal safe colored and bold text when tput is supported 20 | if tput setaf 0 &>/dev/null; then 21 | ALL_OFF="$(tput sgr0)" 22 | BOLD="$(tput bold)" 23 | GREEN="${BOLD}$(tput setaf 2)" 24 | RED="${BOLD}$(tput setaf 1)" 25 | else 26 | ALL_OFF="\e[0m" 27 | BOLD="\e[1m" 28 | GREEN="${BOLD}\e[32m" 29 | RED="${BOLD}\e[31m" 30 | fi 31 | 32 | usage() { 33 | echo "Usage: $(basename "$0") [OPTIONS] [COMMAND]" 34 | echo "Control usb device and protection settings." 35 | echo 36 | echo "OPTIONS:" 37 | echo " --machine-readable -- enables machine readable output mode" 38 | echo 39 | echo "COMMANDS:" 40 | echo " protect, disable, off -- disallow new usb devices (protected)" 41 | echo " unprotect, enable, on -- allow new usb devices (unprotected)" 42 | echo " temporary, temp, tmp [seconds] -- temporarily disable protection (default ${TEMPORARY_WAIT_DEFAULT} sec)" 43 | echo " check -- exit with 1 if usb is unprotected" 44 | echo " status -- print current protection status" 45 | echo " list, ls -- list currently connected usb devices" 46 | echo " log -- display usb events in the kernel ring buffer" 47 | echo " version -- display version information and exit" 48 | } 49 | 50 | version() { 51 | echo "$(basename "$0") ${VERSION}" 52 | } 53 | 54 | deny_new_usb() { 55 | ${SUDO} sysctl -q "${SYSCTL_VAR}=${1}" 56 | usb_status 57 | } 58 | 59 | usb_protected() { 60 | sysctl -n "${SYSCTL_VAR}"|grep -q 1 61 | } 62 | 63 | usb_status() { 64 | if usb_protected; then 65 | if (( MACHINE_READABLE )); then 66 | printf "protected\n" 67 | else 68 | printf "USB device status: %s\n" "${GREEN}PROTECTED${ALL_OFF}" 69 | fi 70 | else 71 | if (( MACHINE_READABLE )); then 72 | printf "unprotected\n" 73 | else 74 | printf "USB device status: %s\n" "${RED}UNPROTECTED${ALL_OFF}" 75 | fi 76 | fi 77 | } 78 | 79 | usb_list() { 80 | lsusb -t 81 | } 82 | 83 | temporary() { 84 | local wait_seconds 85 | wait_seconds=$1 86 | 87 | OLD_DEVICES=$(usb_list) 88 | deny_new_usb 0 89 | echo 'Press Ctrl-C to finish' 90 | trap temporary_end INT TERM 91 | for _ in $(seq "$((wait_seconds))"); do 92 | sleep 1 93 | NEW_DEVICES=$(usb_list) 94 | diff_usb_list "${OLD_DEVICES}" "${NEW_DEVICES}" 95 | OLD_DEVICES="${NEW_DEVICES}" 96 | done 97 | temporary_end 98 | } 99 | 100 | temporary_end() { 101 | deny_new_usb 1 102 | diff_usb_list "${OLD_DEVICES}" "$(usb_list)" 103 | exit 104 | } 105 | 106 | diff_usb_list() { 107 | local OLD_DEVICES="${1}" 108 | local NEW_DEVICES="${2}" 109 | diff --color="${USBCTL_DIFF_COLOR:-auto}" <(printf "%s" "${OLD_DEVICES}") <(printf "%s" "${NEW_DEVICES}") ||: 110 | } 111 | 112 | usb_log() { 113 | DMESG=dmesg 114 | if sysctl -n kernel.dmesg_restrict | grep -q 1; then 115 | DMESG="${SUDO} ${DMESG}" 116 | fi 117 | ${DMESG} --color="${USBCTL_LOG_COLOR:-always}" --ctime|grep -i usb --color="${USBCTL_GREP_COLOR:-never}" 118 | } 119 | 120 | # option checking 121 | while :; do 122 | case "$1" in 123 | --machine-readable) 124 | MACHINE_READABLE=1 125 | USBCTL_LOG_COLOR=never 126 | USBCTL_DIFF_COLOR=never 127 | USBCTL_GREP_COLOR=never 128 | shift 129 | continue 130 | ;; 131 | on|enable|unprotect) 132 | deny_new_usb 0 133 | ;; 134 | off|disable|protect) 135 | deny_new_usb 1 136 | ;; 137 | temporary|tmp|temp) 138 | temporary "${2:-$TEMPORARY_WAIT_DEFAULT}" 139 | ;; 140 | check) 141 | usb_protected 142 | ;; 143 | status) 144 | usb_status 145 | ;; 146 | list|ls) 147 | usb_list 148 | ;; 149 | log) 150 | usb_log 151 | ;; 152 | version|--version) 153 | version 154 | ;; 155 | *) 156 | usage 157 | ;; 158 | esac 159 | break 160 | done 161 | 162 | # vim:set noet: 163 | --------------------------------------------------------------------------------