diff --git a/COPYING-5.14.0-611.16.1.el9 b/COPYING-5.14.0-611.20.1.el9 similarity index 100% rename from COPYING-5.14.0-611.16.1.el9 rename to COPYING-5.14.0-611.20.1.el9 diff --git a/Makefile.rhelver b/Makefile.rhelver index b6d4683a51..dc11f32df6 100644 --- a/Makefile.rhelver +++ b/Makefile.rhelver @@ -12,7 +12,7 @@ RHEL_MINOR = 7 # # Use this spot to avoid future merge conflicts. # Do not trim this comment. -RHEL_RELEASE = 611.16.1 +RHEL_RELEASE = 611.20.1 # # ZSTREAM diff --git a/drivers/hid/hid-multitouch.c b/drivers/hid/hid-multitouch.c index cc8df4e84d..61adfdd31c 100644 --- a/drivers/hid/hid-multitouch.c +++ b/drivers/hid/hid-multitouch.c @@ -1447,6 +1447,14 @@ static __u8 *mt_report_fixup(struct hid_device *hdev, __u8 *rdesc, if (hdev->vendor == I2C_VENDOR_ID_GOODIX && (hdev->product == I2C_DEVICE_ID_GOODIX_01E8 || hdev->product == I2C_DEVICE_ID_GOODIX_01E9)) { + if (*size < 608) { + dev_info( + &hdev->dev, + "GT7868Q fixup: report descriptor is only %u bytes, skipping\n", + *size); + return rdesc; + } + if (rdesc[607] == 0x15) { rdesc[607] = 0x25; dev_info( diff --git a/drivers/scsi/st.c b/drivers/scsi/st.c index 250d8e4e60..ce03f11dc8 100644 --- a/drivers/scsi/st.c +++ b/drivers/scsi/st.c @@ -3526,8 +3526,64 @@ static int partition_tape(struct scsi_tape *STp, int size) out: return result; } - +/* + * Handles any extra state needed for ioctls which are not st-specific. + * Called with the scsi_tape lock held, released before return + */ +static long st_common_ioctl(struct scsi_tape *STp, struct st_modedef *STm, + struct file *file, unsigned int cmd_in, + unsigned long arg) +{ + int i, retval = 0; + + if (!STm->defined) { + retval = -ENXIO; + goto out; + } + + switch (cmd_in) { + case SCSI_IOCTL_GET_IDLUN: + case SCSI_IOCTL_GET_BUS_NUMBER: + case SCSI_IOCTL_GET_PCI: + break; + case SG_IO: + case SCSI_IOCTL_SEND_COMMAND: + case CDROM_SEND_PACKET: + if (!capable(CAP_SYS_RAWIO)) { + retval = -EPERM; + goto out; + } + fallthrough; + default: + if ((i = flush_buffer(STp, 0)) < 0) { + retval = i; + goto out; + } else { /* flush_buffer succeeds */ + if (STp->can_partitions) { + i = switch_partition(STp); + if (i < 0) { + retval = i; + goto out; + } + } + } + } + mutex_unlock(&STp->lock); + + retval = scsi_ioctl(STp->device, file->f_mode & FMODE_WRITE, + cmd_in, (void __user *)arg); + if (!retval && cmd_in == SCSI_IOCTL_STOP_UNIT) { + /* unload */ + STp->rew_at_close = 0; + STp->ready = ST_NO_TAPE; + } + + return retval; +out: + mutex_unlock(&STp->lock); + return retval; +} /* The ioctl command */ static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg) @@ -3565,6 +3621,15 @@ static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg) if (retval) goto out; + switch (cmd_in) { + case MTIOCPOS: + case MTIOCGET: + case MTIOCTOP: + break; + default: + return st_common_ioctl(STp, STm, file, cmd_in, arg); + } + cmd_type = _IOC_TYPE(cmd_in); cmd_nr = _IOC_NR(cmd_in); @@ -3876,29 +3941,7 @@ static long st_ioctl(struct file *file, unsigned int cmd_in, unsigned long arg) } mt_pos.mt_blkno = blk; retval = put_user_mtpos(p, &mt_pos); - goto out; } - mutex_unlock(&STp->lock); - - switch (cmd_in) { - case SG_IO: - case SCSI_IOCTL_SEND_COMMAND: - case CDROM_SEND_PACKET: - if (!capable(CAP_SYS_RAWIO)) - return -EPERM; - break; - default: - break; - } - - retval = scsi_ioctl(STp->device, file->f_mode & FMODE_WRITE, cmd_in, p); - if (!retval && cmd_in == SCSI_IOCTL_STOP_UNIT) { - /* unload */ - STp->rew_at_close = 0; - STp->ready = ST_NO_TAPE; - } - return retval; - out: mutex_unlock(&STp->lock); return retval; diff --git a/fs/nfs/delegation.c b/fs/nfs/delegation.c index fe47de4201..cc055c9e54 100644 --- a/fs/nfs/delegation.c +++ b/fs/nfs/delegation.c @@ -994,6 +994,11 @@ void nfs_delegation_mark_returned(struct inode *inode, } nfs_mark_delegation_revoked(delegation); + clear_bit(NFS_DELEGATION_RETURNING, &delegation->flags); + spin_unlock(&delegation->lock); + if (nfs_detach_delegation(NFS_I(inode), delegation, NFS_SERVER(inode))) + nfs_put_delegation(delegation); + goto out_rcu_unlock; out_clear_returning: clear_bit(NFS_DELEGATION_RETURNING, &delegation->flags); diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index ce111e7893..5f70480d1f 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -600,6 +600,8 @@ extern int E_md4hash(const unsigned char *passwd, unsigned char *p16, extern struct TCP_Server_Info * cifs_find_tcp_session(struct smb3_fs_context *ctx); +struct cifs_tcon *cifs_setup_ipc(struct cifs_ses *ses, bool seal); + void __cifs_put_smb_ses(struct cifs_ses *ses); extern struct cifs_ses * diff --git a/fs/smb/client/connect.c b/fs/smb/client/connect.c index 7cfd77376c..56933591fd 100644 --- a/fs/smb/client/connect.c +++ b/fs/smb/client/connect.c @@ -2017,39 +2017,31 @@ static int match_session(struct cifs_ses *ses, /** * cifs_setup_ipc - helper to setup the IPC tcon for the session * @ses: smb session to issue the request on - * @ctx: the superblock configuration context to use for building the - * new tree connection for the IPC (interprocess communication RPC) + * @seal: if encryption is requested * * A new IPC connection is made and stored in the session * tcon_ipc. The IPC tcon has the same lifetime as the session. */ -static int -cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx) +struct cifs_tcon *cifs_setup_ipc(struct cifs_ses *ses, bool seal) { int rc = 0, xid; struct cifs_tcon *tcon; char unc[SERVER_NAME_LENGTH + sizeof("//x/IPC$")] = {0}; - bool seal = false; struct TCP_Server_Info *server = ses->server; /* * If the mount request that resulted in the creation of the * session requires encryption, force IPC to be encrypted too. */ - if (ctx->seal) { - if (server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION) - seal = true; - else { - cifs_server_dbg(VFS, - "IPC: server doesn't support encryption\n"); - return -EOPNOTSUPP; - } + if (seal && !(server->capabilities & SMB2_GLOBAL_CAP_ENCRYPTION)) { + cifs_server_dbg(VFS, "IPC: server doesn't support encryption\n"); + return ERR_PTR(-EOPNOTSUPP); } /* no need to setup directory caching on IPC share, so pass in false */ tcon = tcon_info_alloc(false, netfs_trace_tcon_ref_new_ipc); if (tcon == NULL) - return -ENOMEM; + return ERR_PTR(-ENOMEM); spin_lock(&server->srv_lock); scnprintf(unc, sizeof(unc), "\\\\%s\\IPC$", server->hostname); @@ -2059,13 +2051,13 @@ cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx) tcon->ses = ses; tcon->ipc = true; tcon->seal = seal; - rc = server->ops->tree_connect(xid, ses, unc, tcon, ctx->local_nls); + rc = server->ops->tree_connect(xid, ses, unc, tcon, ses->local_nls); free_xid(xid); if (rc) { - cifs_server_dbg(VFS, "failed to connect to IPC (rc=%d)\n", rc); + cifs_server_dbg(VFS | ONCE, "failed to connect to IPC (rc=%d)\n", rc); tconInfoFree(tcon, netfs_trace_tcon_ref_free_ipc_fail); - goto out; + return ERR_PTR(rc); } cifs_dbg(FYI, "IPC tcon rc=%d ipc tid=0x%x\n", rc, tcon->tid); @@ -2073,9 +2065,7 @@ cifs_setup_ipc(struct cifs_ses *ses, struct smb3_fs_context *ctx) spin_lock(&tcon->tc_lock); tcon->status = TID_GOOD; spin_unlock(&tcon->tc_lock); - ses->tcon_ipc = tcon; -out: - return rc; + return tcon; } static struct cifs_ses * @@ -2349,6 +2339,7 @@ cifs_get_smb_ses(struct TCP_Server_Info *server, struct smb3_fs_context *ctx) { struct sockaddr_in6 *addr6 = (struct sockaddr_in6 *)&server->dstaddr; struct sockaddr_in *addr = (struct sockaddr_in *)&server->dstaddr; + struct cifs_tcon *ipc; struct cifs_ses *ses; unsigned int xid; int retries = 0; @@ -2527,7 +2518,12 @@ retry_new_session: list_add(&ses->smb_ses_list, &server->smb_ses_list); spin_unlock(&cifs_tcp_ses_lock); - cifs_setup_ipc(ses, ctx); + ipc = cifs_setup_ipc(ses, ctx->seal); + spin_lock(&cifs_tcp_ses_lock); + spin_lock(&ses->ses_lock); + ses->tcon_ipc = !IS_ERR(ipc) ? ipc : NULL; + spin_unlock(&ses->ses_lock); + spin_unlock(&cifs_tcp_ses_lock); free_xid(xid); diff --git a/fs/smb/client/dfs_cache.c b/fs/smb/client/dfs_cache.c index 4dada26d56..f2ad0ccd08 100644 --- a/fs/smb/client/dfs_cache.c +++ b/fs/smb/client/dfs_cache.c @@ -1120,24 +1120,63 @@ static bool target_share_equal(struct cifs_tcon *tcon, const char *s1) return match; } -static bool is_ses_good(struct cifs_ses *ses) +static bool is_ses_good(struct cifs_tcon *tcon, struct cifs_ses *ses) { struct TCP_Server_Info *server = ses->server; - struct cifs_tcon *tcon = ses->tcon_ipc; + struct cifs_tcon *ipc = NULL; bool ret; + spin_lock(&cifs_tcp_ses_lock); spin_lock(&ses->ses_lock); spin_lock(&ses->chan_lock); + ret = !cifs_chan_needs_reconnect(ses, server) && - ses->ses_status == SES_GOOD && - !tcon->need_reconnect; + ses->ses_status == SES_GOOD; + spin_unlock(&ses->chan_lock); + + if (!ret) + goto out; + + if (likely(ses->tcon_ipc)) { + if (ses->tcon_ipc->need_reconnect) { + ret = false; + goto out; + } + } else { + spin_unlock(&ses->ses_lock); + spin_unlock(&cifs_tcp_ses_lock); + + ipc = cifs_setup_ipc(ses, tcon->seal); + + spin_lock(&cifs_tcp_ses_lock); + spin_lock(&ses->ses_lock); + if (!IS_ERR(ipc)) { + if (!ses->tcon_ipc) { + ses->tcon_ipc = ipc; + ipc = NULL; + } + } else { + ret = false; + ipc = NULL; + } + } + +out: spin_unlock(&ses->ses_lock); + spin_unlock(&cifs_tcp_ses_lock); + if (ipc && server->ops->tree_disconnect) { + unsigned int xid = get_xid(); + + (void)server->ops->tree_disconnect(xid, ipc); + _free_xid(xid); + } + tconInfoFree(ipc, netfs_trace_tcon_ref_free_ipc); return ret; } /* Refresh dfs referral of @ses */ -static void refresh_ses_referral(struct cifs_ses *ses) +static void refresh_ses_referral(struct cifs_tcon *tcon, struct cifs_ses *ses) { struct cache_entry *ce; unsigned int xid; @@ -1153,7 +1192,7 @@ static void refresh_ses_referral(struct cifs_ses *ses) } ses = CIFS_DFS_ROOT_SES(ses); - if (!is_ses_good(ses)) { + if (!is_ses_good(tcon, ses)) { cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n", __func__); goto out; @@ -1241,7 +1280,7 @@ static void refresh_tcon_referral(struct cifs_tcon *tcon, bool force_refresh) up_read(&htable_rw_lock); ses = CIFS_DFS_ROOT_SES(ses); - if (!is_ses_good(ses)) { + if (!is_ses_good(tcon, ses)) { cifs_dbg(FYI, "%s: skip cache refresh due to disconnected ipc\n", __func__); goto out; @@ -1309,7 +1348,7 @@ void dfs_cache_refresh(struct work_struct *work) tcon = container_of(work, struct cifs_tcon, dfs_cache_work.work); list_for_each_entry(ses, &tcon->dfs_ses_list, dlist) - refresh_ses_referral(ses); + refresh_ses_referral(tcon, ses); refresh_tcon_referral(tcon, false); queue_delayed_work(dfscache_wq, &tcon->dfs_cache_work, diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 8780d30ffd..1088e3c244 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -2462,11 +2462,8 @@ cifs_do_rename(const unsigned int xid, struct dentry *from_dentry, } #endif /* CONFIG_CIFS_ALLOW_INSECURE_LEGACY */ do_rename_exit: - if (rc == 0) { + if (rc == 0) d_move(from_dentry, to_dentry); - /* Force a new lookup */ - d_drop(from_dentry); - } cifs_put_tlink(tlink); return rc; } diff --git a/include/uapi/linux/landlock.h b/include/uapi/linux/landlock.h index 70edd17baf..c223572f82 100644 --- a/include/uapi/linux/landlock.h +++ b/include/uapi/linux/landlock.h @@ -57,9 +57,11 @@ struct landlock_ruleset_attr { * * - %LANDLOCK_CREATE_RULESET_VERSION: Get the highest supported Landlock ABI * version. + * - %LANDLOCK_CREATE_RULESET_ERRATA: Get a bitmask of fixed issues. */ /* clang-format off */ #define LANDLOCK_CREATE_RULESET_VERSION (1U << 0) +#define LANDLOCK_CREATE_RULESET_ERRATA (1U << 1) /* clang-format on */ /** @@ -296,9 +298,12 @@ struct landlock_net_port_attr { * - %LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET: Restrict a sandboxed process from * connecting to an abstract UNIX socket created by a process outside the * related Landlock domain (e.g. a parent domain or a non-sandboxed process). + * - %LANDLOCK_SCOPE_SIGNAL: Restrict a sandboxed process from sending a signal + * to another process outside the domain. */ /* clang-format off */ #define LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET (1ULL << 0) +#define LANDLOCK_SCOPE_SIGNAL (1ULL << 1) /* clang-format on*/ #endif /* _UAPI_LINUX_LANDLOCK_H */ diff --git a/kernel/auditfilter.c b/kernel/auditfilter.c index eb1bbd9090..fd396df9c3 100644 --- a/kernel/auditfilter.c +++ b/kernel/auditfilter.c @@ -1325,7 +1325,7 @@ int audit_compare_dname_path(const struct qstr *dname, const char *path, int par /* handle trailing slashes */ pathlen -= parentlen; - while (p[pathlen - 1] == '/') + while (pathlen > 0 && p[pathlen - 1] == '/') pathlen--; if (pathlen != dlen) diff --git a/mm/memory-failure.c b/mm/memory-failure.c index 49518cb6da..7a5dd8e043 100644 --- a/mm/memory-failure.c +++ b/mm/memory-failure.c @@ -2541,10 +2541,9 @@ int unpoison_memory(unsigned long pfn) static DEFINE_RATELIMIT_STATE(unpoison_rs, DEFAULT_RATELIMIT_INTERVAL, DEFAULT_RATELIMIT_BURST); - if (!pfn_valid(pfn)) - return -ENXIO; - - p = pfn_to_page(pfn); + p = pfn_to_online_page(pfn); + if (!p) + return -EIO; folio = page_folio(p); mutex_lock(&mf_mutex); diff --git a/net/sctp/inqueue.c b/net/sctp/inqueue.c index 5c16521818..f5a7d5a387 100644 --- a/net/sctp/inqueue.c +++ b/net/sctp/inqueue.c @@ -169,13 +169,14 @@ next_chunk: chunk->head_skb = chunk->skb; /* skbs with "cover letter" */ - if (chunk->head_skb && chunk->skb->data_len == chunk->skb->len) + if (chunk->head_skb && chunk->skb->data_len == chunk->skb->len) { + if (WARN_ON(!skb_shinfo(chunk->skb)->frag_list)) { + __SCTP_INC_STATS(dev_net(chunk->skb->dev), + SCTP_MIB_IN_PKT_DISCARDS); + sctp_chunk_free(chunk); + goto next_chunk; + } chunk->skb = skb_shinfo(chunk->skb)->frag_list; - - if (WARN_ON(!chunk->skb)) { - __SCTP_INC_STATS(dev_net(chunk->skb->dev), SCTP_MIB_IN_PKT_DISCARDS); - sctp_chunk_free(chunk); - goto next_chunk; } } diff --git a/redhat/Makefile b/redhat/Makefile index 415a96c660..8f95a1b351 100644 --- a/redhat/Makefile +++ b/redhat/Makefile @@ -93,6 +93,13 @@ ifndef RHJOBS fi) endif +# for official builds use RELEASE_LOCALVERSION persisted in Makefile.variables +ifneq ($(filter $(MAKECMDGOALS),dist-release dist-release-tag dist-release-git dist-rtg dist-get-tag),) + DISTLOCALVERSION:=$(RELEASE_LOCALVERSION) + ifeq (,$(findstring s,$(firstword -$(MAKEFLAGS)))) + $(info DISTLOCALVERSION is "$(DISTLOCALVERSION)".) + endif +else LOCVERFILE:=../localversion # create an empty localversion file if you don't want a local buildid ifneq ($(wildcard $(LOCVERFILE)),) @@ -110,6 +117,7 @@ else endif $(info DISTLOCALVERSION is "$(DISTLOCALVERSION)".) endif +endif # MAKECMDGOALS # options for process_configs.sh script ifdef NO_CONFIGCHECKS diff --git a/redhat/Makefile.variables b/redhat/Makefile.variables index a86186d005..29bb6152f6 100644 --- a/redhat/Makefile.variables +++ b/redhat/Makefile.variables @@ -114,6 +114,10 @@ NO_CONFIGCHECKS ?= # considered stable and may be changed or removed without warning. RHSELFTESTDATA ?= +# Local version to be used for official (non-scratch) builds. Makefile +# dist-{release/tag/git} targets will ignore localversion and DISTLOCALVERSION +RELEASE_LOCALVERSION:= + # This variable is used by the redhat/self-tests. It should not be # considered stable and my be changed or removed without warning. RHDISTDATADIR ?= diff --git a/redhat/kernel.changelog-9.7 b/redhat/kernel.changelog-9.7 index 0014662b90..16a88c9b4b 100644 --- a/redhat/kernel.changelog-9.7 +++ b/redhat/kernel.changelog-9.7 @@ -1,3 +1,34 @@ +* Sat Dec 20 2025 CKI KWF Bot [5.14.0-611.20.1.el9_7] +- HID: multitouch: fix slab out-of-bounds access in mt_report_fixup() (CKI Backport Bot) [RHEL-124607] {CVE-2025-39806} +- sctp: avoid NULL dereference when chunk data buffer is missing (CKI Backport Bot) [RHEL-134001] {CVE-2025-40240} +- selftests/landlock: Add a new test for setuid() (Štěpán Horáček) [RHEL-132712] +- selftests/landlock: Split signal_scoping_threads tests (Štěpán Horáček) [RHEL-132712] +- landlock: Always allow signals between threads of the same process (Štěpán Horáček) [RHEL-132712] +- landlock: Prepare to add second errata (Štěpán Horáček) [RHEL-132712] +- landlock: Add the errata interface (Štěpán Horáček) [RHEL-132712] +- selftests/landlock: Test signal scoping for threads (Štěpán Horáček) [RHEL-132712] +- selftests/landlock: Test signal scoping (Štěpán Horáček) [RHEL-132712] +- landlock: Add signal scoping (Štěpán Horáček) [RHEL-132712] +Resolves: RHEL-124607, RHEL-132712, RHEL-134001 + +* Thu Dec 18 2025 CKI KWF Bot [5.14.0-611.19.1.el9_7] +- scsi: st: Skip buffer flush for information ioctls (John Meneghini) [RHEL-133543] +- scsi: st: Separate st-unique ioctl handling from SCSI common ioctl handling (John Meneghini) [RHEL-133543] +- audit: fix out-of-bounds read in audit_compare_dname_path() (Richard Guy Briggs) [RHEL-119176] {CVE-2025-39840} +Resolves: RHEL-119176, RHEL-133543 + +* Sat Dec 13 2025 CKI KWF Bot [5.14.0-611.18.1.el9_7] +- NFS: remove revoked delegation from server's delegation list (Benjamin Coddington) [RHEL-134237] +- redhat: use RELEASE_LOCALVERSION also for dist-get-tag (Jan Stancek) +- redhat: introduce RELEASE_LOCALVERSION variable (Jan Stancek) +Resolves: RHEL-134237 + +* Thu Dec 11 2025 CKI KWF Bot [5.14.0-611.17.1.el9_7] +- smb: client: handle lack of IPC in dfs_cache_refresh() (Paulo Alcantara) [RHEL-126165] +- smb: client: get rid of d_drop() in cifs_do_rename() (Paulo Alcantara) [RHEL-124917] +- mm/memory-failure: fix VM_BUG_ON_PAGE(PagePoisoned(page)) when unpoison memory (CKI Backport Bot) [RHEL-119150] {CVE-2025-39883} +Resolves: RHEL-119150, RHEL-124917, RHEL-126165 + * Sun Dec 07 2025 CKI KWF Bot [5.14.0-611.16.1.el9_7] - CVE-2025-38499 kernel: clone_private_mnt(): make sure that caller has CAP_SYS_ADMIN in the right userns (Abhi Das) [RHEL-129261] {CVE-2025-38499} - tls: wait for pending async decryptions if tls_strp_msg_hold fails (CKI Backport Bot) [RHEL-128860] {CVE-2025-40176} diff --git a/redhat/self-test/data/centos-6161a435c191.el7 b/redhat/self-test/data/centos-6161a435c191.el7 index 080aa062df..34bd311982 100644 --- a/redhat/self-test/data/centos-6161a435c191.el7 +++ b/redhat/self-test/data/centos-6161a435c191.el7 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-0.rc4.6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=c9s RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/centos-6161a435c191.fc25 b/redhat/self-test/data/centos-6161a435c191.fc25 index 9f10f7f5d0..1d4017ce55 100644 --- a/redhat/self-test/data/centos-6161a435c191.fc25 +++ b/redhat/self-test/data/centos-6161a435c191.fc25 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-0.rc4.6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=c9s RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/centos-9f4ad9e425a1.el7 b/redhat/self-test/data/centos-9f4ad9e425a1.el7 index cd2f0004e0..e75ce65f45 100644 --- a/redhat/self-test/data/centos-9f4ad9e425a1.el7 +++ b/redhat/self-test/data/centos-9f4ad9e425a1.el7 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=c9s RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/centos-9f4ad9e425a1.fc25 b/redhat/self-test/data/centos-9f4ad9e425a1.fc25 index e5ab1dc157..5b3abd462f 100644 --- a/redhat/self-test/data/centos-9f4ad9e425a1.fc25 +++ b/redhat/self-test/data/centos-9f4ad9e425a1.fc25 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=c9s RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/centos-a5e13c6df0e4.el7 b/redhat/self-test/data/centos-a5e13c6df0e4.el7 index 9acbe25a51..3412736efc 100644 --- a/redhat/self-test/data/centos-a5e13c6df0e4.el7 +++ b/redhat/self-test/data/centos-a5e13c6df0e4.el7 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-0.rc5.6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=c9s RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/centos-a5e13c6df0e4.fc25 b/redhat/self-test/data/centos-a5e13c6df0e4.fc25 index 0dad71c43a..4aad2a7bf2 100644 --- a/redhat/self-test/data/centos-a5e13c6df0e4.fc25 +++ b/redhat/self-test/data/centos-a5e13c6df0e4.fc25 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-0.rc5.6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=c9s RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/centos-edc9dd1e3c31.el7 b/redhat/self-test/data/centos-edc9dd1e3c31.el7 index c441cc9a8a..823b2973ba 100644 --- a/redhat/self-test/data/centos-edc9dd1e3c31.el7 +++ b/redhat/self-test/data/centos-edc9dd1e3c31.el7 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=c9s RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/centos-edc9dd1e3c31.fc25 b/redhat/self-test/data/centos-edc9dd1e3c31.fc25 index fb5c2ad2e6..54034e81a7 100644 --- a/redhat/self-test/data/centos-edc9dd1e3c31.fc25 +++ b/redhat/self-test/data/centos-edc9dd1e3c31.fc25 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=c9s RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/fedora-6161a435c191.el7 b/redhat/self-test/data/fedora-6161a435c191.el7 index 40a6a201b8..82e50cda27 100644 --- a/redhat/self-test/data/fedora-6161a435c191.el7 +++ b/redhat/self-test/data/fedora-6161a435c191.el7 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-0.rc4.6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rawhide RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/fedora-6161a435c191.fc25 b/redhat/self-test/data/fedora-6161a435c191.fc25 index 740ea30d21..ff46861a86 100644 --- a/redhat/self-test/data/fedora-6161a435c191.fc25 +++ b/redhat/self-test/data/fedora-6161a435c191.fc25 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-0.rc4.6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rawhide RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/fedora-9f4ad9e425a1.el7 b/redhat/self-test/data/fedora-9f4ad9e425a1.el7 index c084d162c4..3ac2ca5a44 100644 --- a/redhat/self-test/data/fedora-9f4ad9e425a1.el7 +++ b/redhat/self-test/data/fedora-9f4ad9e425a1.el7 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rawhide RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/fedora-9f4ad9e425a1.fc25 b/redhat/self-test/data/fedora-9f4ad9e425a1.fc25 index eec2a62dbf..f7a38e4745 100644 --- a/redhat/self-test/data/fedora-9f4ad9e425a1.fc25 +++ b/redhat/self-test/data/fedora-9f4ad9e425a1.fc25 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rawhide RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/fedora-a5e13c6df0e4.el7 b/redhat/self-test/data/fedora-a5e13c6df0e4.el7 index a843dca222..573866d77c 100644 --- a/redhat/self-test/data/fedora-a5e13c6df0e4.el7 +++ b/redhat/self-test/data/fedora-a5e13c6df0e4.el7 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-0.rc5.6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rawhide RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/fedora-a5e13c6df0e4.fc25 b/redhat/self-test/data/fedora-a5e13c6df0e4.fc25 index 944db1f7fe..da35404bd7 100644 --- a/redhat/self-test/data/fedora-a5e13c6df0e4.fc25 +++ b/redhat/self-test/data/fedora-a5e13c6df0e4.fc25 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-0.rc5.6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rawhide RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/fedora-edc9dd1e3c31.el7 b/redhat/self-test/data/fedora-edc9dd1e3c31.el7 index 0c722f57a6..b7fe19d703 100644 --- a/redhat/self-test/data/fedora-edc9dd1e3c31.el7 +++ b/redhat/self-test/data/fedora-edc9dd1e3c31.el7 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rawhide RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/fedora-edc9dd1e3c31.fc25 b/redhat/self-test/data/fedora-edc9dd1e3c31.fc25 index 18434ab243..a91df9ee0d 100644 --- a/redhat/self-test/data/fedora-edc9dd1e3c31.fc25 +++ b/redhat/self-test/data/fedora-edc9dd1e3c31.fc25 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rawhide RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/rhel-6161a435c191.el7 b/redhat/self-test/data/rhel-6161a435c191.el7 index e04c12fdfe..c7dc9c4882 100644 --- a/redhat/self-test/data/rhel-6161a435c191.el7 +++ b/redhat/self-test/data/rhel-6161a435c191.el7 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-0.rc4.6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rhel-9.7.0 RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/rhel-6161a435c191.fc25 b/redhat/self-test/data/rhel-6161a435c191.fc25 index 41861edb2f..3bf9706a92 100644 --- a/redhat/self-test/data/rhel-6161a435c191.fc25 +++ b/redhat/self-test/data/rhel-6161a435c191.fc25 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-0.rc4.6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rhel-9.7.0 RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/rhel-9f4ad9e425a1.el7 b/redhat/self-test/data/rhel-9f4ad9e425a1.el7 index ac8dd2f6d2..92a62d4639 100644 --- a/redhat/self-test/data/rhel-9f4ad9e425a1.el7 +++ b/redhat/self-test/data/rhel-9f4ad9e425a1.el7 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rhel-9.7.0 RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/rhel-9f4ad9e425a1.fc25 b/redhat/self-test/data/rhel-9f4ad9e425a1.fc25 index 125303552b..6f286e410e 100644 --- a/redhat/self-test/data/rhel-9f4ad9e425a1.fc25 +++ b/redhat/self-test/data/rhel-9f4ad9e425a1.fc25 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rhel-9.7.0 RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/rhel-a5e13c6df0e4.el7 b/redhat/self-test/data/rhel-a5e13c6df0e4.el7 index 1ba258d283..68316fbad8 100644 --- a/redhat/self-test/data/rhel-a5e13c6df0e4.el7 +++ b/redhat/self-test/data/rhel-a5e13c6df0e4.el7 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-0.rc5.6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rhel-9.7.0 RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/rhel-a5e13c6df0e4.fc25 b/redhat/self-test/data/rhel-a5e13c6df0e4.fc25 index eaef54cce6..2d4166436e 100644 --- a/redhat/self-test/data/rhel-a5e13c6df0e4.fc25 +++ b/redhat/self-test/data/rhel-a5e13c6df0e4.fc25 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-0.rc5.6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rhel-9.7.0 RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/rhel-edc9dd1e3c31.el7 b/redhat/self-test/data/rhel-edc9dd1e3c31.el7 index 3d6238cebf..ed58447823 100644 --- a/redhat/self-test/data/rhel-edc9dd1e3c31.el7 +++ b/redhat/self-test/data/rhel-edc9dd1e3c31.el7 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rhel-9.7.0 RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/redhat/self-test/data/rhel-edc9dd1e3c31.fc25 b/redhat/self-test/data/rhel-edc9dd1e3c31.fc25 index 2596d8942f..eedaa12aaf 100644 --- a/redhat/self-test/data/rhel-edc9dd1e3c31.fc25 +++ b/redhat/self-test/data/rhel-edc9dd1e3c31.fc25 @@ -46,6 +46,7 @@ PROCESS_CONFIGS_CHECK_OPTS=-n -t -c PROCESS_CONFIGS_OPTS=-n -w -c REDHAT=../redhat RELEASETAG=kernel-5.12.0-6.test +RELEASE_LOCALVERSION= RHDISTGIT_BRANCH=rhel-9.7.0 RHDISTGIT_USER="shadowman" RHEL_MAJOR=9 diff --git a/security/landlock/cred.h b/security/landlock/cred.h index af89ab00e6..bf75545983 100644 --- a/security/landlock/cred.h +++ b/security/landlock/cred.h @@ -26,7 +26,7 @@ landlock_cred(const struct cred *cred) return cred->security + landlock_blob_sizes.lbs_cred; } -static inline const struct landlock_ruleset *landlock_get_current_domain(void) +static inline struct landlock_ruleset *landlock_get_current_domain(void) { return landlock_cred(current_cred())->domain; } diff --git a/security/landlock/errata.h b/security/landlock/errata.h new file mode 100644 index 0000000000..8e626accac --- /dev/null +++ b/security/landlock/errata.h @@ -0,0 +1,99 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ +/* + * Landlock - Errata information + * + * Copyright © 2025 Microsoft Corporation + */ + +#ifndef _SECURITY_LANDLOCK_ERRATA_H +#define _SECURITY_LANDLOCK_ERRATA_H + +#include + +struct landlock_erratum { + const int abi; + const u8 number; +}; + +/* clang-format off */ +#define LANDLOCK_ERRATUM(NUMBER) \ + { \ + .abi = LANDLOCK_ERRATA_ABI, \ + .number = NUMBER, \ + }, +/* clang-format on */ + +/* + * Some fixes may require user space to check if they are applied on the running + * kernel before using a specific feature. For instance, this applies when a + * restriction was previously too restrictive and is now getting relaxed (for + * compatibility or semantic reasons). However, non-visible changes for + * legitimate use (e.g. security fixes) do not require an erratum. + */ +static const struct landlock_erratum landlock_errata_init[] __initconst = { + +/* + * Only Sparse may not implement __has_include. If a compiler does not + * implement __has_include, a warning will be printed at boot time (see + * setup.c). + */ +#ifdef __has_include + +#define LANDLOCK_ERRATA_ABI 1 +#if __has_include("errata/abi-1.h") +#include "errata/abi-1.h" +#endif +#undef LANDLOCK_ERRATA_ABI + +#define LANDLOCK_ERRATA_ABI 2 +#if __has_include("errata/abi-2.h") +#include "errata/abi-2.h" +#endif +#undef LANDLOCK_ERRATA_ABI + +#define LANDLOCK_ERRATA_ABI 3 +#if __has_include("errata/abi-3.h") +#include "errata/abi-3.h" +#endif +#undef LANDLOCK_ERRATA_ABI + +#define LANDLOCK_ERRATA_ABI 4 +#if __has_include("errata/abi-4.h") +#include "errata/abi-4.h" +#endif +#undef LANDLOCK_ERRATA_ABI + +#define LANDLOCK_ERRATA_ABI 5 +#if __has_include("errata/abi-5.h") +#include "errata/abi-5.h" +#endif +#undef LANDLOCK_ERRATA_ABI + +#define LANDLOCK_ERRATA_ABI 6 +#if __has_include("errata/abi-6.h") +#include "errata/abi-6.h" +#endif +#undef LANDLOCK_ERRATA_ABI + +/* + * For each new erratum, we need to include all the ABI files up to the impacted + * ABI to make all potential future intermediate errata easy to backport. + * + * If such change involves more than one ABI addition, then it must be in a + * dedicated commit with the same Fixes tag as used for the actual fix. + * + * Each commit creating a new security/landlock/errata/abi-*.h file must have a + * Depends-on tag to reference the commit that previously added the line to + * include this new file, except if the original Fixes tag is enough. + * + * Each erratum must be documented in its related ABI file, and a dedicated + * commit must update Documentation/userspace-api/landlock.rst to include this + * erratum. This commit will not be backported. + */ + +#endif + + {} +}; + +#endif /* _SECURITY_LANDLOCK_ERRATA_H */ diff --git a/security/landlock/errata/abi-6.h b/security/landlock/errata/abi-6.h new file mode 100644 index 0000000000..df7bc0e1fd --- /dev/null +++ b/security/landlock/errata/abi-6.h @@ -0,0 +1,19 @@ +/* SPDX-License-Identifier: GPL-2.0-only */ + +/** + * DOC: erratum_2 + * + * Erratum 2: Scoped signal handling + * ~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~ + * + * This fix addresses an issue where signal scoping was overly restrictive, + * preventing sandboxed threads from signaling other threads within the same + * process if they belonged to different domains. Because threads are not + * security boundaries, user space might assume that any thread within the same + * process can send signals between themselves (see :manpage:`nptl(7)` and + * :manpage:`libpsx(3)`). Consistent with :manpage:`ptrace(2)` behavior, direct + * interaction between threads of the same process should always be allowed. + * This change ensures that any thread is allowed to send signals to any other + * thread within the same process, regardless of their domain. + */ +LANDLOCK_ERRATUM(2) diff --git a/security/landlock/fs.c b/security/landlock/fs.c index 7877a64cc6..fd282c656f 100644 --- a/security/landlock/fs.c +++ b/security/landlock/fs.c @@ -27,7 +27,9 @@ #include #include #include +#include #include +#include #include #include #include @@ -1636,6 +1638,54 @@ static int hook_file_ioctl_compat(struct file *file, unsigned int cmd, return -EACCES; } +/* + * Always allow sending signals between threads of the same process. This + * ensures consistency with hook_task_kill(). + */ +static bool control_current_fowner(struct fown_struct *const fown) +{ + struct task_struct *p; + + /* + * Lock already held by __f_setown(), see commit 26f204380a3c ("fs: Fix + * file_set_fowner LSM hook inconsistencies"). + */ + lockdep_assert_held(&fown->lock); + + /* + * Some callers (e.g. fcntl_dirnotify) may not be in an RCU read-side + * critical section. + */ + guard(rcu)(); + p = pid_task(fown->pid, fown->pid_type); + if (!p) + return true; + + return !same_thread_group(p, current); +} + +static void hook_file_set_fowner(struct file *file) +{ + struct landlock_ruleset *prev_dom; + struct landlock_ruleset *new_dom = NULL; + + if (control_current_fowner(&file->f_owner)) { + new_dom = landlock_get_current_domain(); + landlock_get_ruleset(new_dom); + } + + prev_dom = landlock_file(file)->fown_domain; + landlock_file(file)->fown_domain = new_dom; + + /* May be called in an RCU read-side critical section. */ + landlock_put_ruleset_deferred(prev_dom); +} + +static void hook_file_free_security(struct file *file) +{ + landlock_put_ruleset_deferred(landlock_file(file)->fown_domain); +} + static struct security_hook_list landlock_hooks[] __ro_after_init = { LSM_HOOK_INIT(inode_free_security, hook_inode_free_security), @@ -1660,6 +1710,8 @@ static struct security_hook_list landlock_hooks[] __ro_after_init = { LSM_HOOK_INIT(file_truncate, hook_file_truncate), LSM_HOOK_INIT(file_ioctl, hook_file_ioctl), LSM_HOOK_INIT(file_ioctl_compat, hook_file_ioctl_compat), + LSM_HOOK_INIT(file_set_fowner, hook_file_set_fowner), + LSM_HOOK_INIT(file_free_security, hook_file_free_security), }; __init void landlock_add_fs_hooks(void) diff --git a/security/landlock/fs.h b/security/landlock/fs.h index 488e481368..1487e1f023 100644 --- a/security/landlock/fs.h +++ b/security/landlock/fs.h @@ -52,6 +52,13 @@ struct landlock_file_security { * needed to authorize later operations on the open file. */ access_mask_t allowed_access; + /** + * @fown_domain: Domain of the task that set the PID that may receive a + * signal e.g., SIGURG when writing MSG_OOB to the related socket. + * This pointer is protected by the related file->f_owner->lock, as for + * fown_struct's members: pid, uid, and euid. + */ + struct landlock_ruleset *fown_domain; }; /** diff --git a/security/landlock/limits.h b/security/landlock/limits.h index d74818003e..15f7606066 100644 --- a/security/landlock/limits.h +++ b/security/landlock/limits.h @@ -26,7 +26,7 @@ #define LANDLOCK_MASK_ACCESS_NET ((LANDLOCK_LAST_ACCESS_NET << 1) - 1) #define LANDLOCK_NUM_ACCESS_NET __const_hweight64(LANDLOCK_MASK_ACCESS_NET) -#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET +#define LANDLOCK_LAST_SCOPE LANDLOCK_SCOPE_SIGNAL #define LANDLOCK_MASK_SCOPE ((LANDLOCK_LAST_SCOPE << 1) - 1) #define LANDLOCK_NUM_SCOPE __const_hweight64(LANDLOCK_MASK_SCOPE) /* clang-format on */ diff --git a/security/landlock/setup.c b/security/landlock/setup.c index 28519a45b1..37b262081e 100644 --- a/security/landlock/setup.c +++ b/security/landlock/setup.c @@ -6,12 +6,14 @@ * Copyright © 2018-2020 ANSSI */ +#include #include #include #include #include "common.h" #include "cred.h" +#include "errata.h" #include "fs.h" #include "net.h" #include "setup.h" @@ -31,8 +33,36 @@ const struct lsm_id landlock_lsmid = { .id = LSM_ID_LANDLOCK, }; +int landlock_errata __ro_after_init; + +static void __init compute_errata(void) +{ + size_t i; + +#ifndef __has_include + /* + * This is a safeguard to make sure the compiler implements + * __has_include (see errata.h). + */ + WARN_ON_ONCE(1); + return; +#endif + + for (i = 0; landlock_errata_init[i].number; i++) { + const int prev_errata = landlock_errata; + + if (WARN_ON_ONCE(landlock_errata_init[i].abi > + landlock_abi_version)) + continue; + + landlock_errata |= BIT(landlock_errata_init[i].number - 1); + WARN_ON_ONCE(prev_errata == landlock_errata); + } +} + static int __init landlock_init(void) { + compute_errata(); landlock_add_cred_hooks(); landlock_add_task_hooks(); landlock_add_fs_hooks(); diff --git a/security/landlock/setup.h b/security/landlock/setup.h index c4252d46d4..fca307c35f 100644 --- a/security/landlock/setup.h +++ b/security/landlock/setup.h @@ -11,7 +11,10 @@ #include +extern const int landlock_abi_version; + extern bool landlock_initialized; +extern int landlock_errata; extern struct lsm_blob_sizes landlock_blob_sizes; extern const struct lsm_id landlock_lsmid; diff --git a/security/landlock/syscalls.c b/security/landlock/syscalls.c index c67836841e..584bfd0c9f 100644 --- a/security/landlock/syscalls.c +++ b/security/landlock/syscalls.c @@ -159,7 +159,9 @@ static const struct file_operations ruleset_fops = { * the new ruleset. * @size: Size of the pointed &struct landlock_ruleset_attr (needed for * backward and forward compatibility). - * @flags: Supported value: %LANDLOCK_CREATE_RULESET_VERSION. + * @flags: Supported value: + * - %LANDLOCK_CREATE_RULESET_VERSION + * - %LANDLOCK_CREATE_RULESET_ERRATA * * This system call enables to create a new Landlock ruleset, and returns the * related file descriptor on success. @@ -168,6 +170,10 @@ static const struct file_operations ruleset_fops = { * 0, then the returned value is the highest supported Landlock ABI version * (starting at 1). * + * If @flags is %LANDLOCK_CREATE_RULESET_ERRATA and @attr is NULL and @size is + * 0, then the returned value is a bitmask of fixed issues for the current + * Landlock ABI version. + * * Possible returned errors are: * * - %EOPNOTSUPP: Landlock is supported by the kernel but disabled at boot time; @@ -191,9 +197,15 @@ SYSCALL_DEFINE3(landlock_create_ruleset, return -EOPNOTSUPP; if (flags) { - if ((flags == LANDLOCK_CREATE_RULESET_VERSION) && !attr && - !size) - return LANDLOCK_ABI_VERSION; + if (attr || size) + return -EINVAL; + + if (flags == LANDLOCK_CREATE_RULESET_VERSION) + return landlock_abi_version; + + if (flags == LANDLOCK_CREATE_RULESET_ERRATA) + return landlock_errata; + return -EINVAL; } @@ -234,6 +246,8 @@ SYSCALL_DEFINE3(landlock_create_ruleset, return ruleset_fd; } +const int landlock_abi_version = LANDLOCK_ABI_VERSION; + /* * Returns an owned ruleset from a FD. It is thus needed to call * landlock_put_ruleset() on the return value. diff --git a/security/landlock/task.c b/security/landlock/task.c index 4f8013ca41..5e9a86f848 100644 --- a/security/landlock/task.c +++ b/security/landlock/task.c @@ -13,11 +13,13 @@ #include #include #include +#include #include #include #include "common.h" #include "cred.h" +#include "fs.h" #include "ruleset.h" #include "setup.h" #include "task.h" @@ -242,12 +244,78 @@ static int hook_unix_may_send(struct socket *const sock, return 0; } +static int hook_task_kill(struct task_struct *const p, + struct kernel_siginfo *const info, const int sig, + const struct cred *const cred) +{ + bool is_scoped; + const struct landlock_ruleset *dom; + + if (cred) { + /* Dealing with USB IO. */ + dom = landlock_cred(cred)->domain; + } else { + /* + * Always allow sending signals between threads of the same process. + * This is required for process credential changes by the Native POSIX + * Threads Library and implemented by the set*id(2) wrappers and + * libcap(3) with tgkill(2). See nptl(7) and libpsx(3). + * + * This exception is similar to the __ptrace_may_access() one. + */ + if (same_thread_group(p, current)) + return 0; + + dom = landlock_get_current_domain(); + } + + /* Quick return for non-landlocked tasks. */ + if (!dom) + return 0; + + rcu_read_lock(); + is_scoped = domain_is_scoped(dom, landlock_get_task_domain(p), + LANDLOCK_SCOPE_SIGNAL); + rcu_read_unlock(); + if (is_scoped) + return -EPERM; + + return 0; +} + +static int hook_file_send_sigiotask(struct task_struct *tsk, + struct fown_struct *fown, int signum) +{ + const struct landlock_ruleset *dom; + bool is_scoped = false; + + /* Lock already held by send_sigio() and send_sigurg(). */ + lockdep_assert_held(&fown->lock); + dom = landlock_file(container_of(fown, struct file, f_owner))->fown_domain; + + /* Quick return for unowned socket. */ + if (!dom) + return 0; + + rcu_read_lock(); + is_scoped = domain_is_scoped(dom, landlock_get_task_domain(tsk), + LANDLOCK_SCOPE_SIGNAL); + rcu_read_unlock(); + if (is_scoped) + return -EPERM; + + return 0; +} + static struct security_hook_list landlock_hooks[] __ro_after_init = { LSM_HOOK_INIT(ptrace_access_check, hook_ptrace_access_check), LSM_HOOK_INIT(ptrace_traceme, hook_ptrace_traceme), LSM_HOOK_INIT(unix_stream_connect, hook_unix_stream_connect), LSM_HOOK_INIT(unix_may_send, hook_unix_may_send), + + LSM_HOOK_INIT(task_kill, hook_task_kill), + LSM_HOOK_INIT(file_send_sigiotask, hook_file_send_sigiotask), }; __init void landlock_add_task_hooks(void) diff --git a/tools/testing/selftests/landlock/base_test.c b/tools/testing/selftests/landlock/base_test.c index 1bc16fde2e..4766f8fec9 100644 --- a/tools/testing/selftests/landlock/base_test.c +++ b/tools/testing/selftests/landlock/base_test.c @@ -98,10 +98,54 @@ TEST(abi_version) ASSERT_EQ(EINVAL, errno); } +/* + * Old source trees might not have the set of Kselftest fixes related to kernel + * UAPI headers. + */ +#ifndef LANDLOCK_CREATE_RULESET_ERRATA +#define LANDLOCK_CREATE_RULESET_ERRATA (1U << 1) +#endif + +TEST(errata) +{ + const struct landlock_ruleset_attr ruleset_attr = { + .handled_access_fs = LANDLOCK_ACCESS_FS_READ_FILE, + }; + int errata; + + errata = landlock_create_ruleset(NULL, 0, + LANDLOCK_CREATE_RULESET_ERRATA); + /* The errata bitmask will not be backported to tests. */ + ASSERT_LE(0, errata); + TH_LOG("errata: 0x%x", errata); + + ASSERT_EQ(-1, landlock_create_ruleset(&ruleset_attr, 0, + LANDLOCK_CREATE_RULESET_ERRATA)); + ASSERT_EQ(EINVAL, errno); + + ASSERT_EQ(-1, landlock_create_ruleset(NULL, sizeof(ruleset_attr), + LANDLOCK_CREATE_RULESET_ERRATA)); + ASSERT_EQ(EINVAL, errno); + + ASSERT_EQ(-1, + landlock_create_ruleset(&ruleset_attr, sizeof(ruleset_attr), + LANDLOCK_CREATE_RULESET_ERRATA)); + ASSERT_EQ(EINVAL, errno); + + ASSERT_EQ(-1, landlock_create_ruleset( + NULL, 0, + LANDLOCK_CREATE_RULESET_VERSION | + LANDLOCK_CREATE_RULESET_ERRATA)); + ASSERT_EQ(-1, landlock_create_ruleset(NULL, 0, + LANDLOCK_CREATE_RULESET_ERRATA | + 1 << 31)); + ASSERT_EQ(EINVAL, errno); +} + /* Tests ordering of syscall argument checks. */ TEST(create_ruleset_checks_ordering) { - const int last_flag = LANDLOCK_CREATE_RULESET_VERSION; + const int last_flag = LANDLOCK_CREATE_RULESET_ERRATA; const int invalid_flag = last_flag << 1; int ruleset_fd; const struct landlock_ruleset_attr ruleset_attr = { diff --git a/tools/testing/selftests/landlock/common.h b/tools/testing/selftests/landlock/common.h index 61056fa074..19f4149eee 100644 --- a/tools/testing/selftests/landlock/common.h +++ b/tools/testing/selftests/landlock/common.h @@ -68,6 +68,7 @@ static void _init_caps(struct __test_metadata *const _metadata, bool drop_all) CAP_MKNOD, CAP_NET_ADMIN, CAP_NET_BIND_SERVICE, + CAP_SETUID, CAP_SYS_ADMIN, CAP_SYS_CHROOT, /* clang-format on */ diff --git a/tools/testing/selftests/landlock/scoped_signal_test.c b/tools/testing/selftests/landlock/scoped_signal_test.c new file mode 100644 index 0000000000..3454464b82 --- /dev/null +++ b/tools/testing/selftests/landlock/scoped_signal_test.c @@ -0,0 +1,378 @@ +// SPDX-License-Identifier: GPL-2.0 +/* + * Landlock tests - Signal Scoping + * + * Copyright © 2024 Tahera Fahimi + */ + +#define _GNU_SOURCE +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#include "common.h" +#include "scoped_common.h" + +/* This variable is used for handling several signals. */ +static volatile sig_atomic_t is_signaled; + +/* clang-format off */ +FIXTURE(scoping_signals) {}; +/* clang-format on */ + +FIXTURE_VARIANT(scoping_signals) +{ + int sig; +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoping_signals, sigtrap) { + /* clang-format on */ + .sig = SIGTRAP, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoping_signals, sigurg) { + /* clang-format on */ + .sig = SIGURG, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoping_signals, sighup) { + /* clang-format on */ + .sig = SIGHUP, +}; + +/* clang-format off */ +FIXTURE_VARIANT_ADD(scoping_signals, sigtstp) { + /* clang-format on */ + .sig = SIGTSTP, +}; + +FIXTURE_SETUP(scoping_signals) +{ + drop_caps(_metadata); + + is_signaled = 0; +} + +FIXTURE_TEARDOWN(scoping_signals) +{ +} + +static void scope_signal_handler(int sig, siginfo_t *info, void *ucontext) +{ + if (sig == SIGTRAP || sig == SIGURG || sig == SIGHUP || sig == SIGTSTP) + is_signaled = 1; +} + +/* + * In this test, a child process sends a signal to parent before and + * after getting scoped. + */ +TEST_F(scoping_signals, send_sig_to_parent) +{ + int pipe_parent[2]; + int status; + pid_t child; + pid_t parent = getpid(); + struct sigaction action = { + .sa_sigaction = scope_signal_handler, + .sa_flags = SA_SIGINFO, + + }; + + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + ASSERT_LE(0, sigaction(variant->sig, &action, NULL)); + + /* The process should not have already been signaled. */ + EXPECT_EQ(0, is_signaled); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + char buf_child; + int err; + + EXPECT_EQ(0, close(pipe_parent[1])); + + /* + * The child process can send signal to parent when + * domain is not scoped. + */ + err = kill(parent, variant->sig); + ASSERT_EQ(0, err); + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); + EXPECT_EQ(0, close(pipe_parent[0])); + + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); + + /* + * The child process cannot send signal to the parent + * anymore. + */ + err = kill(parent, variant->sig); + ASSERT_EQ(-1, err); + ASSERT_EQ(EPERM, errno); + + /* + * No matter of the domain, a process should be able to + * send a signal to itself. + */ + ASSERT_EQ(0, is_signaled); + ASSERT_EQ(0, raise(variant->sig)); + ASSERT_EQ(1, is_signaled); + + _exit(_metadata->exit_code); + return; + } + EXPECT_EQ(0, close(pipe_parent[0])); + + /* Waits for a first signal to be received, without race condition. */ + while (!is_signaled && !usleep(1)) + ; + ASSERT_EQ(1, is_signaled); + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + EXPECT_EQ(0, close(pipe_parent[1])); + is_signaled = 0; + + ASSERT_EQ(child, waitpid(child, &status, 0)); + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) + _metadata->exit_code = KSFT_FAIL; + + EXPECT_EQ(0, is_signaled); +} + +/* clang-format off */ +FIXTURE(scoped_domains) {}; +/* clang-format on */ + +#include "scoped_base_variants.h" + +FIXTURE_SETUP(scoped_domains) +{ + drop_caps(_metadata); +} + +FIXTURE_TEARDOWN(scoped_domains) +{ +} + +/* + * This test ensures that a scoped process cannot send signal out of + * scoped domain. + */ +TEST_F(scoped_domains, check_access_signal) +{ + pid_t child; + pid_t parent = getpid(); + int status; + bool can_signal_child, can_signal_parent; + int pipe_parent[2], pipe_child[2]; + char buf_parent; + int err; + + can_signal_parent = !variant->domain_child; + can_signal_child = !variant->domain_parent; + + if (variant->domain_both) + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); + + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + ASSERT_EQ(0, pipe2(pipe_child, O_CLOEXEC)); + + child = fork(); + ASSERT_LE(0, child); + if (child == 0) { + char buf_child; + + EXPECT_EQ(0, close(pipe_child[0])); + EXPECT_EQ(0, close(pipe_parent[1])); + + if (variant->domain_child) + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); + + ASSERT_EQ(1, write(pipe_child[1], ".", 1)); + EXPECT_EQ(0, close(pipe_child[1])); + + /* Waits for the parent to send signals. */ + ASSERT_EQ(1, read(pipe_parent[0], &buf_child, 1)); + EXPECT_EQ(0, close(pipe_parent[0])); + + err = kill(parent, 0); + if (can_signal_parent) { + ASSERT_EQ(0, err); + } else { + ASSERT_EQ(-1, err); + ASSERT_EQ(EPERM, errno); + } + /* + * No matter of the domain, a process should be able to + * send a signal to itself. + */ + ASSERT_EQ(0, raise(0)); + + _exit(_metadata->exit_code); + return; + } + EXPECT_EQ(0, close(pipe_parent[0])); + EXPECT_EQ(0, close(pipe_child[1])); + + if (variant->domain_parent) + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); + + ASSERT_EQ(1, read(pipe_child[0], &buf_parent, 1)); + EXPECT_EQ(0, close(pipe_child[0])); + + err = kill(child, 0); + if (can_signal_child) { + ASSERT_EQ(0, err); + } else { + ASSERT_EQ(-1, err); + ASSERT_EQ(EPERM, errno); + } + ASSERT_EQ(0, raise(0)); + + ASSERT_EQ(1, write(pipe_parent[1], ".", 1)); + EXPECT_EQ(0, close(pipe_parent[1])); + ASSERT_EQ(child, waitpid(child, &status, 0)); + + if (WIFSIGNALED(status) || !WIFEXITED(status) || + WEXITSTATUS(status) != EXIT_SUCCESS) + _metadata->exit_code = KSFT_FAIL; +} + +enum thread_return { + THREAD_INVALID = 0, + THREAD_SUCCESS = 1, + THREAD_ERROR = 2, + THREAD_TEST_FAILED = 3, +}; + +static void *thread_sync(void *arg) +{ + const int pipe_read = *(int *)arg; + char buf; + + if (read(pipe_read, &buf, 1) != 1) + return (void *)THREAD_ERROR; + + return (void *)THREAD_SUCCESS; +} + +TEST(signal_scoping_thread_before) +{ + pthread_t no_sandbox_thread; + enum thread_return ret = THREAD_INVALID; + int thread_pipe[2]; + + drop_caps(_metadata); + ASSERT_EQ(0, pipe2(thread_pipe, O_CLOEXEC)); + + ASSERT_EQ(0, pthread_create(&no_sandbox_thread, NULL, thread_sync, + &thread_pipe[0])); + + /* Enforces restriction after creating the thread. */ + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); + + EXPECT_EQ(0, pthread_kill(no_sandbox_thread, 0)); + EXPECT_EQ(1, write(thread_pipe[1], ".", 1)); + + EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret)); + EXPECT_EQ(THREAD_SUCCESS, ret); + + EXPECT_EQ(0, close(thread_pipe[0])); + EXPECT_EQ(0, close(thread_pipe[1])); +} + +TEST(signal_scoping_thread_after) +{ + pthread_t scoped_thread; + enum thread_return ret = THREAD_INVALID; + int thread_pipe[2]; + + drop_caps(_metadata); + ASSERT_EQ(0, pipe2(thread_pipe, O_CLOEXEC)); + + /* Enforces restriction before creating the thread. */ + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); + + ASSERT_EQ(0, pthread_create(&scoped_thread, NULL, thread_sync, + &thread_pipe[0])); + + EXPECT_EQ(0, pthread_kill(scoped_thread, 0)); + EXPECT_EQ(1, write(thread_pipe[1], ".", 1)); + + EXPECT_EQ(0, pthread_join(scoped_thread, (void **)&ret)); + EXPECT_EQ(THREAD_SUCCESS, ret); + + EXPECT_EQ(0, close(thread_pipe[0])); + EXPECT_EQ(0, close(thread_pipe[1])); +} + +struct thread_setuid_args { + int pipe_read, new_uid; +}; + +void *thread_setuid(void *ptr) +{ + const struct thread_setuid_args *arg = ptr; + char buf; + + if (read(arg->pipe_read, &buf, 1) != 1) + return (void *)THREAD_ERROR; + + /* libc's setuid() should update all thread's credentials. */ + if (getuid() != arg->new_uid) + return (void *)THREAD_TEST_FAILED; + + return (void *)THREAD_SUCCESS; +} + +TEST(signal_scoping_thread_setuid) +{ + struct thread_setuid_args arg; + pthread_t no_sandbox_thread; + enum thread_return ret = THREAD_INVALID; + int pipe_parent[2]; + int prev_uid; + + disable_caps(_metadata); + + /* This test does not need to be run as root. */ + prev_uid = getuid(); + arg.new_uid = prev_uid + 1; + EXPECT_LT(0, arg.new_uid); + + ASSERT_EQ(0, pipe2(pipe_parent, O_CLOEXEC)); + arg.pipe_read = pipe_parent[0]; + + /* Capabilities must be set before creating a new thread. */ + set_cap(_metadata, CAP_SETUID); + ASSERT_EQ(0, pthread_create(&no_sandbox_thread, NULL, thread_setuid, + &arg)); + + /* Enforces restriction after creating the thread. */ + create_scoped_domain(_metadata, LANDLOCK_SCOPE_SIGNAL); + + EXPECT_NE(arg.new_uid, getuid()); + EXPECT_EQ(0, setuid(arg.new_uid)); + EXPECT_EQ(arg.new_uid, getuid()); + EXPECT_EQ(1, write(pipe_parent[1], ".", 1)); + + EXPECT_EQ(0, pthread_join(no_sandbox_thread, (void **)&ret)); + EXPECT_EQ(THREAD_SUCCESS, ret); + + clear_cap(_metadata, CAP_SETUID); + EXPECT_EQ(0, close(pipe_parent[0])); + EXPECT_EQ(0, close(pipe_parent[1])); +} + +TEST_HARNESS_MAIN diff --git a/tools/testing/selftests/landlock/scoped_test.c b/tools/testing/selftests/landlock/scoped_test.c index 2d15f09d63..b90f76ed0d 100644 --- a/tools/testing/selftests/landlock/scoped_test.c +++ b/tools/testing/selftests/landlock/scoped_test.c @@ -12,7 +12,7 @@ #include "common.h" -#define ACCESS_LAST LANDLOCK_SCOPE_ABSTRACT_UNIX_SOCKET +#define ACCESS_LAST LANDLOCK_SCOPE_SIGNAL TEST(ruleset_with_unknown_scope) {