From patchwork Mon Aug 19 22:08:03 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit X-Patchwork-Submitter: Giacomo Leidi X-Patchwork-Id: 67165 Return-Path: X-Original-To: patchwork@mira.cbaines.net Delivered-To: patchwork@mira.cbaines.net Received: by mira.cbaines.net (Postfix, from userid 113) id 9BC2E27BBE9; Mon, 19 Aug 2024 23:09:55 +0100 (BST) X-Spam-Checker-Version: SpamAssassin 3.4.6 (2021-04-09) on mira.cbaines.net X-Spam-Level: X-Spam-Status: No, score=-7.4 required=5.0 tests=BAYES_00,DKIM_INVALID, DKIM_SIGNED,MAILING_LIST_MULTI,RCVD_IN_MSPIKE_H2, RCVD_IN_VALIDITY_CERTIFIED,RCVD_IN_VALIDITY_RPBL,RCVD_IN_VALIDITY_SAFE, SPF_HELO_PASS,URIBL_BLOCKED autolearn=unavailable autolearn_force=no version=3.4.6 Received: from lists.gnu.org (lists.gnu.org [209.51.188.17]) by mira.cbaines.net (Postfix) with ESMTPS id 6C14727BBEB for ; Mon, 19 Aug 2024 23:09:50 +0100 (BST) Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1sgAZ9-0000Cr-7E; Mon, 19 Aug 2024 18:09:31 -0400 Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1sgAYz-0000C5-60 for guix-patches@gnu.org; Mon, 19 Aug 2024 18:09:23 -0400 Received: from debbugs.gnu.org ([2001:470:142:5::43]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1sgAYy-0003sb-G0 for guix-patches@gnu.org; Mon, 19 Aug 2024 18:09:20 -0400 DKIM-Signature: v=1; a=rsa-sha256; q=dns/txt; c=relaxed/relaxed; d=debbugs.gnu.org; s=debbugs-gnu-org; h=MIME-Version:References:In-Reply-To:Date:From:To:Subject; bh=fnoegWIFjnuS7mQqES8XFN0q6+mWfKZDt6xrwN84Cvc=; b=G06swp9kyT10b0rbzpnY4P9EylxAkmBDSYSvrvN2ep/QwfBzAzVF8oHtUgjS0Johwf9tGVeUC41L3uAKYev5mMFvj9ebtFEkCmkmLpuTaGDlaM+sKorQ9y82XC2PeumbGr+CweytkOOKUXmzd3bNTFqa2nEo3AcJgTEOCIp88CGPR0N8wylExfj+Ay1AzjkiWl/rKFrV0NoB2JFHRLbc1iOULiHieAPgGGx/79XZtm2KRY7FrGAv7v5w5GaOcCfQNq4AAKSjfful/VhdoeXqi+K6OEqiMGgnJrYsgMztQg1ukw5FfQtfQOg4Q/lQuXo6VID7gy+dU7te36R5myVILg==; Received: from Debian-debbugs by debbugs.gnu.org with local (Exim 4.84_2) (envelope-from ) id 1sgAZd-0002Co-Us for guix-patches@gnu.org; Mon, 19 Aug 2024 18:10:01 -0400 X-Loop: help-debbugs@gnu.org Subject: [bug#72337] [PATCH v2 2/3] account: Add /etc/subid and /etc/subgid allocation logic. Resent-From: Giacomo Leidi Original-Sender: "Debbugs-submit" Resent-CC: guix-patches@gnu.org Resent-Date: Mon, 19 Aug 2024 22:10:01 +0000 Resent-Message-ID: Resent-Sender: help-debbugs@gnu.org X-GNU-PR-Message: followup 72337 X-GNU-PR-Package: guix-patches X-GNU-PR-Keywords: To: 72337@debbugs.gnu.org Cc: Giacomo Leidi Received: via spool by 72337-submit@debbugs.gnu.org id=B72337.17241053438382 (code B ref 72337); Mon, 19 Aug 2024 22:10:01 +0000 Received: (at 72337) by debbugs.gnu.org; 19 Aug 2024 22:09:03 +0000 Received: from localhost ([127.0.0.1]:59377 helo=debbugs.gnu.org) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sgAYf-0002Ao-FV for submit@debbugs.gnu.org; Mon, 19 Aug 2024 18:09:02 -0400 Received: from confino.investici.org ([93.190.126.19]:39659) by debbugs.gnu.org with esmtp (Exim 4.84_2) (envelope-from ) id 1sgAYb-0002AX-Se for 72337@debbugs.gnu.org; Mon, 19 Aug 2024 18:08:59 -0400 DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=autistici.org; s=stigmate; t=1724105293; bh=fnoegWIFjnuS7mQqES8XFN0q6+mWfKZDt6xrwN84Cvc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=ZmQYPU1JvQ8oWWtjupa4d79b0E/J48IzEmEDZ3UVOJsl3FTwOUJS1fDEIgwBtrmSD 32MGvek1x1dJb2i0k6gO3qUA35fKwuVY/ig3J1D6cg3V8K9rWbQbRAZgQAopxC20G5 xkarEuelP1CXIS06a9L4odeu7VeIJpJ8KvX7Lxq8= Received: from mx1.investici.org (unknown [127.0.0.1]) by confino.investici.org (Postfix) with ESMTP id 4Wnmv16cYpz10xV; Mon, 19 Aug 2024 22:08:13 +0000 (UTC) Received: from [93.190.126.19] (mx1.investici.org [93.190.126.19]) (Authenticated sender: goodoldpaul@autistici.org) by localhost (Postfix) with ESMTPSA id 4Wnmv15tntz10xJ; Mon, 19 Aug 2024 22:08:13 +0000 (UTC) Date: Tue, 20 Aug 2024 00:08:03 +0200 Message-ID: X-Mailer: git-send-email 2.45.2 In-Reply-To: References: MIME-Version: 1.0 X-BeenThere: debbugs-submit@debbugs.gnu.org X-Mailman-Version: 2.1.18 Precedence: list X-BeenThere: guix-patches@gnu.org List-Id: List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Reply-to: Giacomo Leidi X-ACL-Warn: , Giacomo Leidi via Guix-patches X-Patchwork-Original-From: Giacomo Leidi via Guix-patches via From: Giacomo Leidi Errors-To: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org Sender: guix-patches-bounces+patchwork=mira.cbaines.net@gnu.org X-getmail-retrieved-from-mailbox: Patches * gnu/build/accounts.scm (list-set): New variable; (%sub-id-min): new variable; (%sub-id-max): new variable; (%sub-id-count): new variable; (sub-id?): new variable; (subid-range-fits?): new variable; (subid-range-fits-between?): new variable; (insert-subid-range): new variable; (reserve-subids): new variable; (range->entry): new variable; (entry->range): new variable; (allocate-subids): new variable; (subuid+subgid-databases): new variable. * gnu/system/accounts.scm (subid-range-end): New variable; (subid-range-has-start?): new variable; (subid-range-less): new variable. * test/accounts.scm: Test them. Change-Id: I8de1fd7cfe508b9c76408064d6f498471da0752d --- gnu/build/accounts.scm | 229 +++++++++++++++++++++++++++++++++++++++- gnu/system/accounts.scm | 30 ++++++ tests/accounts.scm | 108 +++++++++++++++++++ 3 files changed, 366 insertions(+), 1 deletion(-) diff --git a/gnu/build/accounts.scm b/gnu/build/accounts.scm index ea8c69f205..3cbbacfaee 100644 --- a/gnu/build/accounts.scm +++ b/gnu/build/accounts.scm @@ -74,8 +74,12 @@ (define-module (gnu build accounts) %id-max %system-id-min %system-id-max + %sub-id-min + %sub-id-max + %sub-id-count - user+group-databases)) + user+group-databases + subuid+subgid-databases)) ;;; Commentary: ;;; @@ -91,6 +95,18 @@ (define-module (gnu build accounts) ;;; ;;; Code: + +;;; +;;; General utilities. +;;; + +(define (list-set lst el k) + (if (>= k (length lst)) + `(,@lst ,el) + `(,@(list-head lst k) + ,el + ,@(list-tail lst k)))) + ;;; ;;; Machinery to define user and group databases. @@ -342,6 +358,12 @@ (define %id-max 60000) (define %system-id-min 100) (define %system-id-max 999) +;; According to Shadow's libmisc/find_new_sub_uids.c and +;; libmisc/find_new_sub_gids.c. +(define %sub-id-min 100000) +(define %sub-id-max 600100000) +(define %sub-id-count 65536) + (define (system-id? id) (and (> id %system-id-min) (<= id %system-id-max))) @@ -350,6 +372,10 @@ (define (user-id? id) (and (>= id %id-min) (< id %id-max))) +(define (sub-id? id) + (and (>= id %sub-id-min) + (< id %sub-id-max))) + (define* (allocate-id assignment #:key system?) "Return two values: a newly allocated ID, and an updated record based on ASSIGNMENT. If SYSTEM? is true, return a system ID." @@ -405,6 +431,156 @@ (define* (reserve-ids allocation ids #:key (skip? #t)) (allocation-ids allocation) ids)))) +(define (subid-range-fits? r interval-start interval-end) + (and (<= interval-start + (subid-range-start r)) + (<= (subid-range-end r) + interval-end))) + +(define (subid-range-fits-between? r a b) + (subid-range-fits? r + (+ (subid-range-start a) 1) + (- (subid-range-end b) 1))) + +(define (insert-subid-range range lst) + (define* (actualize r #:key (start %sub-id-min)) + (if (subid-range-has-start? r) + r + (subid-range + (inherit r) + (start start)))) + (define lst-length (length lst)) + (define range-name (subid-range-name range)) + (define range-start (subid-range-start range)) + (define has-start? (subid-range-has-start? range)) + (define range-end (subid-range-end range)) + + (when has-start? + (unless (and (sub-id? range-start) + (sub-id? range-end)) + (raise + (string-append "Subid range of " range-name + " from " range-start " to " range-end + " spans over illegal subids. Max allowed is " + %sub-id-max ", min is " %sub-id-min ".")))) + + (if (<= lst-length 1) + (if (= lst-length 0) + (list (actualize range)) + (if (subid-range-less range (first lst)) + (list-set lst (actualize range) 0) + (list-set lst + (actualize + range + #:start (and (subid-range-has-start? (first lst)) + (+ (subid-range-end (first lst)) 1))) + 1))) + (let loop ((i 0)) + (define next-i (+ i 1)) + (define ith-range + (list-ref lst i)) + (define ith-start + (subid-range-start ith-range)) + (define ith-has-start? + (subid-range-has-start? ith-range)) + (define ith-name + (subid-range-name ith-range)) + + (if (and + (= next-i lst-length) + (subid-range-less ith-range range)) + (let ((actual-range + (actualize + range + #:start (and ith-has-start? + (+ (subid-range-end ith-range) 1))))) + (list-set lst + actual-range + lst-length)) + (let* ((next-range + (list-ref lst next-i)) + (next-has-start? + (subid-range-has-start? next-range))) + (cond + + ((and has-start? (= range-start ith-start)) + (raise + (string-append "Subid range of " range-name + " has same start " + (number->string range-start) + " of the one " + "from " ith-name "."))) + + ((and (= i 0) + (subid-range-less range ith-range) + (or + (and + has-start? ith-has-start? + (subid-range-fits? (actualize range) + %sub-id-min + (- (subid-range-start + (actualize ith-range)) + 1))) + (not (and has-start? ith-has-start?)))) + (list-set lst (actualize range) 0)) + + ((subid-range-less range ith-range) + (raise + (string-append "Subid range of " range-name + " overlaps with the one of " + ith-name "."))) + + ((and (subid-range-less ith-range range) + (subid-range-less range next-range)) + (if (or (not (and has-start? + ith-has-start? + next-has-start?)) + + (and has-start? + ith-has-start? + next-has-start? + (subid-range-fits-between? range + ith-range + next-range))) + (list-set lst + (actualize range + #:start (and ith-has-start? + (+ (subid-range-end ith-range) 1))) + next-i) + (if (>= i lst-length) + (if (and (subid-range-less next-range range) + (let ((actual-next + (actualize next-range + #:start (and ith-has-start? + (+ (subid-range-end ith-range) 1))))) + (or (not (subid-range-has-start? actual-next)) + (subid-range-fits? + (actualize range + #:start (and next-has-start? + (+ (subid-range-end next-range) 1))) + (+ (subid-range-end actual-next) 1) + %sub-id-max)))) + (list-set lst range lst-length) + (raise + (string-append "Couldn't fit " range-name ", reached end of list."))) + (loop next-i)))) + + ((or + (not has-start?) + (subid-range-less next-range range)) + (loop next-i)) + + (else + (raise (string-append "Couldn't fit " range-name ", this should never happen."))))))))) + +(define* (reserve-subids allocation ranges) + "Mark the subid ranges listed in RANGES as reserved in ALLOCATION. +ALLOCATION is supposed to be sorted by SUBID-RANGE-LESS." + (fold insert-subid-range + allocation + (sort-list ranges + subid-range-less))) + (define (allocated? allocation id) "Return true if ID is already allocated as part of ALLOCATION." (->bool (vhash-assv id (allocation-ids allocation)))) @@ -540,6 +716,31 @@ (define* (allocate-passwd users groups #:optional (current-passwd '())) uids users))) +(define (range->entry range) + (subid-entry + (name (subid-range-name range)) + (start (subid-range-start range)) + (count (subid-range-count range)))) + +(define (entry->range entry) + (subid-range + (name (subid-entry-name entry)) + (start (subid-entry-start entry)) + (count (subid-entry-count entry)))) + +(define* (allocate-subids ranges #:optional (current-ranges '())) + "Return a list of subids entries for RANGES, a list of . Members +for each group are taken from MEMBERS, a vhash that maps ranges names to member +names. IDs found in CURRENT-RANGES, a list of subid entries, are reused." + (define subids + ;; Mark all the currently used IDs and the explicitly requested IDs as + ;; reserved. + (reserve-subids (reserve-subids (list) + current-ranges) + ranges)) + + (map range->entry subids)) + (define* (days-since-epoch #:optional (current-time current-time)) "Return the number of days elapsed since the 1st of January, 1970." (let* ((now (current-time time-utc)) @@ -615,3 +816,29 @@ (define* (user+group-databases users groups #:current-time current-time)) (values group-entries passwd-entries shadow-entries)) + +(define* (subuid+subgid-databases subuids subgids + #:key + (current-subuids + (map entry->range + (empty-if-not-found read-subuid))) + (current-subgids + (map entry->range + (empty-if-not-found read-subgid)))) + "Return two values: the list of subgid entries, and the list of subuid entries +corresponding to SUBUIDS and SUBGIDS. +Preserve stateful bits from CURRENT-SUBUIDS and CURRENT-SUBGIDS." + + (define (range-eqv? a b) + (string=? (subid-range-name a) + (subid-range-name b))) + + (define subuid-entries + (allocate-subids + (lset-difference range-eqv? subuids current-subuids) current-subuids)) + + (define subgid-entries + (allocate-subids + (lset-difference range-eqv? subgids current-subgids) current-subgids)) + + (values subuid-entries subgid-entries)) diff --git a/gnu/system/accounts.scm b/gnu/system/accounts.scm index 9a006c188d..1b88ca301f 100644 --- a/gnu/system/accounts.scm +++ b/gnu/system/accounts.scm @@ -45,6 +45,9 @@ (define-module (gnu system accounts) subid-range-name subid-range-start subid-range-count + subid-range-end + subid-range-has-start? + subid-range-less sexp->user-account sexp->user-group @@ -102,6 +105,33 @@ (define-record-type* ; find_new_sub_uids.c (default 65536))) +(define (subid-range-end range) + "Returns the last subid referenced in RANGE." + (and + (subid-range-has-start? range) + (+ (subid-range-start range) + (subid-range-count range) + -1))) + +(define (subid-range-has-start? range) + "Returns #t when RANGE's start is a number." + (number? (subid-range-start range))) + +(define (subid-range-less a b) + "Returns #t when subid range A either starts before, or is more specific +than B. When it is not possible to determine whether a range is more specific +w.r.t. another range their names are compared alphabetically." + (define start-a (subid-range-start a)) + (define start-b (subid-range-start b)) + (cond ((and (not start-a) (not start-b)) + (string< (subid-range-name a) + (subid-range-name b))) + ((and start-a start-b) + (< start-a start-b)) + (else + (and start-a + (not start-b))))) + (define (default-home-directory account) "Return the default home directory for ACCOUNT." (string-append "/home/" (user-account-name account))) diff --git a/tests/accounts.scm b/tests/accounts.scm index 4944c22f49..2fbebfaf56 100644 --- a/tests/accounts.scm +++ b/tests/accounts.scm @@ -193,6 +193,7 @@ (define %subgid-sample (define allocate-groups (@@ (gnu build accounts) allocate-groups)) (define allocate-passwd (@@ (gnu build accounts) allocate-passwd)) +(define allocate-subids (@@ (gnu build accounts) allocate-subids)) (test-equal "allocate-groups" ;; Allocate GIDs in a stateless fashion. @@ -257,6 +258,69 @@ (define allocate-passwd (@@ (gnu build accounts) allocate-passwd)) (list (group-entry (name "d") (gid (- %id-max 2)))))) +(test-equal "allocate-subids" + ;; Allocate sub IDs in a stateless fashion. + (list (subid-entry (name "root") (start %sub-id-min) (count 100)) + (subid-entry (name "t") (start 100100) (count 899)) + (subid-entry (name "x") (start 100999) (count 200))) + (allocate-subids (list + (subid-range (name "x") (count 200)) + (subid-range (name "t") (count 899))) + (list (subid-range (name "root") (count 100))))) + +(test-equal "allocate-subids with requested IDs ranges" + ;; Make sure the requested sub ID for "t" and "x" are honored. + (list (subid-entry (name "x") (start %sub-id-min) (count 200)) + (subid-entry (name "t") (start 1000000) (count 899)) + (subid-entry (name "l") (start 1000899) (count 100)) + (subid-entry (name "root") (start 1000999) (count 100))) + (allocate-subids (list + (subid-range (name "root") (count 100)) + (subid-range (name "l") (count 100))) + (list + (subid-range (name "x") (start %sub-id-min) (count 200)) + (subid-range (name "t") (start 1000000) (count 899))))) + +(test-equal "allocate-subids with interleaving" + ;; Make sure the requested sub ID for "m" is honored. + (list (subid-entry (name "x") (start %sub-id-min) (count 200)) + (subid-entry (name "t") (start 1000000) (count 899)) + (subid-entry (name "i") (start 1100000) (count 1)) + (subid-entry (name "root") (start 1100001) (count 100)) + (subid-entry (name "m") (start 1200000) (count 27))) + (allocate-subids (list (subid-range (name "m") (start 1200000) (count 27))) + (list + (subid-range (name "x") (start %sub-id-min) (count 200)) + (subid-range (name "t") (start 1000000) (count 899)) + (subid-range (name "i") (start 1100000) (count 1)) + (subid-range (name "root") (count 100))))) + +(let ((inputs+currents + (list + ;; Try impossible before + (list + (list (subid-range (name "m") (start 100100) (count 27))) + (list + (subid-range (name "x") (start %sub-id-min) (count 150)))) + ;; Try impossible after + (list + (list (subid-range (name "m") (start %sub-id-min) (count 30))) + (list + (subid-range (name "x") (start (+ 29 %sub-id-min)) (count 150)))) + ;; Try impossible between + (list + (list (subid-range (name "m") (start 100200) (count 500))) + (list + (subid-range (name "root") (start %sub-id-min) (count 100)) + (subid-range (name "x") (start (+ %sub-id-min 500)) (count 100))))))) + (test-error "allocate-subids with interleaving, impossible interleaving" + "error" + ;; Make sure it's impossible to explicitly request impossible allocations + (for-each + (lambda (lst) + (allocate-subids (first lst) (second lst))) + inputs+currents))) + (test-equal "allocate-passwd" ;; Allocate UIDs in a stateless fashion. (list (password-entry (name "alice") (uid %id-min) (gid 1000) @@ -376,4 +440,48 @@ (define allocate-passwd (@@ (gnu build accounts) allocate-passwd)) (make-time type 0 (* 24 3600 100))))) list)) +(test-equal "subuid+subgid-databases" + ;; The whole process. + (list (list (subid-entry (name "root") + (start %sub-id-min) + (count 100)) + (subid-entry (name "alice") + (start (+ %sub-id-min 100)) + (count 200)) + (subid-entry (name "bob") + (start (+ %sub-id-min 100 200)) + (count 200))) + (list + (subid-entry (name "root") + (start %sub-id-min) + (count 200)) + (subid-entry (name "alice") + (start (+ %sub-id-min 200)) + (count 400)) + (subid-entry (name "charlie") + (start (+ %sub-id-min 200 400)) + (count 300)))) + (call-with-values + (lambda () + (subuid+subgid-databases + (list (subid-range (name "root") + (start %sub-id-min) + (count 100)) + (subid-range (name "alice") + (start (+ %sub-id-min 100)) + (count 200)) + (subid-range (name "bob") + (count 200))) + (list + (subid-range (name "alice") + (count 400)) + (subid-range (name "charlie") + (count 300))) + #:current-subgids + (list (subid-range (name "root") + (start %sub-id-min) + (count 200))) + #:current-subuids '())) + list)) + (test-end "accounts")