9VjZdZddlmZddlZddlZejdZdZd d Zd d Z dS)abTrust-boundary validation for panel-supplied document root strings. The docroot value originates from the hosting panel (cPanel / DirectAdmin / Plesk) and is consumed by privileged code that writes jail.c mount configuration files read by root. The jail.c mount syntax is whitespace-delimited (MountEntry.render in mount_types.py joins source/target/options with spaces) and section headers are bracketed (`[]` in jail_config.MountConfig.render), so any whitespace, control character, newline, or bracket inside the docroot corrupts the parser. A sibling module already rejects newlines/carriage-returns on the analogous crontab write path (crontab/libhooks.py and crontab/parser.py); this module is the equivalent guard for the jail mount config write path. Validation is centralized at the trust boundary - call sites are `enable_website_isolation` (where a tenant-owned domain is resolved to a docroot for the first time) and `write_jail_mounts_config` (where the docroot map is re-read from the panel for every regeneration). The helper raises ValueError on rejection, matching the sibling crontab pattern. ) annotationsNz^/[A-Za-z0-9_./-]*$idocrootstrreturnct|tstd||stdt|tkr*tdt|dtd||dstd|t |std||dD]}|d krtd ||S) a1Validate a panel-supplied document root before it reaches the jail mount config writer. Args: docroot: Document root string returned by the panel (e.g. from ``clcommon.cpapi.docroot`` or ``clcommon.cpapi.userdomains``). Returns: The validated docroot string, unchanged. Raises: ValueError: If the docroot is empty, not an absolute path, too long, contains a path-traversal segment, or contains any character outside the strict allowlist (alnum, `_`, `-`, `.`, `/`). z Invalid docroot (not a string): zInvalid docroot: empty stringzInvalid docroot (length z exceeds ): /z Invalid docroot (not absolute): z)Invalid docroot (disallowed characters): z..z$Invalid docroot (parent traversal): ) isinstancer ValueErrorlen_DOCROOT_MAX_LEN startswith_DOCROOT_ALLOWED_REmatchsplit)rsegments _opt/cloudlinux/venv/lib/python3.11/site-packages/clcagefslib/webisolation/docroot_validation.pyvalidate_docrootr3s3" gs # #IGGGGHHH :8999 7||&&& ^s7|| ^ ^>N ^ ^SZ ^ ^      c " "IGGGGHHH  $ $W - -RPWPPQQQ ==%%QQ d??OGOOPP P  Nallowed_prefixcJ tj|d}tj|d}n(#t$r}t d|d||d}~wwxYw||kr0||dzst d|d|d||S) a Reject a docroot whose on-disk path escapes ``allowed_prefix``. The lexical ``validate_docroot`` above pins the *string shape* of a panel-supplied docroot, but the value still flows verbatim into jail.c mount-line ``source`` fields and is later consumed by a root-run mount executor that calls ``bind(2)``. Per Linux mount(2) semantics, MS_BIND on a symlinked source dereferences the symlink and bind-mounts the resolved path - which lets a tenant who can write anywhere along the docroot's ancestor chain (typically ``/home//public_html/`` on cPanel / DA / Plesk) pre-aim the bind source at an arbitrary host path (``ln -s / /home//public_html/evil``). The root-run jail then happily bind-mounts ``/`` (or any other operator path the tenant chose) inside the tenant's isolated namespace, exposing ``/etc/shadow``, other tenants' homes, etc. The fix shape is the playbook's ``realpath() + assert the resolved path begins with the allowed prefix``: canonicalise both the docroot and the allowed prefix (so a benign operator-installed symlink like ``/home -> /home2`` does not produce a false reject), then require that the resolved docroot stays under the resolved prefix. A tenant-planted symlink aimed outside the home tree (``/home/u/public_html/evil -> /``) resolves to ``/``, which is not a descendant of the user's resolved home and is rejected. The sibling ``create_overlay_storage_directory`` already takes the equivalent O_NOFOLLOW component-walk stance (see ``py/clcagefslib/webisolation/jail_utils.py``); this is the docroot-side analogue called at the same trust boundary as ``validate_docroot``. Args: docroot: Document root string returned by the panel. Must already have passed the lexical ``validate_docroot`` check (so it is absolute, ``..``-free, and within the character allowlist) - this function does *not* re-run those checks. allowed_prefix: Absolute path the resolved docroot must be inside. Typically the user's home directory; passing the unresolved value is fine because the function canonicalises it too. Returns: The docroot string, unchanged. Raises: ValueError: If the resolved docroot escapes the resolved ``allowed_prefix``, or if either path cannot be canonicalised (e.g. because of an OS error other than ENOENT). F)strictz!Invalid docroot (cannot resolve: rNr zInvalid docroot (resolves to z outside allowed prefix )ospathrealpathOSErrorr r)rrresolved_docrootresolved_prefixexcs rvalidate_docroot_no_symlinksr [sr7++GE+BB'**>%*HH  C C C C C   ?**3C3N3N#44* @,< @ @- @ @4; @ @    NsAA A*A%%A*)rrrr)rrrrrr) __doc__ __future__rrrecompilerr rr rrr&s,#""""" !bj!788 %%%%PLLLLLLr