CVE-2024-5321
Description
A security issue was discovered in Kubernetes clusters with Windows nodes where BUILTIN\Users may be able to read container logs and NT AUTHORITY\Authenticated Users may be able to modify container logs.
AI Insight
LLM-synthesized narrative grounded in this CVE's description and references.
Kubernetes clusters with Windows nodes expose container logs to BUILTIN\Users for reading and NT AUTHORITY\Authenticated Users for modification due to improper file permission enforcement.
Vulnerability
Overview
CVE-2024-5321 is a security issue in Kubernetes clusters that include Windows nodes. The flaw lies in how the kubelet handles container log file permissions on Windows. On Windows, the standard Go os.Chmod function only sets the read-only flag on files, failing to properly apply POSIX-style permission bits. Consequently, when creating log files, the kubelet does not restrict access to the intended owner, leaving files accessible to broader groups. The OTHERS mode in the new Windows-specific file utilities is mapped to BUILTIN\Users, while the GROUP mode is mapped to the file group SID, and the OWNER mode to the file owner SID [1][2][3][4].
Exploitation
Exploitation requires an attacker to have local access to a Windows node in a Kubernetes cluster. No authentication is needed beyond the ability to log in to the node as any user. The prerequisite is that the cluster uses Windows worker nodes and the kubelet creates container log files with the incorrect default permissions. The attack surface is the file system of the Windows node, where container logs are stored. The groups BUILTIN\Users and NT AUTHORITY\Authenticated Users gain unintended access [1].
Impact
An attacker with BUILTIN\Users membership can read container logs, potentially revealing sensitive information such as application secrets, error messages, or user data. More critically, an attacker with NT AUTHORITY\Authenticated Users membership can modify container logs, enabling log tampering to hide malicious activity or inject false entries. This compromises the integrity and confidentiality of logging mechanisms in the cluster.
Mitigation
Kubernetes has released patches that introduce new Windows-specific file permission functions (MkdirAll and Chmod in pkg/filesystem/util) that use Windows APIs (golang.org/x/sys/windows) to correctly set ACLs. These patches ensure that container logs are created with the appropriate permissions, restricting access to the intended container user accounts (ContainerUser or ContainerAdministrator) [1]. Cluster administrators should update to a patched version of Kubernetes. As a workaround, administrators can manually correct permissions on existing log directories, but this is not recommended as a permanent fix. This vulnerability is not currently listed in CISA's Known Exploited Vulnerabilities (KEV) catalog.
- Add funcs in pkg/filesystem/util that can actually set file permissiosn · kubernetes/kubernetes@90589b8
- Add funcs in pkg/filesystem/util that can actually set file permissiosn · kubernetes/kubernetes@de20330
- Add funcs in pkg/filesystem/util that can actually set file permissiosn · kubernetes/kubernetes@23660a7
- Add funcs in pkg/filesystem/util that can actually set file permissiosn · kubernetes/kubernetes@84beb29
AI Insight generated on May 20, 2026. Synthesized from this CVE's description and the cited reference URLs; citations are validated against the source bundle.
Affected packages
Versions sourced from the GitHub Security Advisory.
| Package | Affected versions | Patched versions |
|---|---|---|
k8s.io/kubernetesGo | < 1.27.16 | 1.27.16 |
k8s.io/kubernetesGo | >= 1.28.0, < 1.28.12 | 1.28.12 |
k8s.io/kubernetesGo | >= 1.29.0, < 1.29.7 | 1.29.7 |
k8s.io/kubernetesGo | >= 1.30.0, < 1.30.3 | 1.30.3 |
Affected products
215- Range: >= 1.30.0, < 1.30.3
- osv-coords214 versionspkg:apk/chainguard/argo-cd-2.12pkg:apk/chainguard/argo-cd-2.12-compatpkg:apk/chainguard/argo-cd-2.12-repo-serverpkg:apk/chainguard/argo-cd-fips-2.12pkg:apk/chainguard/argo-cd-fips-2.12-compatpkg:apk/chainguard/argo-cd-fips-2.12-repo-serverpkg:apk/chainguard/argocd-image-updaterpkg:apk/chainguard/argocd-image-updater-fipspkg:apk/chainguard/aws-ebs-csi-driverpkg:apk/chainguard/aws-ebs-csi-driver-fipspkg:apk/chainguard/aws-efs-csi-driverpkg:apk/chainguard/aws-efs-csi-driver-fipspkg:apk/chainguard/calico-3.27pkg:apk/chainguard/calico-3.27-apiserverpkg:apk/chainguard/calico-3.27-app-policypkg:apk/chainguard/calico-3.27-calicoctlpkg:apk/chainguard/calico-3.27-cnipkg:apk/chainguard/calico-3.27-cni-compatpkg:apk/chainguard/calico-3.27-felixpkg:apk/chainguard/calico-3.27-kube-controllerspkg:apk/chainguard/calico-3.27-nodepkg:apk/chainguard/calico-3.27-pod2daemonpkg:apk/chainguard/calico-3.27-pod2daemon-flexvol-compatpkg:apk/chainguard/calico-3.27-typha-clientpkg:apk/chainguard/calico-3.27-typhadpkg:apk/chainguard/calico-apiserver-3.27pkg:apk/chainguard/calico-apiserver-compat-3.27pkg:apk/chainguard/calico-apiserver-compat-fips-3.27pkg:apk/chainguard/calico-apiserver-fips-3.27pkg:apk/chainguard/calico-apiserver-fips-3.28pkg:apk/chainguard/calico-app-policy-3.27pkg:apk/chainguard/calico-app-policy-fips-3.27pkg:apk/chainguard/calico-app-policy-fips-3.28pkg:apk/chainguard/calico-cni-3.27pkg:apk/chainguard/calico-cni-compat-3.27pkg:apk/chainguard/calico-cni-compat-fips-3.27pkg:apk/chainguard/calico-cni-compat-fips-3.28pkg:apk/chainguard/calico-cni-fips-3.27pkg:apk/chainguard/calico-cni-fips-3.28pkg:apk/chainguard/calicoctl-3.27pkg:apk/chainguard/calicoctl-fips-3.27pkg:apk/chainguard/calicoctl-fips-3.28pkg:apk/chainguard/calico-felix-3.27pkg:apk/chainguard/calico-felix-fips-3.27pkg:apk/chainguard/calico-felix-fips-3.28pkg:apk/chainguard/calico-fips-3.27pkg:apk/chainguard/calico-fips-3.28pkg:apk/chainguard/calico-key-cert-provisioner-fips-3.28pkg:apk/chainguard/calico-kube-controllers-3.27pkg:apk/chainguard/calico-kube-controllers-fips-3.27pkg:apk/chainguard/calico-kube-controllers-fips-3.28pkg:apk/chainguard/calico-node-3.27pkg:apk/chainguard/calico-node-fips-3.27pkg:apk/chainguard/calico-node-fips-3.28pkg:apk/chainguard/calico-pod2daemon-3.27pkg:apk/chainguard/calico-pod2daemon-fips-3.27pkg:apk/chainguard/calico-pod2daemon-fips-3.28pkg:apk/chainguard/calico-pod2daemon-flexvol-compat-3.27pkg:apk/chainguard/calico-pod2daemon-flexvol-compat-fips-3.27pkg:apk/chainguard/calico-typha-client-3.27pkg:apk/chainguard/calico-typha-client-fips-3.27pkg:apk/chainguard/calico-typha-client-fips-3.28pkg:apk/chainguard/calico-typhad-3.27pkg:apk/chainguard/calico-typhad-fips-3.27pkg:apk/chainguard/calico-typhad-fips-3.28pkg:apk/chainguard/cluster-autoscaler-1.28pkg:apk/chainguard/cluster-autoscaler-1.28-compatpkg:apk/chainguard/cluster-autoscaler-1.29pkg:apk/chainguard/cluster-autoscaler-1.29-compatpkg:apk/chainguard/cluster-autoscaler-1.30pkg:apk/chainguard/cluster-autoscaler-1.30-compatpkg:apk/chainguard/cluster-autoscaler-fips-1.28pkg:apk/chainguard/cluster-autoscaler-fips-1.28-compatpkg:apk/chainguard/cluster-autoscaler-fips-1.29pkg:apk/chainguard/cluster-autoscaler-fips-1.29-compatpkg:apk/chainguard/cluster-autoscaler-fips-1.30pkg:apk/chainguard/cluster-autoscaler-fips-1.30-compatpkg:apk/chainguard/crictlpkg:apk/chainguard/critestpkg:apk/chainguard/cri-toolspkg:apk/chainguard/gpu-feature-discoverypkg:apk/chainguard/ip-masq-agentpkg:apk/chainguard/kubeadm-fips-1.28pkg:apk/chainguard/kubeadm-fips-1.28-defaultpkg:apk/chainguard/kubeadm-fips-1.29pkg:apk/chainguard/kubeadm-fips-1.29-defaultpkg:apk/chainguard/kubeadm-fips-1.30pkg:apk/chainguard/kubeadm-fips-1.30-defaultpkg:apk/chainguard/kubeadm-fips-1.30-default-compatpkg:apk/chainguard/kube-apiserver-fips-1.28pkg:apk/chainguard/kube-apiserver-fips-1.28-defaultpkg:apk/chainguard/kube-apiserver-fips-1.29pkg:apk/chainguard/kube-apiserver-fips-1.29-defaultpkg:apk/chainguard/kube-apiserver-fips-1.30pkg:apk/chainguard/kube-apiserver-fips-1.30-defaultpkg:apk/chainguard/kube-apiserver-fips-1.30-default-compatpkg:apk/chainguard/kube-controller-manager-fips-1.28pkg:apk/chainguard/kube-controller-manager-fips-1.28-defaultpkg:apk/chainguard/kube-controller-manager-fips-1.29pkg:apk/chainguard/kube-controller-manager-fips-1.29-defaultpkg:apk/chainguard/kube-controller-manager-fips-1.30pkg:apk/chainguard/kube-controller-manager-fips-1.30-defaultpkg:apk/chainguard/kube-controller-manager-fips-1.30-default-compatpkg:apk/chainguard/kubectl-bash-completion-fips-1.28pkg:apk/chainguard/kubectl-bash-completion-fips-1.29pkg:apk/chainguard/kubectl-bash-completion-fips-1.30pkg:apk/chainguard/kubectl-fips-1.28pkg:apk/chainguard/kubectl-fips-1.28-defaultpkg:apk/chainguard/kubectl-fips-1.29pkg:apk/chainguard/kubectl-fips-1.29-defaultpkg:apk/chainguard/kubectl-fips-1.30pkg:apk/chainguard/kubectl-fips-1.30-defaultpkg:apk/chainguard/kubectl-fips-1.30-default-compatpkg:apk/chainguard/kubeflow-pipelinespkg:apk/chainguard/kubeflow-pipelines-apiserverpkg:apk/chainguard/kubeflow-pipelines-cache-deployerpkg:apk/chainguard/kubeflow-pipelines-cache-deployer-compatpkg:apk/chainguard/kubeflow-pipelines-cache_serverpkg:apk/chainguard/kubeflow-pipelines-frontendpkg:apk/chainguard/kubeflow-pipelines-metadata-envoy-configpkg:apk/chainguard/kubeflow-pipelines-metadata-writerpkg:apk/chainguard/kubeflow-pipelines-metadata-writer-compatpkg:apk/chainguard/kubeflow-pipelines-persistence_agentpkg:apk/chainguard/kubeflow-pipelines-scheduledworkflowpkg:apk/chainguard/kubeflow-pipelines-viewer-crd-controllerpkg:apk/chainguard/kubelet-fips-1.28pkg:apk/chainguard/kubelet-fips-1.28-defaultpkg:apk/chainguard/kubelet-fips-1.29pkg:apk/chainguard/kubelet-fips-1.29-defaultpkg:apk/chainguard/kubelet-fips-1.30pkg:apk/chainguard/kubelet-fips-1.30-defaultpkg:apk/chainguard/kubelet-fips-1.30-default-compatpkg:apk/chainguard/kube-proxy-fips-1.28pkg:apk/chainguard/kube-proxy-fips-1.28-defaultpkg:apk/chainguard/kube-proxy-fips-1.29pkg:apk/chainguard/kube-proxy-fips-1.29-defaultpkg:apk/chainguard/kube-proxy-fips-1.30pkg:apk/chainguard/kube-proxy-fips-1.30-defaultpkg:apk/chainguard/kube-proxy-fips-1.30-default-compatpkg:apk/chainguard/kubernetes-1.27pkg:apk/chainguard/kubernetes-csi-driver-hostpathpkg:apk/chainguard/kubernetes-dns-node-cachepkg:apk/chainguard/kubernetes-dns-node-cache-fipspkg:apk/chainguard/kubernetes-fips-1.28pkg:apk/chainguard/kubernetes-fips-1.28-defaultpkg:apk/chainguard/kubernetes-fips-1.29pkg:apk/chainguard/kubernetes-fips-1.29-defaultpkg:apk/chainguard/kubernetes-fips-1.30pkg:apk/chainguard/kubernetes-fips-1.30-defaultpkg:apk/chainguard/kubernetes-pause-fips-3.9pkg:apk/chainguard/kube-scheduler-fips-1.28pkg:apk/chainguard/kube-scheduler-fips-1.28-defaultpkg:apk/chainguard/kube-scheduler-fips-1.29pkg:apk/chainguard/kube-scheduler-fips-1.29-defaultpkg:apk/chainguard/kube-scheduler-fips-1.30pkg:apk/chainguard/kube-scheduler-fips-1.30-defaultpkg:apk/chainguard/kube-scheduler-fips-1.30-default-compatpkg:apk/chainguard/local-static-provisionerpkg:apk/chainguard/local-static-provisioner-compatpkg:apk/chainguard/local-volume-node-cleanuppkg:apk/chainguard/local-volume-node-cleanup-compatpkg:apk/chainguard/node-feature-discovery-0.15pkg:apk/chainguard/node-feature-discovery-0.16pkg:apk/chainguard/nodetaintpkg:apk/chainguard/rancher-webhook-0.4pkg:apk/chainguard/rancher-webhook-fips-0.4pkg:apk/chainguard/rancher-webhook-fips-0.5pkg:apk/chainguard/sparkctlpkg:apk/chainguard/spark-operatorpkg:apk/chainguard/spark-operator-oci-entrypointpkg:apk/wolfi/argo-cd-2.12pkg:apk/wolfi/argo-cd-2.12-compatpkg:apk/wolfi/argo-cd-2.12-repo-serverpkg:apk/wolfi/argocd-image-updaterpkg:apk/wolfi/aws-ebs-csi-driverpkg:apk/wolfi/aws-efs-csi-driverpkg:apk/wolfi/cluster-autoscaler-1.28pkg:apk/wolfi/cluster-autoscaler-1.28-compatpkg:apk/wolfi/cluster-autoscaler-1.29pkg:apk/wolfi/cluster-autoscaler-1.29-compatpkg:apk/wolfi/cluster-autoscaler-1.30pkg:apk/wolfi/cluster-autoscaler-1.30-compatpkg:apk/wolfi/crictlpkg:apk/wolfi/critestpkg:apk/wolfi/cri-toolspkg:apk/wolfi/gpu-feature-discoverypkg:apk/wolfi/ip-masq-agentpkg:apk/wolfi/kubeflow-pipelinespkg:apk/wolfi/kubeflow-pipelines-apiserverpkg:apk/wolfi/kubeflow-pipelines-cache-deployerpkg:apk/wolfi/kubeflow-pipelines-cache-deployer-compatpkg:apk/wolfi/kubeflow-pipelines-cache_serverpkg:apk/wolfi/kubeflow-pipelines-frontendpkg:apk/wolfi/kubeflow-pipelines-metadata-envoy-configpkg:apk/wolfi/kubeflow-pipelines-metadata-writerpkg:apk/wolfi/kubeflow-pipelines-metadata-writer-compatpkg:apk/wolfi/kubeflow-pipelines-persistence_agentpkg:apk/wolfi/kubeflow-pipelines-scheduledworkflowpkg:apk/wolfi/kubeflow-pipelines-viewer-crd-controllerpkg:apk/wolfi/kubernetes-1.27pkg:apk/wolfi/kubernetes-csi-driver-hostpathpkg:apk/wolfi/kubernetes-dns-node-cachepkg:apk/wolfi/local-static-provisionerpkg:apk/wolfi/local-static-provisioner-compatpkg:apk/wolfi/local-volume-node-cleanuppkg:apk/wolfi/local-volume-node-cleanup-compatpkg:apk/wolfi/node-feature-discovery-0.15pkg:apk/wolfi/node-feature-discovery-0.16pkg:apk/wolfi/nodetaintpkg:apk/wolfi/sparkctlpkg:apk/wolfi/spark-operatorpkg:apk/wolfi/spark-operator-oci-entrypointpkg:golang/k8s.io/kubernetespkg:rpm/opensuse/govulncheck-vulndb&distro=openSUSE%20Tumbleweed
< 2.12.3-r1+ 213 more
- (no CPE)range: < 2.12.3-r1
- (no CPE)range: < 2.12.3-r1
- (no CPE)range: < 2.12.3-r1
- (no CPE)range: < 2.12.3-r2
- (no CPE)range: < 2.12.3-r2
- (no CPE)range: < 2.12.3-r2
- (no CPE)range: < 0.17.0-r1
- (no CPE)range: < 0.17.0-r2
- (no CPE)range: < 1.32.0-r3
- (no CPE)range: < 1.32.0-r3
- (no CPE)range: < 2.1.1-r0
- (no CPE)range: < 2.1.14-r1
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 3.27.5-r33
- (no CPE)range: < 3.27.5-r35
- (no CPE)range: < 3.28.3-r33
- (no CPE)range: < 1.28.5-r3
- (no CPE)range: < 1.28.5-r3
- (no CPE)range: < 1.29.4-r0
- (no CPE)range: < 1.29.4-r0
- (no CPE)range: < 1.30.2-r1
- (no CPE)range: < 1.30.2-r1
- (no CPE)range: < 1.28.6-r1
- (no CPE)range: < 1.28.6-r1
- (no CPE)range: < 1.29.5-r8
- (no CPE)range: < 1.29.5-r8
- (no CPE)range: < 1.30.2-r1
- (no CPE)range: < 1.30.2-r1
- (no CPE)range: < 1.30.1-r1
- (no CPE)range: < 1.30.1-r1
- (no CPE)range: < 1.30.1-r1
- (no CPE)range: < 0.8.2-r3
- (no CPE)range: < 2.9.3-r14
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 1.14.0-r3
- (no CPE)range: < 1.23.1-r3
- (no CPE)range: < 1.23.1-r2
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 1.28.15-r9
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 0
- (no CPE)range: < 2.7.0-r5
- (no CPE)range: < 2.7.0-r5
- (no CPE)range: < 2.7.0-r5
- (no CPE)range: < 2.7.0-r5
- (no CPE)range: < 0.15.6-r1
- (no CPE)range: < 0.16.3-r1
- (no CPE)range: < 0.0.4-r21
- (no CPE)range: < 0.4.18-r2
- (no CPE)range: < 0.4.18-r4
- (no CPE)range: < 0.5.11-r0
- (no CPE)range: < 1.4.3-r1
- (no CPE)range: < 1.4.3-r1
- (no CPE)range: < 1.4.3-r1
- (no CPE)range: < 2.12.3-r1
- (no CPE)range: < 2.12.3-r1
- (no CPE)range: < 2.12.3-r1
- (no CPE)range: < 0.17.0-r1
- (no CPE)range: < 1.32.0-r3
- (no CPE)range: < 2.1.1-r0
- (no CPE)range: < 1.28.5-r3
- (no CPE)range: < 1.28.5-r3
- (no CPE)range: < 1.29.4-r0
- (no CPE)range: < 1.29.4-r0
- (no CPE)range: < 1.30.2-r1
- (no CPE)range: < 1.30.2-r1
- (no CPE)range: < 1.30.1-r1
- (no CPE)range: < 1.30.1-r1
- (no CPE)range: < 1.30.1-r1
- (no CPE)range: < 0.8.2-r3
- (no CPE)range: < 2.9.3-r14
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 2.2.0-r8
- (no CPE)range: < 0
- (no CPE)range: < 1.14.0-r3
- (no CPE)range: < 1.23.1-r3
- (no CPE)range: < 2.7.0-r5
- (no CPE)range: < 2.7.0-r5
- (no CPE)range: < 2.7.0-r5
- (no CPE)range: < 2.7.0-r5
- (no CPE)range: < 0.15.6-r1
- (no CPE)range: < 0.16.3-r1
- (no CPE)range: < 0.0.4-r21
- (no CPE)range: < 1.4.3-r1
- (no CPE)range: < 1.4.3-r1
- (no CPE)range: < 1.4.3-r1
- (no CPE)range: < 1.27.16
- (no CPE)range: < 0.0.20250807T150727-1.1
Patches
490589b8f63d2Add funcs in pkg/filesystem/util that can actually set file permissiosn
6 files changed · +386 −6
pkg/kubelet/kubelet.go+12 −4 modified@@ -37,7 +37,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "k8s.io/client-go/informers" - + utilfs "k8s.io/kubernetes/pkg/util/filesystem" "k8s.io/mount-utils" "k8s.io/utils/integer" netutils "k8s.io/utils/net" @@ -1335,17 +1335,17 @@ func (kl *Kubelet) setupDataDirs() error { if err := os.MkdirAll(kl.getPodsDir(), 0750); err != nil { return fmt.Errorf("error creating pods directory: %v", err) } - if err := os.MkdirAll(kl.getPluginsDir(), 0750); err != nil { + if err := utilfs.MkdirAll(kl.getPluginsDir(), 0750); err != nil { return fmt.Errorf("error creating plugins directory: %v", err) } - if err := os.MkdirAll(kl.getPluginsRegistrationDir(), 0750); err != nil { + if err := utilfs.MkdirAll(kl.getPluginsRegistrationDir(), 0750); err != nil { return fmt.Errorf("error creating plugins registry directory: %v", err) } if err := os.MkdirAll(kl.getPodResourcesDir(), 0750); err != nil { return fmt.Errorf("error creating podresources directory: %v", err) } if utilfeature.DefaultFeatureGate.Enabled(features.ContainerCheckpoint) { - if err := os.MkdirAll(kl.getCheckpointsDir(), 0700); err != nil { + if err := utilfs.MkdirAll(kl.getCheckpointsDir(), 0700); err != nil { return fmt.Errorf("error creating checkpoint directory: %v", err) } } @@ -1435,6 +1435,14 @@ func (kl *Kubelet) initializeModules() error { } } + if sysruntime.GOOS == "windows" { + // On Windows we should not allow other users to read the logs directory + // to avoid allowing non-root containers from reading the logs of other containers. + if err := utilfs.Chmod(ContainerLogsDir, 0750); err != nil { + return fmt.Errorf("failed to set permissions on directory %q: %w", ContainerLogsDir, err) + } + } + // Start the image manager. kl.imageManager.Start()
pkg/kubelet/kuberuntime/kuberuntime_manager.go+10 −0 modified@@ -22,6 +22,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "sort" "time" @@ -64,6 +65,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/util/cache" "k8s.io/kubernetes/pkg/kubelet/util/format" sc "k8s.io/kubernetes/pkg/securitycontext" + utilfs "k8s.io/kubernetes/pkg/util/filesystem" ) const ( @@ -266,6 +268,14 @@ func NewKubeGenericRuntimeManager( if err := osInterface.MkdirAll(podLogsRootDirectory, 0755); err != nil { klog.ErrorS(err, "Failed to create pod log directory", "path", podLogsRootDirectory) } + + if runtime.GOOS == "windows" { + // On Windows we should not allow other users to read the logs directory + // to avoid allowing non-root containers from reading the logs of other pods. + if err := utilfs.Chmod(podLogsRootDirectory, 0750); err != nil { + klog.ErrorS(err, "Failed to set permissions on pod log directory", "path", podLogsRootDirectory) + } + } } if imageCredentialProviderConfigFile != "" || imageCredentialProviderBinDir != "" {
pkg/util/filesystem/defaultfs.go+1 −2 modified@@ -70,9 +70,8 @@ func (fs *DefaultFs) Rename(oldpath, newpath string) error { return os.Rename(oldpath, newpath) } -// MkdirAll via os.MkdirAll func (fs *DefaultFs) MkdirAll(path string, perm os.FileMode) error { - return os.MkdirAll(fs.prefix(path), perm) + return MkdirAll(fs.prefix(path), perm) } // Chtimes via os.Chtimes
pkg/util/filesystem/util_unix.go+34 −0 added@@ -0,0 +1,34 @@ +//go:build freebsd || linux || darwin +// +build freebsd linux darwin + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filesystem + +import ( + "os" +) + +// Chmod is the same as os.Chmod on Linux. +func Chmod(name string, mode os.FileMode) error { + return os.Chmod(name, mode) +} + +// MkdirAll is the same as os.MkdirAll on Linux. +func MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(path, perm) +}
pkg/util/filesystem/util_windows.go+183 −0 added@@ -0,0 +1,183 @@ +//go:build windows +// +build windows + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filesystem + +import ( + "fmt" + "os" + + "k8s.io/klog/v2" + + "golang.org/x/sys/windows" +) + +// On Windows os.Mkdir all doesn't set any permissions so call the Chown function below to set +// permissions once the directory is created. +func MkdirAll(path string, perm os.FileMode) error { + klog.V(6).InfoS("Function MkdirAll starts", "path", path, "perm", perm) + err := os.MkdirAll(path, perm) + if err != nil { + return fmt.Errorf("Error creating directory %s: %v", path, err) + } + + err = Chmod(path, perm) + if err != nil { + return fmt.Errorf("Error setting permissions for directory %s: %v", path, err) + } + + return nil +} + +const ( + // These aren't defined in the syscall package for Windows :( + USER_READ = 0x100 + USER_WRITE = 0x80 + USER_EXECUTE = 0x40 + GROUP_READ = 0x20 + GROUP_WRITE = 0x10 + GROUP_EXECUTE = 0x8 + OTHERS_READ = 0x4 + OTHERS_WRITE = 0x2 + OTHERS_EXECUTE = 0x1 + USER_ALL = USER_READ | USER_WRITE | USER_EXECUTE + GROUP_ALL = GROUP_READ | GROUP_WRITE | GROUP_EXECUTE + OTHERS_ALL = OTHERS_READ | OTHERS_WRITE | OTHERS_EXECUTE +) + +// On Windows os.Chmod only sets the read-only flag on files, so we need to use Windows APIs to set the desired access on files / directories. +// The OWNER mode will set file permissions for the file owner SID, the GROUP mode will set file permissions for the file group SID, +// and the OTHERS mode will set file permissions for BUILTIN\Users. +// Please note that Windows containers can be run as one of two user accounts; ContainerUser or ContainerAdministrator. +// Containers run as ContainerAdministrator will inherit permissions from BUILTIN\Administrators, +// while containers run as ContainerUser will inherit permissions from BUILTIN\Users. +// Windows containers do not have the ability to run as a custom user account that is known to the host so the OTHERS group mode +// is used to grant / deny permissions of files on the hosts to the ContainerUser account. +func Chmod(path string, filemode os.FileMode) error { + klog.V(6).InfoS("Function Chmod starts", "path", path, "filemode", filemode) + // Get security descriptor for the file + sd, err := windows.GetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION) + if err != nil { + return fmt.Errorf("Error getting security descriptor for file %s: %v", path, err) + } + + // Get owner SID from the security descriptor for assigning USER permissions + owner, _, err := sd.Owner() + if err != nil { + return fmt.Errorf("Error getting owner SID for file %s: %v", path, err) + } + ownerString := owner.String() + + // Get the group SID from the security descriptor for assigning GROUP permissions + group, _, err := sd.Group() + if err != nil { + return fmt.Errorf("Error getting group SID for file %s: %v", path, err) + } + groupString := group.String() + + mask := uint32(windows.ACCESS_MASK(filemode)) + + // Build a new Discretionary Access Control List (DACL) with the desired permissions using + //the Security Descriptor Definition Language (SDDL) format. + // https://learn.microsoft.com/windows/win32/secauthz/security-descriptor-definition-language + // the DACL is a list of Access Control Entries (ACEs) where each ACE represents the permissions (Allow or Deny) for a specific SID. + // Each ACE has the following format: + // (AceType;AceFlags;Rights;ObjectGuid;InheritObjectGuid;AccountSid) + // We can leave ObjectGuid and InheritObjectGuid empty for our purposes. + + dacl := "D:" + + // build the owner ACE + dacl += "(A;OICI;" + if mask&USER_ALL == USER_ALL { + dacl += "FA" + } else { + if mask&USER_READ == USER_READ { + dacl += "FR" + } + if mask&USER_WRITE == USER_WRITE { + dacl += "FW" + } + if mask&USER_EXECUTE == USER_EXECUTE { + dacl += "FX" + } + } + dacl += ";;;" + ownerString + ")" + + // Build the group ACE + dacl += "(A;OICI;" + if mask&GROUP_ALL == GROUP_ALL { + dacl += "FA" + } else { + if mask&GROUP_READ == GROUP_READ { + dacl += "FR" + } + if mask&GROUP_WRITE == GROUP_WRITE { + dacl += "FW" + } + if mask&GROUP_EXECUTE == GROUP_EXECUTE { + dacl += "FX" + } + } + dacl += ";;;" + groupString + ")" + + // Build the others ACE + dacl += "(A;OICI;" + if mask&OTHERS_ALL == OTHERS_ALL { + dacl += "FA" + } else { + if mask&OTHERS_READ == OTHERS_READ { + dacl += "FR" + } + if mask&OTHERS_WRITE == OTHERS_WRITE { + dacl += "FW" + } + if mask&OTHERS_EXECUTE == OTHERS_EXECUTE { + dacl += "FX" + } + } + dacl += ";;;BU)" + + klog.V(6).InfoS("Setting new DACL for path", "path", path, "dacl", dacl) + + // create a new security descriptor from the DACL string + newSD, err := windows.SecurityDescriptorFromString(dacl) + if err != nil { + return fmt.Errorf("Error creating new security descriptor from DACL string: %v", err) + } + + // get the DACL in binary format from the newly created security descriptor + newDACL, _, err := newSD.DACL() + if err != nil { + return fmt.Errorf("Error getting DACL from new security descriptor: %v", err) + } + + // Write the new security descriptor to the file + return windows.SetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, + nil, // owner SID + nil, // group SID + newDACL, + nil) // SACL +}
pkg/util/filesystem/util_windows_test.go+146 −0 added@@ -0,0 +1,146 @@ +//go:build windows +// +build windows + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filesystem + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "golang.org/x/sys/windows" +) + +func TestWindowsChmod(t *testing.T) { + // Note: OWNER will be replaced with the actual owner SID in the test cases + testCases := []struct { + fileMode os.FileMode + expectedDescriptor string + }{ + { + fileMode: 0777, + expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;FA;;;OWNER)(A;OICI;FA;;;BA)(A;OICI;FA;;;BU)", + }, + { + fileMode: 0750, + expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;FA;;;OWNER)(A;OICI;0x1200a9;;;BA)", // 0x1200a9 = GENERIC_READ | GENERIC_EXECUTE + }, + { + fileMode: 0664, + expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;0x12019f;;;OWNER)(A;OICI;0x12019f;;;BA)(A;OICI;FR;;;BU)", // 0x12019f = GENERIC_READ | GENERIC_WRITE + }, + } + + for _, testCase := range testCases { + tempDir, err := os.MkdirTemp("", "test-dir") + require.NoError(t, err, "Failed to create temporary directory.") + defer os.RemoveAll(tempDir) + + // Set the file GROUP to BUILTIN\Administrators (BA) for test determinism and + err = setGroupInfo(tempDir, "S-1-5-32-544") + require.NoError(t, err, "Failed to set group for directory.") + + err = Chmod(tempDir, testCase.fileMode) + require.NoError(t, err, "Failed to set permissions for directory.") + + owner, descriptor, err := getPermissionsInfo(tempDir) + require.NoError(t, err, "Failed to get permissions for directory.") + + expectedDescriptor := strings.ReplaceAll(testCase.expectedDescriptor, "OWNER", owner) + + assert.Equal(t, expectedDescriptor, descriptor, "Unexpected DACL for directory. when setting permissions to %o", testCase.fileMode) + } +} + +// Gets the owner and entire security descriptor of a file or directory in the SDDL format +// https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-definition-language +func getPermissionsInfo(path string) (string, string, error) { + sd, err := windows.GetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION) + if err != nil { + return "", "", fmt.Errorf("Error getting security descriptor for file %s: %v", path, err) + } + + owner, _, err := sd.Owner() + if err != nil { + return "", "", fmt.Errorf("Error getting owner SID for file %s: %v", path, err) + } + + sdString := sd.String() + + return owner.String(), sdString, nil +} + +// Sets the GROUP of a file or a directory to the specified group +func setGroupInfo(path, group string) error { + groupSID, err := windows.StringToSid(group) + if err != nil { + return fmt.Errorf("Error converting group name %s to SID: %v", group, err) + + } + + err = windows.SetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.GROUP_SECURITY_INFORMATION, + nil, // owner SID + groupSID, + nil, // DACL + nil, //SACL + ) + + if err != nil { + return fmt.Errorf("Error setting group SID for file %s: %v", path, err) + } + + return nil +} + +// TestDeleteFilePermissions tests that when a folder's permissions are set to 0660, child items +// cannot be deleted in the folder but when a folder's permissions are set to 0770, child items can be deleted. +func TestDeleteFilePermissions(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test-dir") + require.NoError(t, err, "Failed to create temporary directory.") + + err = Chmod(tempDir, 0660) + require.NoError(t, err, "Failed to set permissions for directory to 0660.") + + filePath := filepath.Join(tempDir, "test-file") + err = os.WriteFile(filePath, []byte("test"), 0440) + require.NoError(t, err, "Failed to create file in directory.") + + err = os.Remove(filePath) + require.Error(t, err, "Expected expected error when trying to remove file in directory.") + + err = Chmod(tempDir, 0770) + require.NoError(t, err, "Failed to set permissions for directory to 0770.") + + err = os.Remove(filePath) + require.NoError(t, err, "Failed to remove file in directory.") + + err = os.Remove(tempDir) + require.NoError(t, err, "Failed to remove directory.") +}
84beb2915fa2Add funcs in pkg/filesystem/util that can actually set file permissiosn
6 files changed · +309 −7
pkg/kubelet/kubelet.go+12 −4 modified@@ -39,7 +39,7 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.12.0" "go.opentelemetry.io/otel/trace" "k8s.io/client-go/informers" - + utilfs "k8s.io/kubernetes/pkg/util/filesystem" "k8s.io/mount-utils" "k8s.io/utils/integer" netutils "k8s.io/utils/net" @@ -1397,17 +1397,17 @@ func (kl *Kubelet) setupDataDirs() error { if err := os.MkdirAll(kl.getPodsDir(), 0750); err != nil { return fmt.Errorf("error creating pods directory: %v", err) } - if err := os.MkdirAll(kl.getPluginsDir(), 0750); err != nil { + if err := utilfs.MkdirAll(kl.getPluginsDir(), 0750); err != nil { return fmt.Errorf("error creating plugins directory: %v", err) } - if err := os.MkdirAll(kl.getPluginsRegistrationDir(), 0750); err != nil { + if err := utilfs.MkdirAll(kl.getPluginsRegistrationDir(), 0750); err != nil { return fmt.Errorf("error creating plugins registry directory: %v", err) } if err := os.MkdirAll(kl.getPodResourcesDir(), 0750); err != nil { return fmt.Errorf("error creating podresources directory: %v", err) } if utilfeature.DefaultFeatureGate.Enabled(features.ContainerCheckpoint) { - if err := os.MkdirAll(kl.getCheckpointsDir(), 0700); err != nil { + if err := utilfs.MkdirAll(kl.getCheckpointsDir(), 0700); err != nil { return fmt.Errorf("error creating checkpoint directory: %v", err) } } @@ -1497,6 +1497,14 @@ func (kl *Kubelet) initializeModules() error { } } + if sysruntime.GOOS == "windows" { + // On Windows we should not allow other users to read the logs directory + // to avoid allowing non-root containers from reading the logs of other containers. + if err := utilfs.Chmod(ContainerLogsDir, 0750); err != nil { + return fmt.Errorf("failed to set permissions on directory %q: %w", ContainerLogsDir, err) + } + } + // Start the image manager. kl.imageManager.Start()
pkg/kubelet/kuberuntime/kuberuntime_manager.go+10 −0 modified@@ -22,6 +22,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "sort" "time" @@ -65,6 +66,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/util/cache" "k8s.io/kubernetes/pkg/kubelet/util/format" sc "k8s.io/kubernetes/pkg/securitycontext" + utilfs "k8s.io/kubernetes/pkg/util/filesystem" ) const ( @@ -267,6 +269,14 @@ func NewKubeGenericRuntimeManager( if err := osInterface.MkdirAll(podLogsRootDirectory, 0755); err != nil { klog.ErrorS(err, "Failed to create pod log directory", "path", podLogsRootDirectory) } + + if runtime.GOOS == "windows" { + // On Windows we should not allow other users to read the logs directory + // to avoid allowing non-root containers from reading the logs of other pods. + if err := utilfs.Chmod(podLogsRootDirectory, 0750); err != nil { + klog.ErrorS(err, "Failed to set permissions on pod log directory", "path", podLogsRootDirectory) + } + } } if imageCredentialProviderConfigFile != "" || imageCredentialProviderBinDir != "" {
pkg/util/filesystem/defaultfs.go+2 −3 modified@@ -72,9 +72,8 @@ func (fs *DefaultFs) Rename(oldpath, newpath string) error { return os.Rename(oldpath, newpath) } -// MkdirAll via os.MkdirAll func (fs *DefaultFs) MkdirAll(path string, perm os.FileMode) error { - return os.MkdirAll(fs.prefix(path), perm) + return MkdirAll(fs.prefix(path), perm) } // MkdirAllWithPathCheck checks if path exists already. If not, it creates a directory @@ -97,7 +96,7 @@ func MkdirAllWithPathCheck(path string, perm os.FileMode) error { return fmt.Errorf("path %v exists but is not a directory", path) } // If existence of path not known, attempt to create it. - if err := os.MkdirAll(path, perm); err != nil { + if err := MkdirAll(path, perm); err != nil { return err } return nil
pkg/util/filesystem/util_unix.go+10 −0 modified@@ -35,3 +35,13 @@ func IsUnixDomainSocket(filePath string) (bool, error) { } return true, nil } + +// Chmod is the same as os.Chmod on Linux. +func Chmod(name string, mode os.FileMode) error { + return os.Chmod(name, mode) +} + +// MkdirAll is the same as os.MkdirAll on Linux. +func MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(path, perm) +}
pkg/util/filesystem/util_windows.go+156 −0 modified@@ -27,6 +27,8 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" + + "golang.org/x/sys/windows" ) const ( @@ -85,3 +87,157 @@ func IsUnixDomainSocket(filePath string) (bool, error) { } return true, nil } + +// On Windows os.Mkdir all doesn't set any permissions so call the Chown function below to set +// permissions once the directory is created. +func MkdirAll(path string, perm os.FileMode) error { + klog.V(6).InfoS("Function MkdirAll starts", "path", path, "perm", perm) + err := os.MkdirAll(path, perm) + if err != nil { + return fmt.Errorf("Error creating directory %s: %v", path, err) + } + + err = Chmod(path, perm) + if err != nil { + return fmt.Errorf("Error setting permissions for directory %s: %v", path, err) + } + + return nil +} + +const ( + // These aren't defined in the syscall package for Windows :( + USER_READ = 0x100 + USER_WRITE = 0x80 + USER_EXECUTE = 0x40 + GROUP_READ = 0x20 + GROUP_WRITE = 0x10 + GROUP_EXECUTE = 0x8 + OTHERS_READ = 0x4 + OTHERS_WRITE = 0x2 + OTHERS_EXECUTE = 0x1 + USER_ALL = USER_READ | USER_WRITE | USER_EXECUTE + GROUP_ALL = GROUP_READ | GROUP_WRITE | GROUP_EXECUTE + OTHERS_ALL = OTHERS_READ | OTHERS_WRITE | OTHERS_EXECUTE +) + +// On Windows os.Chmod only sets the read-only flag on files, so we need to use Windows APIs to set the desired access on files / directories. +// The OWNER mode will set file permissions for the file owner SID, the GROUP mode will set file permissions for the file group SID, +// and the OTHERS mode will set file permissions for BUILTIN\Users. +// Please note that Windows containers can be run as one of two user accounts; ContainerUser or ContainerAdministrator. +// Containers run as ContainerAdministrator will inherit permissions from BUILTIN\Administrators, +// while containers run as ContainerUser will inherit permissions from BUILTIN\Users. +// Windows containers do not have the ability to run as a custom user account that is known to the host so the OTHERS group mode +// is used to grant / deny permissions of files on the hosts to the ContainerUser account. +func Chmod(path string, filemode os.FileMode) error { + klog.V(6).InfoS("Function Chmod starts", "path", path, "filemode", filemode) + // Get security descriptor for the file + sd, err := windows.GetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION) + if err != nil { + return fmt.Errorf("Error getting security descriptor for file %s: %v", path, err) + } + + // Get owner SID from the security descriptor for assigning USER permissions + owner, _, err := sd.Owner() + if err != nil { + return fmt.Errorf("Error getting owner SID for file %s: %v", path, err) + } + ownerString := owner.String() + + // Get the group SID from the security descriptor for assigning GROUP permissions + group, _, err := sd.Group() + if err != nil { + return fmt.Errorf("Error getting group SID for file %s: %v", path, err) + } + groupString := group.String() + + mask := uint32(windows.ACCESS_MASK(filemode)) + + // Build a new Discretionary Access Control List (DACL) with the desired permissions using + //the Security Descriptor Definition Language (SDDL) format. + // https://learn.microsoft.com/windows/win32/secauthz/security-descriptor-definition-language + // the DACL is a list of Access Control Entries (ACEs) where each ACE represents the permissions (Allow or Deny) for a specific SID. + // Each ACE has the following format: + // (AceType;AceFlags;Rights;ObjectGuid;InheritObjectGuid;AccountSid) + // We can leave ObjectGuid and InheritObjectGuid empty for our purposes. + + dacl := "D:" + + // build the owner ACE + dacl += "(A;OICI;" + if mask&USER_ALL == USER_ALL { + dacl += "FA" + } else { + if mask&USER_READ == USER_READ { + dacl += "FR" + } + if mask&USER_WRITE == USER_WRITE { + dacl += "FW" + } + if mask&USER_EXECUTE == USER_EXECUTE { + dacl += "FX" + } + } + dacl += ";;;" + ownerString + ")" + + // Build the group ACE + dacl += "(A;OICI;" + if mask&GROUP_ALL == GROUP_ALL { + dacl += "FA" + } else { + if mask&GROUP_READ == GROUP_READ { + dacl += "FR" + } + if mask&GROUP_WRITE == GROUP_WRITE { + dacl += "FW" + } + if mask&GROUP_EXECUTE == GROUP_EXECUTE { + dacl += "FX" + } + } + dacl += ";;;" + groupString + ")" + + // Build the others ACE + dacl += "(A;OICI;" + if mask&OTHERS_ALL == OTHERS_ALL { + dacl += "FA" + } else { + if mask&OTHERS_READ == OTHERS_READ { + dacl += "FR" + } + if mask&OTHERS_WRITE == OTHERS_WRITE { + dacl += "FW" + } + if mask&OTHERS_EXECUTE == OTHERS_EXECUTE { + dacl += "FX" + } + } + dacl += ";;;BU)" + + klog.V(6).InfoS("Setting new DACL for path", "path", path, "dacl", dacl) + + // create a new security descriptor from the DACL string + newSD, err := windows.SecurityDescriptorFromString(dacl) + if err != nil { + return fmt.Errorf("Error creating new security descriptor from DACL string: %v", err) + } + + // get the DACL in binary format from the newly created security descriptor + newDACL, _, err := newSD.DACL() + if err != nil { + return fmt.Errorf("Error getting DACL from new security descriptor: %v", err) + } + + // Write the new security descriptor to the file + return windows.SetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, + nil, // owner SID + nil, // group SID + newDACL, + nil) // SACL +}
pkg/util/filesystem/util_windows_test.go+119 −0 modified@@ -20,14 +20,20 @@ limitations under the License. package filesystem import ( + "fmt" + "math/rand" "net" "os" + "path/filepath" + "strings" "sync" "testing" "time" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "golang.org/x/sys/windows" ) func TestIsUnixDomainSocketPipe(t *testing.T) { @@ -87,3 +93,116 @@ func TestPendingUnixDomainSocket(t *testing.T) { wg.Wait() unixln.Close() } + +func TestWindowsChmod(t *testing.T) { + // Note: OWNER will be replaced with the actual owner SID in the test cases + testCases := []struct { + fileMode os.FileMode + expectedDescriptor string + }{ + { + fileMode: 0777, + expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;FA;;;OWNER)(A;OICI;FA;;;BA)(A;OICI;FA;;;BU)", + }, + { + fileMode: 0750, + expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;FA;;;OWNER)(A;OICI;0x1200a9;;;BA)", // 0x1200a9 = GENERIC_READ | GENERIC_EXECUTE + }, + { + fileMode: 0664, + expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;0x12019f;;;OWNER)(A;OICI;0x12019f;;;BA)(A;OICI;FR;;;BU)", // 0x12019f = GENERIC_READ | GENERIC_WRITE + }, + } + + for _, testCase := range testCases { + tempDir, err := os.MkdirTemp("", "test-dir") + require.NoError(t, err, "Failed to create temporary directory.") + defer os.RemoveAll(tempDir) + + // Set the file GROUP to BUILTIN\Administrators (BA) for test determinism and + err = setGroupInfo(tempDir, "S-1-5-32-544") + require.NoError(t, err, "Failed to set group for directory.") + + err = Chmod(tempDir, testCase.fileMode) + require.NoError(t, err, "Failed to set permissions for directory.") + + owner, descriptor, err := getPermissionsInfo(tempDir) + require.NoError(t, err, "Failed to get permissions for directory.") + + expectedDescriptor := strings.ReplaceAll(testCase.expectedDescriptor, "OWNER", owner) + + assert.Equal(t, expectedDescriptor, descriptor, "Unexpected DACL for directory. when setting permissions to %o", testCase.fileMode) + } +} + +// Gets the owner and entire security descriptor of a file or directory in the SDDL format +// https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-definition-language +func getPermissionsInfo(path string) (string, string, error) { + sd, err := windows.GetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION) + if err != nil { + return "", "", fmt.Errorf("Error getting security descriptor for file %s: %v", path, err) + } + + owner, _, err := sd.Owner() + if err != nil { + return "", "", fmt.Errorf("Error getting owner SID for file %s: %v", path, err) + } + + sdString := sd.String() + + return owner.String(), sdString, nil +} + +// Sets the GROUP of a file or a directory to the specified group +func setGroupInfo(path, group string) error { + groupSID, err := windows.StringToSid(group) + if err != nil { + return fmt.Errorf("Error converting group name %s to SID: %v", group, err) + + } + + err = windows.SetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.GROUP_SECURITY_INFORMATION, + nil, // owner SID + groupSID, + nil, // DACL + nil, //SACL + ) + + if err != nil { + return fmt.Errorf("Error setting group SID for file %s: %v", path, err) + } + + return nil +} + +// TestDeleteFilePermissions tests that when a folder's permissions are set to 0660, child items +// cannot be deleted in the folder but when a folder's permissions are set to 0770, child items can be deleted. +func TestDeleteFilePermissions(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test-dir") + require.NoError(t, err, "Failed to create temporary directory.") + + err = Chmod(tempDir, 0660) + require.NoError(t, err, "Failed to set permissions for directory to 0660.") + + filePath := filepath.Join(tempDir, "test-file") + err = os.WriteFile(filePath, []byte("test"), 0440) + require.NoError(t, err, "Failed to create file in directory.") + + err = os.Remove(filePath) + require.Error(t, err, "Expected expected error when trying to remove file in directory.") + + err = Chmod(tempDir, 0770) + require.NoError(t, err, "Failed to set permissions for directory to 0770.") + + err = os.Remove(filePath) + require.NoError(t, err, "Failed to remove file in directory.") + + err = os.Remove(tempDir) + require.NoError(t, err, "Failed to remove directory.") +}
de2033033b1dAdd funcs in pkg/filesystem/util that can actually set file permissiosn
6 files changed · +386 −6
pkg/kubelet/kubelet.go+12 −4 modified@@ -38,7 +38,7 @@ import ( "go.opentelemetry.io/otel/attribute" "go.opentelemetry.io/otel/trace" "k8s.io/client-go/informers" - + utilfs "k8s.io/kubernetes/pkg/util/filesystem" "k8s.io/mount-utils" "k8s.io/utils/integer" netutils "k8s.io/utils/net" @@ -1366,17 +1366,17 @@ func (kl *Kubelet) setupDataDirs() error { if err := os.MkdirAll(kl.getPodsDir(), 0750); err != nil { return fmt.Errorf("error creating pods directory: %v", err) } - if err := os.MkdirAll(kl.getPluginsDir(), 0750); err != nil { + if err := utilfs.MkdirAll(kl.getPluginsDir(), 0750); err != nil { return fmt.Errorf("error creating plugins directory: %v", err) } - if err := os.MkdirAll(kl.getPluginsRegistrationDir(), 0750); err != nil { + if err := utilfs.MkdirAll(kl.getPluginsRegistrationDir(), 0750); err != nil { return fmt.Errorf("error creating plugins registry directory: %v", err) } if err := os.MkdirAll(kl.getPodResourcesDir(), 0750); err != nil { return fmt.Errorf("error creating podresources directory: %v", err) } if utilfeature.DefaultFeatureGate.Enabled(features.ContainerCheckpoint) { - if err := os.MkdirAll(kl.getCheckpointsDir(), 0700); err != nil { + if err := utilfs.MkdirAll(kl.getCheckpointsDir(), 0700); err != nil { return fmt.Errorf("error creating checkpoint directory: %v", err) } } @@ -1466,6 +1466,14 @@ func (kl *Kubelet) initializeModules() error { } } + if sysruntime.GOOS == "windows" { + // On Windows we should not allow other users to read the logs directory + // to avoid allowing non-root containers from reading the logs of other containers. + if err := utilfs.Chmod(ContainerLogsDir, 0750); err != nil { + return fmt.Errorf("failed to set permissions on directory %q: %w", ContainerLogsDir, err) + } + } + // Start the image manager. kl.imageManager.Start()
pkg/kubelet/kuberuntime/kuberuntime_manager.go+10 −0 modified@@ -22,6 +22,7 @@ import ( "fmt" "os" "path/filepath" + "runtime" "sort" "time" @@ -64,6 +65,7 @@ import ( "k8s.io/kubernetes/pkg/kubelet/util/cache" "k8s.io/kubernetes/pkg/kubelet/util/format" sc "k8s.io/kubernetes/pkg/securitycontext" + utilfs "k8s.io/kubernetes/pkg/util/filesystem" ) const ( @@ -266,6 +268,14 @@ func NewKubeGenericRuntimeManager( if err := osInterface.MkdirAll(podLogsRootDirectory, 0755); err != nil { klog.ErrorS(err, "Failed to create pod log directory", "path", podLogsRootDirectory) } + + if runtime.GOOS == "windows" { + // On Windows we should not allow other users to read the logs directory + // to avoid allowing non-root containers from reading the logs of other pods. + if err := utilfs.Chmod(podLogsRootDirectory, 0750); err != nil { + klog.ErrorS(err, "Failed to set permissions on pod log directory", "path", podLogsRootDirectory) + } + } } if imageCredentialProviderConfigFile != "" || imageCredentialProviderBinDir != "" {
pkg/util/filesystem/defaultfs.go+1 −2 modified@@ -70,9 +70,8 @@ func (fs *DefaultFs) Rename(oldpath, newpath string) error { return os.Rename(oldpath, newpath) } -// MkdirAll via os.MkdirAll func (fs *DefaultFs) MkdirAll(path string, perm os.FileMode) error { - return os.MkdirAll(fs.prefix(path), perm) + return MkdirAll(fs.prefix(path), perm) } // Chtimes via os.Chtimes
pkg/util/filesystem/util_unix.go+34 −0 added@@ -0,0 +1,34 @@ +//go:build freebsd || linux || darwin +// +build freebsd linux darwin + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filesystem + +import ( + "os" +) + +// Chmod is the same as os.Chmod on Linux. +func Chmod(name string, mode os.FileMode) error { + return os.Chmod(name, mode) +} + +// MkdirAll is the same as os.MkdirAll on Linux. +func MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(path, perm) +}
pkg/util/filesystem/util_windows.go+183 −0 added@@ -0,0 +1,183 @@ +//go:build windows +// +build windows + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filesystem + +import ( + "fmt" + "os" + + "k8s.io/klog/v2" + + "golang.org/x/sys/windows" +) + +// On Windows os.Mkdir all doesn't set any permissions so call the Chown function below to set +// permissions once the directory is created. +func MkdirAll(path string, perm os.FileMode) error { + klog.V(6).InfoS("Function MkdirAll starts", "path", path, "perm", perm) + err := os.MkdirAll(path, perm) + if err != nil { + return fmt.Errorf("Error creating directory %s: %v", path, err) + } + + err = Chmod(path, perm) + if err != nil { + return fmt.Errorf("Error setting permissions for directory %s: %v", path, err) + } + + return nil +} + +const ( + // These aren't defined in the syscall package for Windows :( + USER_READ = 0x100 + USER_WRITE = 0x80 + USER_EXECUTE = 0x40 + GROUP_READ = 0x20 + GROUP_WRITE = 0x10 + GROUP_EXECUTE = 0x8 + OTHERS_READ = 0x4 + OTHERS_WRITE = 0x2 + OTHERS_EXECUTE = 0x1 + USER_ALL = USER_READ | USER_WRITE | USER_EXECUTE + GROUP_ALL = GROUP_READ | GROUP_WRITE | GROUP_EXECUTE + OTHERS_ALL = OTHERS_READ | OTHERS_WRITE | OTHERS_EXECUTE +) + +// On Windows os.Chmod only sets the read-only flag on files, so we need to use Windows APIs to set the desired access on files / directories. +// The OWNER mode will set file permissions for the file owner SID, the GROUP mode will set file permissions for the file group SID, +// and the OTHERS mode will set file permissions for BUILTIN\Users. +// Please note that Windows containers can be run as one of two user accounts; ContainerUser or ContainerAdministrator. +// Containers run as ContainerAdministrator will inherit permissions from BUILTIN\Administrators, +// while containers run as ContainerUser will inherit permissions from BUILTIN\Users. +// Windows containers do not have the ability to run as a custom user account that is known to the host so the OTHERS group mode +// is used to grant / deny permissions of files on the hosts to the ContainerUser account. +func Chmod(path string, filemode os.FileMode) error { + klog.V(6).InfoS("Function Chmod starts", "path", path, "filemode", filemode) + // Get security descriptor for the file + sd, err := windows.GetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION) + if err != nil { + return fmt.Errorf("Error getting security descriptor for file %s: %v", path, err) + } + + // Get owner SID from the security descriptor for assigning USER permissions + owner, _, err := sd.Owner() + if err != nil { + return fmt.Errorf("Error getting owner SID for file %s: %v", path, err) + } + ownerString := owner.String() + + // Get the group SID from the security descriptor for assigning GROUP permissions + group, _, err := sd.Group() + if err != nil { + return fmt.Errorf("Error getting group SID for file %s: %v", path, err) + } + groupString := group.String() + + mask := uint32(windows.ACCESS_MASK(filemode)) + + // Build a new Discretionary Access Control List (DACL) with the desired permissions using + //the Security Descriptor Definition Language (SDDL) format. + // https://learn.microsoft.com/windows/win32/secauthz/security-descriptor-definition-language + // the DACL is a list of Access Control Entries (ACEs) where each ACE represents the permissions (Allow or Deny) for a specific SID. + // Each ACE has the following format: + // (AceType;AceFlags;Rights;ObjectGuid;InheritObjectGuid;AccountSid) + // We can leave ObjectGuid and InheritObjectGuid empty for our purposes. + + dacl := "D:" + + // build the owner ACE + dacl += "(A;OICI;" + if mask&USER_ALL == USER_ALL { + dacl += "FA" + } else { + if mask&USER_READ == USER_READ { + dacl += "FR" + } + if mask&USER_WRITE == USER_WRITE { + dacl += "FW" + } + if mask&USER_EXECUTE == USER_EXECUTE { + dacl += "FX" + } + } + dacl += ";;;" + ownerString + ")" + + // Build the group ACE + dacl += "(A;OICI;" + if mask&GROUP_ALL == GROUP_ALL { + dacl += "FA" + } else { + if mask&GROUP_READ == GROUP_READ { + dacl += "FR" + } + if mask&GROUP_WRITE == GROUP_WRITE { + dacl += "FW" + } + if mask&GROUP_EXECUTE == GROUP_EXECUTE { + dacl += "FX" + } + } + dacl += ";;;" + groupString + ")" + + // Build the others ACE + dacl += "(A;OICI;" + if mask&OTHERS_ALL == OTHERS_ALL { + dacl += "FA" + } else { + if mask&OTHERS_READ == OTHERS_READ { + dacl += "FR" + } + if mask&OTHERS_WRITE == OTHERS_WRITE { + dacl += "FW" + } + if mask&OTHERS_EXECUTE == OTHERS_EXECUTE { + dacl += "FX" + } + } + dacl += ";;;BU)" + + klog.V(6).InfoS("Setting new DACL for path", "path", path, "dacl", dacl) + + // create a new security descriptor from the DACL string + newSD, err := windows.SecurityDescriptorFromString(dacl) + if err != nil { + return fmt.Errorf("Error creating new security descriptor from DACL string: %v", err) + } + + // get the DACL in binary format from the newly created security descriptor + newDACL, _, err := newSD.DACL() + if err != nil { + return fmt.Errorf("Error getting DACL from new security descriptor: %v", err) + } + + // Write the new security descriptor to the file + return windows.SetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, + nil, // owner SID + nil, // group SID + newDACL, + nil) // SACL +}
pkg/util/filesystem/util_windows_test.go+146 −0 added@@ -0,0 +1,146 @@ +//go:build windows +// +build windows + +/* +Copyright 2023 The Kubernetes Authors. + +Licensed under the Apache License, Version 2.0 (the "License"); +you may not use this file except in compliance with the License. +You may obtain a copy of the License at + + http://www.apache.org/licenses/LICENSE-2.0 + +Unless required by applicable law or agreed to in writing, software +distributed under the License is distributed on an "AS IS" BASIS, +WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +See the License for the specific language governing permissions and +limitations under the License. +*/ + +package filesystem + +import ( + "fmt" + "os" + "path/filepath" + "strings" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" + + "golang.org/x/sys/windows" +) + +func TestWindowsChmod(t *testing.T) { + // Note: OWNER will be replaced with the actual owner SID in the test cases + testCases := []struct { + fileMode os.FileMode + expectedDescriptor string + }{ + { + fileMode: 0777, + expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;FA;;;OWNER)(A;OICI;FA;;;BA)(A;OICI;FA;;;BU)", + }, + { + fileMode: 0750, + expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;FA;;;OWNER)(A;OICI;0x1200a9;;;BA)", // 0x1200a9 = GENERIC_READ | GENERIC_EXECUTE + }, + { + fileMode: 0664, + expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;0x12019f;;;OWNER)(A;OICI;0x12019f;;;BA)(A;OICI;FR;;;BU)", // 0x12019f = GENERIC_READ | GENERIC_WRITE + }, + } + + for _, testCase := range testCases { + tempDir, err := os.MkdirTemp("", "test-dir") + require.NoError(t, err, "Failed to create temporary directory.") + defer os.RemoveAll(tempDir) + + // Set the file GROUP to BUILTIN\Administrators (BA) for test determinism and + err = setGroupInfo(tempDir, "S-1-5-32-544") + require.NoError(t, err, "Failed to set group for directory.") + + err = Chmod(tempDir, testCase.fileMode) + require.NoError(t, err, "Failed to set permissions for directory.") + + owner, descriptor, err := getPermissionsInfo(tempDir) + require.NoError(t, err, "Failed to get permissions for directory.") + + expectedDescriptor := strings.ReplaceAll(testCase.expectedDescriptor, "OWNER", owner) + + assert.Equal(t, expectedDescriptor, descriptor, "Unexpected DACL for directory. when setting permissions to %o", testCase.fileMode) + } +} + +// Gets the owner and entire security descriptor of a file or directory in the SDDL format +// https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-definition-language +func getPermissionsInfo(path string) (string, string, error) { + sd, err := windows.GetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION) + if err != nil { + return "", "", fmt.Errorf("Error getting security descriptor for file %s: %v", path, err) + } + + owner, _, err := sd.Owner() + if err != nil { + return "", "", fmt.Errorf("Error getting owner SID for file %s: %v", path, err) + } + + sdString := sd.String() + + return owner.String(), sdString, nil +} + +// Sets the GROUP of a file or a directory to the specified group +func setGroupInfo(path, group string) error { + groupSID, err := windows.StringToSid(group) + if err != nil { + return fmt.Errorf("Error converting group name %s to SID: %v", group, err) + + } + + err = windows.SetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.GROUP_SECURITY_INFORMATION, + nil, // owner SID + groupSID, + nil, // DACL + nil, //SACL + ) + + if err != nil { + return fmt.Errorf("Error setting group SID for file %s: %v", path, err) + } + + return nil +} + +// TestDeleteFilePermissions tests that when a folder's permissions are set to 0660, child items +// cannot be deleted in the folder but when a folder's permissions are set to 0770, child items can be deleted. +func TestDeleteFilePermissions(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test-dir") + require.NoError(t, err, "Failed to create temporary directory.") + + err = Chmod(tempDir, 0660) + require.NoError(t, err, "Failed to set permissions for directory to 0660.") + + filePath := filepath.Join(tempDir, "test-file") + err = os.WriteFile(filePath, []byte("test"), 0440) + require.NoError(t, err, "Failed to create file in directory.") + + err = os.Remove(filePath) + require.Error(t, err, "Expected expected error when trying to remove file in directory.") + + err = Chmod(tempDir, 0770) + require.NoError(t, err, "Failed to set permissions for directory to 0770.") + + err = os.Remove(filePath) + require.NoError(t, err, "Failed to remove file in directory.") + + err = os.Remove(tempDir) + require.NoError(t, err, "Failed to remove directory.") +}
23660a78ae46Add funcs in pkg/filesystem/util that can actually set file permissiosn
5 files changed · +300 −8
pkg/kubelet/kubelet.go+14 −5 modified@@ -39,8 +39,9 @@ import ( semconv "go.opentelemetry.io/otel/semconv/v1.12.0" "go.opentelemetry.io/otel/trace" "k8s.io/client-go/informers" - "k8s.io/mount-utils" + + utilfs "k8s.io/kubernetes/pkg/util/filesystem" netutils "k8s.io/utils/net" v1 "k8s.io/api/core/v1" @@ -1390,7 +1391,7 @@ func (kl *Kubelet) setupDataDirs() error { if err := os.MkdirAll(kl.getRootDir(), 0750); err != nil { return fmt.Errorf("error creating root directory: %v", err) } - if err := os.MkdirAll(kl.getPodLogsDir(), 0750); err != nil { + if err := utilfs.MkdirAll(kl.getPodLogsDir(), 0750); err != nil { return fmt.Errorf("error creating pod logs root directory %q: %w", kl.getPodLogsDir(), err) } if err := kl.hostutil.MakeRShared(kl.getRootDir()); err != nil { @@ -1399,17 +1400,17 @@ func (kl *Kubelet) setupDataDirs() error { if err := os.MkdirAll(kl.getPodsDir(), 0750); err != nil { return fmt.Errorf("error creating pods directory: %v", err) } - if err := os.MkdirAll(kl.getPluginsDir(), 0750); err != nil { + if err := utilfs.MkdirAll(kl.getPluginsDir(), 0750); err != nil { return fmt.Errorf("error creating plugins directory: %v", err) } - if err := os.MkdirAll(kl.getPluginsRegistrationDir(), 0750); err != nil { + if err := utilfs.MkdirAll(kl.getPluginsRegistrationDir(), 0750); err != nil { return fmt.Errorf("error creating plugins registry directory: %v", err) } if err := os.MkdirAll(kl.getPodResourcesDir(), 0750); err != nil { return fmt.Errorf("error creating podresources directory: %v", err) } if utilfeature.DefaultFeatureGate.Enabled(features.ContainerCheckpoint) { - if err := os.MkdirAll(kl.getCheckpointsDir(), 0700); err != nil { + if err := utilfs.MkdirAll(kl.getCheckpointsDir(), 0700); err != nil { return fmt.Errorf("error creating checkpoint directory: %v", err) } } @@ -1502,6 +1503,14 @@ func (kl *Kubelet) initializeModules() error { } } + if sysruntime.GOOS == "windows" { + // On Windows we should not allow other users to read the logs directory + // to avoid allowing non-root containers from reading the logs of other containers. + if err := utilfs.Chmod(ContainerLogsDir, 0750); err != nil { + return fmt.Errorf("failed to set permissions on directory %q: %w", ContainerLogsDir, err) + } + } + // Start the image manager. kl.imageManager.Start()
pkg/util/filesystem/defaultfs.go+2 −3 modified@@ -72,9 +72,8 @@ func (fs *DefaultFs) Rename(oldpath, newpath string) error { return os.Rename(oldpath, newpath) } -// MkdirAll via os.MkdirAll func (fs *DefaultFs) MkdirAll(path string, perm os.FileMode) error { - return os.MkdirAll(fs.prefix(path), perm) + return MkdirAll(fs.prefix(path), perm) } // MkdirAllWithPathCheck checks if path exists already. If not, it creates a directory @@ -97,7 +96,7 @@ func MkdirAllWithPathCheck(path string, perm os.FileMode) error { return fmt.Errorf("path %v exists but is not a directory", path) } // If existence of path not known, attempt to create it. - if err := os.MkdirAll(path, perm); err != nil { + if err := MkdirAll(path, perm); err != nil { return err } return nil
pkg/util/filesystem/util_unix.go+10 −0 modified@@ -37,6 +37,16 @@ func IsUnixDomainSocket(filePath string) (bool, error) { return true, nil } +// Chmod is the same as os.Chmod on Linux. +func Chmod(name string, mode os.FileMode) error { + return os.Chmod(name, mode) +} + +// MkdirAll is the same as os.MkdirAll on Linux. +func MkdirAll(path string, perm os.FileMode) error { + return os.MkdirAll(path, perm) +} + // IsAbs is same as filepath.IsAbs on Unix. func IsAbs(path string) bool { return filepath.IsAbs(path)
pkg/util/filesystem/util_windows.go+156 −0 modified@@ -29,6 +29,8 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/klog/v2" + + "golang.org/x/sys/windows" ) const ( @@ -88,6 +90,160 @@ func IsUnixDomainSocket(filePath string) (bool, error) { return true, nil } +// On Windows os.Mkdir all doesn't set any permissions so call the Chown function below to set +// permissions once the directory is created. +func MkdirAll(path string, perm os.FileMode) error { + klog.V(6).InfoS("Function MkdirAll starts", "path", path, "perm", perm) + err := os.MkdirAll(path, perm) + if err != nil { + return fmt.Errorf("Error creating directory %s: %v", path, err) + } + + err = Chmod(path, perm) + if err != nil { + return fmt.Errorf("Error setting permissions for directory %s: %v", path, err) + } + + return nil +} + +const ( + // These aren't defined in the syscall package for Windows :( + USER_READ = 0x100 + USER_WRITE = 0x80 + USER_EXECUTE = 0x40 + GROUP_READ = 0x20 + GROUP_WRITE = 0x10 + GROUP_EXECUTE = 0x8 + OTHERS_READ = 0x4 + OTHERS_WRITE = 0x2 + OTHERS_EXECUTE = 0x1 + USER_ALL = USER_READ | USER_WRITE | USER_EXECUTE + GROUP_ALL = GROUP_READ | GROUP_WRITE | GROUP_EXECUTE + OTHERS_ALL = OTHERS_READ | OTHERS_WRITE | OTHERS_EXECUTE +) + +// On Windows os.Chmod only sets the read-only flag on files, so we need to use Windows APIs to set the desired access on files / directories. +// The OWNER mode will set file permissions for the file owner SID, the GROUP mode will set file permissions for the file group SID, +// and the OTHERS mode will set file permissions for BUILTIN\Users. +// Please note that Windows containers can be run as one of two user accounts; ContainerUser or ContainerAdministrator. +// Containers run as ContainerAdministrator will inherit permissions from BUILTIN\Administrators, +// while containers run as ContainerUser will inherit permissions from BUILTIN\Users. +// Windows containers do not have the ability to run as a custom user account that is known to the host so the OTHERS group mode +// is used to grant / deny permissions of files on the hosts to the ContainerUser account. +func Chmod(path string, filemode os.FileMode) error { + klog.V(6).InfoS("Function Chmod starts", "path", path, "filemode", filemode) + // Get security descriptor for the file + sd, err := windows.GetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION) + if err != nil { + return fmt.Errorf("Error getting security descriptor for file %s: %v", path, err) + } + + // Get owner SID from the security descriptor for assigning USER permissions + owner, _, err := sd.Owner() + if err != nil { + return fmt.Errorf("Error getting owner SID for file %s: %v", path, err) + } + ownerString := owner.String() + + // Get the group SID from the security descriptor for assigning GROUP permissions + group, _, err := sd.Group() + if err != nil { + return fmt.Errorf("Error getting group SID for file %s: %v", path, err) + } + groupString := group.String() + + mask := uint32(windows.ACCESS_MASK(filemode)) + + // Build a new Discretionary Access Control List (DACL) with the desired permissions using + //the Security Descriptor Definition Language (SDDL) format. + // https://learn.microsoft.com/windows/win32/secauthz/security-descriptor-definition-language + // the DACL is a list of Access Control Entries (ACEs) where each ACE represents the permissions (Allow or Deny) for a specific SID. + // Each ACE has the following format: + // (AceType;AceFlags;Rights;ObjectGuid;InheritObjectGuid;AccountSid) + // We can leave ObjectGuid and InheritObjectGuid empty for our purposes. + + dacl := "D:" + + // build the owner ACE + dacl += "(A;OICI;" + if mask&USER_ALL == USER_ALL { + dacl += "FA" + } else { + if mask&USER_READ == USER_READ { + dacl += "FR" + } + if mask&USER_WRITE == USER_WRITE { + dacl += "FW" + } + if mask&USER_EXECUTE == USER_EXECUTE { + dacl += "FX" + } + } + dacl += ";;;" + ownerString + ")" + + // Build the group ACE + dacl += "(A;OICI;" + if mask&GROUP_ALL == GROUP_ALL { + dacl += "FA" + } else { + if mask&GROUP_READ == GROUP_READ { + dacl += "FR" + } + if mask&GROUP_WRITE == GROUP_WRITE { + dacl += "FW" + } + if mask&GROUP_EXECUTE == GROUP_EXECUTE { + dacl += "FX" + } + } + dacl += ";;;" + groupString + ")" + + // Build the others ACE + dacl += "(A;OICI;" + if mask&OTHERS_ALL == OTHERS_ALL { + dacl += "FA" + } else { + if mask&OTHERS_READ == OTHERS_READ { + dacl += "FR" + } + if mask&OTHERS_WRITE == OTHERS_WRITE { + dacl += "FW" + } + if mask&OTHERS_EXECUTE == OTHERS_EXECUTE { + dacl += "FX" + } + } + dacl += ";;;BU)" + + klog.V(6).InfoS("Setting new DACL for path", "path", path, "dacl", dacl) + + // create a new security descriptor from the DACL string + newSD, err := windows.SecurityDescriptorFromString(dacl) + if err != nil { + return fmt.Errorf("Error creating new security descriptor from DACL string: %v", err) + } + + // get the DACL in binary format from the newly created security descriptor + newDACL, _, err := newSD.DACL() + if err != nil { + return fmt.Errorf("Error getting DACL from new security descriptor: %v", err) + } + + // Write the new security descriptor to the file + return windows.SetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.PROTECTED_DACL_SECURITY_INFORMATION, + nil, // owner SID + nil, // group SID + newDACL, + nil) // SACL +} + // IsAbs returns whether the given path is absolute or not. // On Windows, filepath.IsAbs will not return True for paths prefixed with a slash, even // though they can be used as absolute paths (https://docs.microsoft.com/en-us/dotnet/standard/io/file-path-formats).
pkg/util/filesystem/util_windows_test.go+118 −0 modified@@ -20,16 +20,21 @@ limitations under the License. package filesystem import ( + "fmt" "math/rand" "net" "os" + "path/filepath" + "strings" "sync" "testing" "time" winio "github.com/Microsoft/go-winio" "github.com/stretchr/testify/assert" "github.com/stretchr/testify/require" + + "golang.org/x/sys/windows" ) func TestIsUnixDomainSocketPipe(t *testing.T) { @@ -90,6 +95,119 @@ func TestPendingUnixDomainSocket(t *testing.T) { unixln.Close() } +func TestWindowsChmod(t *testing.T) { + // Note: OWNER will be replaced with the actual owner SID in the test cases + testCases := []struct { + fileMode os.FileMode + expectedDescriptor string + }{ + { + fileMode: 0777, + expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;FA;;;OWNER)(A;OICI;FA;;;BA)(A;OICI;FA;;;BU)", + }, + { + fileMode: 0750, + expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;FA;;;OWNER)(A;OICI;0x1200a9;;;BA)", // 0x1200a9 = GENERIC_READ | GENERIC_EXECUTE + }, + { + fileMode: 0664, + expectedDescriptor: "O:OWNERG:BAD:PAI(A;OICI;0x12019f;;;OWNER)(A;OICI;0x12019f;;;BA)(A;OICI;FR;;;BU)", // 0x12019f = GENERIC_READ | GENERIC_WRITE + }, + } + + for _, testCase := range testCases { + tempDir, err := os.MkdirTemp("", "test-dir") + require.NoError(t, err, "Failed to create temporary directory.") + defer os.RemoveAll(tempDir) + + // Set the file GROUP to BUILTIN\Administrators (BA) for test determinism and + err = setGroupInfo(tempDir, "S-1-5-32-544") + require.NoError(t, err, "Failed to set group for directory.") + + err = Chmod(tempDir, testCase.fileMode) + require.NoError(t, err, "Failed to set permissions for directory.") + + owner, descriptor, err := getPermissionsInfo(tempDir) + require.NoError(t, err, "Failed to get permissions for directory.") + + expectedDescriptor := strings.ReplaceAll(testCase.expectedDescriptor, "OWNER", owner) + + assert.Equal(t, expectedDescriptor, descriptor, "Unexpected DACL for directory. when setting permissions to %o", testCase.fileMode) + } +} + +// Gets the owner and entire security descriptor of a file or directory in the SDDL format +// https://learn.microsoft.com/en-us/windows/win32/secauthz/security-descriptor-definition-language +func getPermissionsInfo(path string) (string, string, error) { + sd, err := windows.GetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.DACL_SECURITY_INFORMATION|windows.OWNER_SECURITY_INFORMATION|windows.GROUP_SECURITY_INFORMATION) + if err != nil { + return "", "", fmt.Errorf("Error getting security descriptor for file %s: %v", path, err) + } + + owner, _, err := sd.Owner() + if err != nil { + return "", "", fmt.Errorf("Error getting owner SID for file %s: %v", path, err) + } + + sdString := sd.String() + + return owner.String(), sdString, nil +} + +// Sets the GROUP of a file or a directory to the specified group +func setGroupInfo(path, group string) error { + groupSID, err := windows.StringToSid(group) + if err != nil { + return fmt.Errorf("Error converting group name %s to SID: %v", group, err) + + } + + err = windows.SetNamedSecurityInfo( + path, + windows.SE_FILE_OBJECT, + windows.GROUP_SECURITY_INFORMATION, + nil, // owner SID + groupSID, + nil, // DACL + nil, //SACL + ) + + if err != nil { + return fmt.Errorf("Error setting group SID for file %s: %v", path, err) + } + + return nil +} + +// TestDeleteFilePermissions tests that when a folder's permissions are set to 0660, child items +// cannot be deleted in the folder but when a folder's permissions are set to 0770, child items can be deleted. +func TestDeleteFilePermissions(t *testing.T) { + tempDir, err := os.MkdirTemp("", "test-dir") + require.NoError(t, err, "Failed to create temporary directory.") + + err = Chmod(tempDir, 0660) + require.NoError(t, err, "Failed to set permissions for directory to 0660.") + + filePath := filepath.Join(tempDir, "test-file") + err = os.WriteFile(filePath, []byte("test"), 0440) + require.NoError(t, err, "Failed to create file in directory.") + + err = os.Remove(filePath) + require.Error(t, err, "Expected expected error when trying to remove file in directory.") + + err = Chmod(tempDir, 0770) + require.NoError(t, err, "Failed to set permissions for directory to 0770.") + + err = os.Remove(filePath) + require.NoError(t, err, "Failed to remove file in directory.") + + err = os.Remove(tempDir) + require.NoError(t, err, "Failed to remove directory.") +} + func TestAbsWithSlash(t *testing.T) { // On Windows, filepath.IsAbs will not return True for paths prefixed with a slash assert.True(t, IsAbs("/test"))
Vulnerability mechanics
Generated on May 9, 2026. Inputs: CWE entries + fix-commit diffs from this CVE's patches. Citations validated against bundle.
References
9- github.com/advisories/GHSA-82m2-cv7p-4m75ghsaADVISORY
- nvd.nist.gov/vuln/detail/CVE-2024-5321ghsaADVISORY
- github.com/kubernetes/kubernetes/commit/23660a78ae462a6c8c75ac7ffd9af97550dda1aaghsaWEB
- github.com/kubernetes/kubernetes/commit/84beb2915fa28ae477fe0676be8ba94ccd2b811aghsaWEB
- github.com/kubernetes/kubernetes/commit/90589b8f63d28bcd3db89749950ebc48ed07c190ghsaWEB
- github.com/kubernetes/kubernetes/commit/de2033033b1d202ecaaa79d41861a075df8b49c1ghsaWEB
- github.com/kubernetes/kubernetes/issues/126161nvdWEB
- groups.google.com/g/kubernetes-security-announce/c/81c0BHkKNt0nvdWEB
- www.openwall.com/lists/oss-security/2024/07/17/3nvd
News mentions
0No linked articles in our index yet.