@@ -403,10 +403,7 @@ load_late_nosuid_policy() {
403403load_state_without_policy () {
404404 local line sf_owner sf_group sf_mode sf_path
405405
406- # # Load the state file from disk. Each dpkg-statoverride entry is
407- # # 'owner group mode path'; the path is last and may contain spaces, so read
408- # # the three fixed fields and take the remainder as the path. A plain 4-way
409- # # split would drop any spaced path -- it could then never be un-hardened.
406+ # # Load the state file from disk.
410407 if [ -f " ${state_file} " ]; then
411408 while read -r line; do
412409 read -r sf_owner sf_group sf_mode sf_path <<< " ${line}"
@@ -434,7 +431,7 @@ load_state() {
434431 # # to mean that all whitelisting should be ignored.
435432
436433 local config_file line field_list policy_nosuid_file_item policy_file_item
437- local field_count recovered_file_name mode_index fallback_index candidate_index
434+ local one_field field_count fso_name meta_list field_index hit_barrier
438435
439436 # # Load configuration, deferring whitelist handling until later
440437 for config_file in \
@@ -458,9 +455,6 @@ load_state() {
458455 continue
459456 fi
460457
461- # # Recognize the special directive before the character-class check below,
462- # # whose allowed set excludes '=' and would otherwise reject and abort on
463- # # this documented line.
464458 if [ " ${line} " = ' whitelists_disable_all=true' ]; then
465459 whitelists_disable_all=true
466460 log info " whitelists_disable_all=true"
@@ -477,63 +471,44 @@ load_state() {
477471
478472 processed_config_line= " ${line} "
479473
480- IFS= ' ' read -r -a field_list <<< " ${line} "
481-
482- # # A filename may contain spaces. The option grammar is unambiguous at the
483- # # END of a line -- either a single trailing whitelist keyword, or a
484- # # mode/owner/group[/capability] tail whose first field is the octal mode
485- # # -- so recover the filename by parsing options from the right and
486- # # rejoining everything before them. Space-free lines are left unchanged.
487- # # The mode tail has a fixed width -- 3 fields (mode owner group) or 4
488- # # (mode owner group capability) -- so anchor the mode from the RIGHT. A
489- # # left-to-right scan misanchors when a space-delimited filename chunk is
490- # # itself a bare octal (e.g. '/path/a 744 name 0744 root root').
491- # # Two readings can be lexically valid when a filename component OR a
492- # # numeric owner looks octal ('/a 744 0644 root root' wants the spaced
493- # # name; '/foo 0744 1000 root cap' wants no recovery). Disambiguate by
494- # # which candidate exists on disk -- the tool skips nonexistent targets
495- # # anyway -- keeping the no-recovery reading when field 1 is really the mode.
496- # # Limitation: consecutive spaces collapse and quoting/escaping is not
497- # # supported; a line whose field count is still invalid after recovery is
498- # # rejected loudly (exit 200 below), not silently skipped.
474+ # # We can handle filenames with spaces. This is because the first item
475+ # # after a filename is either an octal file mode or a keyword, and we
476+ # # only support user/group names, not UIDs and GIDs, as owner
477+ # # identifiers. This means that we can scan from right to left for the
478+ # # furthest-right octal integer or keyword field and use it to separate
479+ # # the filename from the rest of the options. (If usernames could
480+ # # themselves be octal integers, this would break, but adduser.conf's
481+ # # default NAME_REGEX forbids this. This is definitely a hack, but the
482+ # # alternative is to change the format of the policy files, which would
483+ # # break custom config for users.
484+ # #
485+ # # 'read' will collapse consecutive spaces when splitting on spaces.
486+ # # Convert spaces into newlines first, then split on newlines with a
487+ # # loop.
488+ field_list= ()
489+ while IFS= read -r one_field; do
490+ field_list+=( " ${one_field} " )
491+ done < < (printf ' %s\n' " ${line} " | tr ' ' ' \n' )
499492 field_count= " ${# field_list[@]} "
500- if (( field_count > 2 )) ; then
501- case " ${field_list[field_count-1]} " in
502- exactwhitelist|matchwhitelist|disablewhitelist|nosuid)
503- recovered_file_name=" $( IFS=' ' ; printf ' %s' " ${field_list[*]: 0: field_count-1} " ) "
504- field_list=( " ${recovered_file_name} " " ${field_list[field_count-1]} " )
505- ;;
506- * )
507- # # The octal mode sits at field_count-3 (3-field tail) or
508- # # field_count-4 (4-field tail with capability). Both positions can
509- # # look octal -- a numeric owner, or a filename component -- so pick
510- # # the candidate whose recovered filename exists on disk (the tool
511- # # skips nonexistent targets anyway). A mode at field 1 means a
512- # # single-field, no-space filename: nothing to recover. Fall back to
513- # # the first (rightmost) octal candidate when neither name exists.
514- mode_index=' '
515- fallback_index=' '
516- for candidate_index in " $(( field_count - 3 )) " " $(( field_count - 4 )) " ; do
517- (( candidate_index >= 1 )) || continue
518- [[ " ${field_list[candidate_index]} " =~ ^0? [0-7]{3,4}$ ]] || continue
519- [ -z " ${fallback_index} " ] && fallback_index=" ${candidate_index} "
520- recovered_file_name=" $( IFS=' ' ; printf ' %s' " ${field_list[*]: 0: candidate_index} " ) "
521- if (( candidate_index == 1 )) || [ -e " ${recovered_file_name} " ]; then
522- mode_index=" ${candidate_index} "
523- break
524- fi
525- done
526- [ -z " ${mode_index} " ] && mode_index=" ${fallback_index} "
527- if [ -n " ${mode_index} " ] && (( mode_index > 1 )) ; then
528- recovered_file_name=" $( IFS=' ' ; printf ' %s' " ${field_list[*]: 0: mode_index} " ) "
529- field_list=( " ${recovered_file_name} " " ${field_list[@]: mode_index} " )
530- fi
531- ;;
532- esac
533- fi
534493
535- case " ${# field_list[@]} " in
536- 2|4|5)
494+ hit_barrier= ' false'
495+ fso_name= " "
496+ meta_list= ()
497+ for (( field_index = field_count - 1 ; field_index >= 0 ; \
498+ field_index-- )) ; do
499+ if [ " ${hit_barrier} " = ' false' ]; then
500+ if [[ " ${field_list[field_index]} " =~ ^[0-7]{3,4}$ ]] \
501+ || [[ " ${field_list[field_index]} " =~ ^(nosuid| exactwhitelist| matchwhitelist| disablewhitelist)$ ]]; then
502+ hit_barrier=' true'
503+ fi
504+ meta_list=( " ${field_list[field_index]} " " ${meta_list[@]} " )
505+ else
506+ fso_name=" ${field_list[field_index]}${fso_name: +" ${fso_name} " } "
507+ fi
508+ done
509+
510+ case " ${# meta_list[@]} " in
511+ 1|3|4)
537512 true
538513 ;;
539514 * )
@@ -544,29 +519,29 @@ load_state() {
544519 esac
545520
546521 # Strip trailing slash if appropriate
547- field_list[0] = " ${field_list[0] %/ } "
522+ fso_name = " ${fso_name %/ } "
548523
549- case " ${field_list[1 ]} " in
524+ case " ${meta_list[0 ]} " in
550525 ' exactwhitelist' )
551- [ ! -e " ${field_list[0] } " ] && continue
552- policy_exact_white_list+=( " ${field_list[0] } " )
526+ [ ! -e " ${fso_name } " ] && continue
527+ policy_exact_white_list+=( " ${fso_name } " )
553528 continue
554529 ;;
555530 ' matchwhitelist' )
556- policy_match_white_list+=( " ${field_list[0] } " )
531+ policy_match_white_list+=( " ${fso_name } " )
557532 continue
558533 ;;
559534 ' disablewhitelist' )
560- policy_disable_white_list+=( " ${field_list[0] } " )
535+ policy_disable_white_list+=( " ${fso_name } " )
561536 continue
562537 ;;
563538 ' nosuid' )
564- [ ! -e " ${field_list[0] } " ] && continue
565- policy_nosuid_file_list+=( " ${field_list[0] } " )
539+ [ ! -e " ${fso_name } " ] && continue
540+ policy_nosuid_file_list+=( " ${fso_name } " )
566541 ;;
567542 * )
568- [ ! -e " ${field_list[0] } " ] && continue
569- add_to_policy " ${field_list [@]} "
543+ [ ! -e " ${fso_name } " ] && continue
544+ add_to_policy " ${fso_name} " " ${meta_list [@]}"
570545 ;;
571546 esac
572547 done < " ${config_file} "
0 commit comments