From patchwork Sun Sep 29 18:50:47 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Pali_Roh=C3=A1r?= X-Patchwork-Id: 13815198 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 777904F8A0; Sun, 29 Sep 2024 18:51:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635864; cv=none; b=rKmoK6klby0ehf1eByb/nVXzDdhOL5zY4Twb083MHTBYlTX14BTivFwhmAZhoIsK6MSBJVpR9fbnGna7loM3jh70U66wnNs3SqLLBk8ybSmGvIdp9fnMkFsRYOru1OHJ7hnGgSxbkr1FK32QnZ+NYfjWguyusr5duVLL5Pv+1lA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635864; c=relaxed/simple; bh=cj10ep1gPlsUYj3PVruGSEFXLq/JujQahcOckuQVlpw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=jRnw2wg1f98dTR+ScgIZ0NxDq5nP0ezkbjZG3yGmlrs9KBk1vRpoNC5P9teHmfEpipcJ4BsMVklMkUwvxkNVVLLKjuviRtecIWwsQLxkj0L7n7Xh8fce5BmrCtrb7xNgTeB53yWcd5E2FaHMKe4KdDE3gHqFM8FYT8m5HlLpty8= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=QksgGl/i; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="QksgGl/i" Received: by smtp.kernel.org (Postfix) with ESMTPSA id F420EC4CED0; Sun, 29 Sep 2024 18:51:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1727635864; bh=cj10ep1gPlsUYj3PVruGSEFXLq/JujQahcOckuQVlpw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=QksgGl/i71D23dyiAksfR2sYzImV4SGkQ27vF14eYGx9owQjNJGVsK+3YWlFcgyRK PbXWixr/C93a+mrwkbWD0dVYgojLpUsl8dKuIvGt5wDMCOxjcIo9Lny5rGEIp3zXxD TDBbZ4Rzk2G4vwOKCibQZg+WdZxhU9ZUvINMB5cpFhabuLfkr7c5GOS+b58QHzNXND fic7LWNJLfATSrW084nFmKY5A+gRMS+Ny5uvapmxKTZnHcylXxq+0MgHv8TlfueBXn 0OlEYclcNKwgD3S6u4gM5GQavKcdxkSMYhQVMhpEODHxdYqliraeU8uUEXCaKT6b7y IlBT52DTK+XQg== Received: by pali.im (Postfix) id EC32092C; Sun, 29 Sep 2024 20:50:56 +0200 (CEST) From: =?utf-8?q?Pali_Roh=C3=A1r?= To: Steve French , Paulo Alcantara , Ronnie Sahlberg Cc: linux-cifs@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 1/7] cifs: Rename smb2_get_reparse_inode to smb2_create_reparse_inode Date: Sun, 29 Sep 2024 20:50:47 +0200 Message-Id: <20240929185053.10554-2-pali@kernel.org> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20240929185053.10554-1-pali@kernel.org> References: <20240929185053.10554-1-pali@kernel.org> Precedence: bulk X-Mailing-List: linux-cifs@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 This function creates a new reparse point, so put "create" into its name. Signed-off-by: Pali Rohár --- fs/smb/client/reparse.c | 6 +++--- fs/smb/client/smb2inode.c | 2 +- fs/smb/client/smb2proto.h | 2 +- 3 files changed, 5 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index a23ea2f78c09..507e17244ed3 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -68,7 +68,7 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, convert_delimiter(sym, '/'); iov.iov_base = buf; iov.iov_len = len; - new = smb2_get_reparse_inode(&data, inode->i_sb, xid, + new = smb2_create_reparse_inode(&data, inode->i_sb, xid, tcon, full_path, &iov, NULL); if (!IS_ERR(new)) d_instantiate(dentry, new); @@ -136,7 +136,7 @@ static int mknod_nfs(unsigned int xid, struct inode *inode, .reparse = { .tag = IO_REPARSE_TAG_NFS, .nfs = p, }, }; - new = smb2_get_reparse_inode(&data, inode->i_sb, xid, + new = smb2_create_reparse_inode(&data, inode->i_sb, xid, tcon, full_path, &iov, NULL); if (!IS_ERR(new)) d_instantiate(dentry, new); @@ -282,7 +282,7 @@ static int mknod_wsl(unsigned int xid, struct inode *inode, memcpy(data.wsl.eas, &cc->ea, len); data.wsl.eas_len = len; - new = smb2_get_reparse_inode(&data, inode->i_sb, + new = smb2_create_reparse_inode(&data, inode->i_sb, xid, tcon, full_path, &reparse_iov, &xattr_iov); if (!IS_ERR(new)) diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 6e69a3b98be3..0fc73035d6dc 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -1193,7 +1193,7 @@ smb2_set_file_info(struct inode *inode, const char *full_path, return rc; } -struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data, +struct inode *smb2_create_reparse_inode(struct cifs_open_info_data *data, struct super_block *sb, const unsigned int xid, struct cifs_tcon *tcon, diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index b208232b12a2..4ac30d29d5a1 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -56,7 +56,7 @@ extern int smb3_handle_read_data(struct TCP_Server_Info *server, extern int smb2_query_reparse_tag(const unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const char *path, __u32 *reparse_tag); -struct inode *smb2_get_reparse_inode(struct cifs_open_info_data *data, +struct inode *smb2_create_reparse_inode(struct cifs_open_info_data *data, struct super_block *sb, const unsigned int xid, struct cifs_tcon *tcon, From patchwork Sun Sep 29 18:50:48 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Pali_Roh=C3=A1r?= X-Patchwork-Id: 13815196 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3FEFE364BA; Sun, 29 Sep 2024 18:51:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635864; cv=none; b=Bm5Kw7ASViOLiaOxYyB5YtsGXEjCpip0KEnRLcG6xkc0D/P918Ih2+L5H0vgerv9mxKcssQ7/pQzXzP/CknSfvZktdA6oUWcfK0xdyHHXjQ5ZJ8pZToeSxlWPq/0Kq+a/qvODusvPN1T4n+IQld/KjSBZzm81/Ag8InP12i8/kQ= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635864; c=relaxed/simple; bh=g6/gu9aNBfiUbdKhwIecVwFtjJMBa44gGtl/X23FNYw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=E56fCYsSbFxLjYKLHo2M+pNpNfaXvvQ6BvcuGtySAiDFNcEKRxncNkF9GU3RGb4Ccg9OOM8SWvb1d1XHhkgSs4P+WK4gbJYd92TMSlcPSVgbcquOejzhyBtb5Vv5uRS0uMTpmOVp7d0sZo66wd/RmZ7qsjh70uEBFgzSVpO/WNQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=mKC19SMG; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="mKC19SMG" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 94434C4CEC5; Sun, 29 Sep 2024 18:51:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1727635863; bh=g6/gu9aNBfiUbdKhwIecVwFtjJMBa44gGtl/X23FNYw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=mKC19SMGxhC6HuauqpY6zqT8M92H9K5qIhi7hMFS0FEVJXxu31M4yoRlTRYbYulbR JApQPL+g1os18ps8C1JOOkhKMlmaBTuFpPr++Tglz224FlfH9lgYm4nObkIPal+Pwe bF7m4bCO0WO8HFzgFiaGm4rojXWctKc2CSDd8SrPh5wXQaah2yPtTIbPHPFiSJjCeU hs6pv0gdDqcDuVqfd8eB/L61fj98/RFjvBQJp/L2JQkNymu72AA/dnG60LZuU5iwa+ m7zgyw5bfA2B/7xoFmrI9czbGNFV1PgmBN/nnRrGQ2/rmcvTRnyUbnUmqjVhCQeZiy JA1DP+lG3jyQQ== Received: by pali.im (Postfix) id 2BD2EB64; Sun, 29 Sep 2024 20:50:57 +0200 (CEST) From: =?utf-8?q?Pali_Roh=C3=A1r?= To: Steve French , Paulo Alcantara , Ronnie Sahlberg Cc: linux-cifs@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 2/7] cifs: Improve creating native symlinks pointing to directory Date: Sun, 29 Sep 2024 20:50:48 +0200 Message-Id: <20240929185053.10554-3-pali@kernel.org> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20240929185053.10554-1-pali@kernel.org> References: <20240929185053.10554-1-pali@kernel.org> Precedence: bulk X-Mailing-List: linux-cifs@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 SMB protocol for native symlinks distinguish between symlink to directory and symlink to file. These two symlink types cannot be exchanged, which means that symlink of file type pointing to directory cannot be resolved at all (and vice-versa). Windows follows this rule for local filesystems (NTFS) and also for SMB. Linux SMB client currenly creates all native symlinks of file type. Which means that Windows (and some other SMB clients) cannot resolve symlinks pointing to directory created by Linux SMB client. As Linux system does not distinguish between directory and file symlinks, its API does not provide enough information for Linux SMB client during creating of native symlinks. Add some heuristic into the Linux SMB client for choosing the correct symlink type during symlink creation. Check if the symlink target location ends with slash, or last path component is dot or dot dot, and check if the target location on SMB share exists and is a directory. If at least one condition is truth then create a new SMB symlink of directory type. Otherwise create it as file type symlink. This change improves interoperability with Windows systems. Windows systems would be able to resolve more SMB symlinks created by Linux SMB client which points to existing directory. Signed-off-by: Pali Rohár --- fs/smb/client/reparse.c | 131 ++++++++++++++++++++++++++++++++++++-- fs/smb/client/smb2inode.c | 3 +- fs/smb/client/smb2proto.h | 1 + 3 files changed, 130 insertions(+), 5 deletions(-) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 507e17244ed3..9390ab801696 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -24,13 +24,16 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, struct inode *new; struct kvec iov; __le16 *path; + bool directory = false; char *sym, sep = CIFS_DIR_SEP(cifs_sb); u16 len, plen; int rc = 0; - sym = kstrdup(symname, GFP_KERNEL); + len = strlen(symname)+1; + sym = kzalloc(len+1, GFP_KERNEL); /* +1 for possible directory slash */ if (!sym) return -ENOMEM; + memcpy(sym, symname, len); data = (struct cifs_open_info_data) { .reparse_point = true, @@ -45,6 +48,125 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, goto out; } + /* + * SMB distinguish between symlink to directory and symlink to file. + * They cannot be exchanged (symlink of file type which points to + * directory cannot be resolved and vice-versa). First do some simple + * check, if the original Linux symlink target ends with slash, or + * last path component is dot or dot dot then it is for sure symlink + * to the directory. + */ + if (!directory) { + const char *basename = kbasename(symname); + int basename_len = strlen(basename); + if (basename_len == 0 || /* symname ends with slash */ + (basename_len == 1 && basename[0] == '.') || /* last component is "." */ + (basename_len == 2 && basename[0] == '.' && basename[1] == '.')) /* last component is ".." */ + directory = true; + } + + /* + * If it was not detected as directory yet and the symlink is relative + * then try to resolve the path on the SMB server, check if the path + * exists and determinate if it is a directory or not. + */ + if (!directory && symname[0] != '/') { + __u32 oplock; + struct tcon_link *tlink; + struct cifs_tcon *tcon; + struct cifs_fid fid; + struct cifs_open_parms oparms; + char *resolved_path; + char *path_sep; + int open_rc; + int full_path_len = strlen(full_path); + int symname_len = strlen(symname); + + tlink = cifs_sb_tlink(cifs_sb); + if (IS_ERR(tlink)) { + rc = PTR_ERR(tlink); + goto out; + } + + resolved_path = kzalloc(full_path_len + symname_len + 1, GFP_KERNEL); + if (!resolved_path) { + rc = -ENOMEM; + goto out; + } + + /* + * Compose the resolved SMB symlink path from the SMB full path + * and Linux target symlink path. + */ + memcpy(resolved_path, full_path, full_path_len+1); + path_sep = strrchr(resolved_path, sep); + if (path_sep) + path_sep++; + else + path_sep = resolved_path; + memcpy(path_sep, symname, symname_len+1); + if (sep == '\\') + convert_delimiter(path_sep, sep); + + tcon = tlink_tcon(tlink); + + oparms = (struct cifs_open_parms) { + .tcon = tcon, + .cifs_sb = cifs_sb, + .desired_access = FILE_READ_ATTRIBUTES, + .disposition = FILE_OPEN, + .path = resolved_path, + .fid = &fid, + }; + + /* Try to open as NOT_FILE */ + oplock = 0; + oparms.create_options = cifs_create_options(cifs_sb, CREATE_NOT_FILE); + open_rc = tcon->ses->server->ops->open(xid, &oparms, &oplock, NULL); + if (open_rc == 0) { + /* Successful open means that the target path is definitely a directory. */ + directory = true; + tcon->ses->server->ops->close(xid, tcon, &fid); + } else if (open_rc != -ENOTDIR) { + /* Try to open as NOT_DIR */ + oplock = 0; + oparms.create_options = cifs_create_options(cifs_sb, CREATE_NOT_DIR); + open_rc = tcon->ses->server->ops->open(xid, &oparms, &oplock, NULL); + if (open_rc == 0) { + tcon->ses->server->ops->close(xid, tcon, &fid); + } else if (open_rc == -EISDIR) { + /* -EISDIR means that the target path is definitely a directory. */ + directory = true; + } else { + cifs_dbg(FYI, + "%s: cannot determinate if the symlink target path '%s' " + "is directory or not, creating '%s' as file symlink\n", + __func__, symname, full_path); + } + } + + kfree(resolved_path); + cifs_put_tlink(tlink); + } + + /* + * For absolute symlinks it is not possible to determinate + * if it should point to directory or file. + */ + if (!directory && symname[0] == '/') + cifs_dbg(FYI, + "%s: cannot determinate if the symlink target path '%s' " + "is directory or not, creating '%s' as file symlink\n", + __func__, symname, full_path); + + /* Ensure that directory symlink target in inode would have trailing slash */ + len = strlen(data.symlink_target); + if (directory && data.symlink_target[len-1] != '/') { + /* symlink_target has already preallocated one byte more */ + data.symlink_target[len] = '/'; + data.symlink_target[len+1] = '\0'; + } + plen = 2 * UniStrnlen((wchar_t *)path, PATH_MAX); len = sizeof(*buf) + plen * 2; buf = kzalloc(len, GFP_KERNEL); @@ -69,7 +191,8 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, iov.iov_base = buf; iov.iov_len = len; new = smb2_create_reparse_inode(&data, inode->i_sb, xid, - tcon, full_path, &iov, NULL); + tcon, full_path, directory, + &iov, NULL); if (!IS_ERR(new)) d_instantiate(dentry, new); else @@ -137,7 +260,7 @@ static int mknod_nfs(unsigned int xid, struct inode *inode, }; new = smb2_create_reparse_inode(&data, inode->i_sb, xid, - tcon, full_path, &iov, NULL); + tcon, full_path, false, &iov, NULL); if (!IS_ERR(new)) d_instantiate(dentry, new); else @@ -283,7 +406,7 @@ static int mknod_wsl(unsigned int xid, struct inode *inode, data.wsl.eas_len = len; new = smb2_create_reparse_inode(&data, inode->i_sb, - xid, tcon, full_path, + xid, tcon, full_path, false, &reparse_iov, &xattr_iov); if (!IS_ERR(new)) d_instantiate(dentry, new); diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index 0fc73035d6dc..fffb9df4faeb 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -1198,6 +1198,7 @@ struct inode *smb2_create_reparse_inode(struct cifs_open_info_data *data, const unsigned int xid, struct cifs_tcon *tcon, const char *full_path, + bool directory, struct kvec *reparse_iov, struct kvec *xattr_iov) { @@ -1217,7 +1218,7 @@ struct inode *smb2_create_reparse_inode(struct cifs_open_info_data *data, FILE_READ_ATTRIBUTES | FILE_WRITE_ATTRIBUTES, FILE_CREATE, - CREATE_NOT_DIR | OPEN_REPARSE_POINT, + (directory ? CREATE_NOT_FILE : CREATE_NOT_DIR) | OPEN_REPARSE_POINT, ACL_NO_MODE); if (xattr_iov) oparms.ea_cctx = xattr_iov; diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index 4ac30d29d5a1..fd092f2bb8c5 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -61,6 +61,7 @@ struct inode *smb2_create_reparse_inode(struct cifs_open_info_data *data, const unsigned int xid, struct cifs_tcon *tcon, const char *full_path, + bool directory, struct kvec *reparse_iov, struct kvec *xattr_iov); int smb2_query_reparse_point(const unsigned int xid, From patchwork Sun Sep 29 18:50:49 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Pali_Roh=C3=A1r?= X-Patchwork-Id: 13815197 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 3FEC72C1A2; Sun, 29 Sep 2024 18:51:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635864; cv=none; b=onGYeNlYQDoQDxO4LUEl+p1kkWUuFv53UQXuJW0E6zqaH8QioKf9DdZqDpYnJlJMgHyvMhUAc8ypuH+/+ZyD4GT7ff3r4ZaGwUJL6l2TKiR6iLYFAnU9LVwSFqwG2jjrTS0pcdXnzlLBgXSm+B/d0ZyxXPp9wmwoeP/3ZpbLuqA= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635864; c=relaxed/simple; bh=Sgx5ATycMHh6UDLNYypmUE89xviImR+y+zjObqc7AmM=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=bp74dYCiN9xfJ2Ob6Pj1cKIjv+k0wcr5TR8iChajSpDZExCYjNXwH5nC0mJKSzajv63pVs3d0AvfhQbu9drZdwMzSfkR4BValUk9SSaeQ+PazYylxFcs9GX+y1MGMlc+JBXlRMnoLU48ZDWhLpo1/NL/YkGQ8xG1xAK3vYsWBFI= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=lrlzMV1j; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="lrlzMV1j" Received: by smtp.kernel.org (Postfix) with ESMTPSA id B81C6C4CEC7; Sun, 29 Sep 2024 18:51:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1727635863; bh=Sgx5ATycMHh6UDLNYypmUE89xviImR+y+zjObqc7AmM=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lrlzMV1jfHCrGOhketOZnVgaRScBje/HypPk4qyjgUWtoWSQtFE0KSUebmWGI4hyQ iYIymUc6hnIpTr4Llc74ZhC+yuN21B/ABpLrtdLC/4+SfiPayBiyB56Br98B1aJH9y xl4ViaOmVYW0NUK/+wyroo4zYuaCB+rljr21lXIlQYstkSUdh+j8bnV277ExTWsd/Q E2AiKVcpSoiLI4yu7NLrTjUOEHABHTfZJjWsENH/zdmc8iXcRWO6XLE72j2k9cdDCN BA8LEUVTsyEWgUp7YIjPODhMP6uKeYYpSx/jIorZjVpRs/Q9mlzGKR63ap+h+9KODW 4lTXSP3HQDn/Q== Received: by pali.im (Postfix) id 52E10B9A; Sun, 29 Sep 2024 20:50:57 +0200 (CEST) From: =?utf-8?q?Pali_Roh=C3=A1r?= To: Steve French , Paulo Alcantara , Ronnie Sahlberg Cc: linux-cifs@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 3/7] cifs: Fix creating native symlinks pointing to current or parent directory Date: Sun, 29 Sep 2024 20:50:49 +0200 Message-Id: <20240929185053.10554-4-pali@kernel.org> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20240929185053.10554-1-pali@kernel.org> References: <20240929185053.10554-1-pali@kernel.org> Precedence: bulk X-Mailing-List: linux-cifs@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Calling 'ln -s . symlink' or 'ln -s .. symlink' creates symlink pointing to some object name which ends with U+F029 unicode codepoint. This is because trailing dot in the object name is replaced by non-ASCII unicode codepoint. So Linux SMB client currently is not able to create native symlink pointing to current or parent directory on Windows SMB server which can be read by either on local Windows server or by any other SMB client which does not implement compatible-reverse character replacement. Fix this problem in cifsConvertToUTF16() function which is doing that character replacement. Function comment already says that it does not need to handle special cases '.' and '..', but after introduction of native symlinks in reparse point form, this handling is needed. Signed-off-by: Pali Rohár --- fs/smb/client/cifs_unicode.c | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/fs/smb/client/cifs_unicode.c b/fs/smb/client/cifs_unicode.c index 79d99a913944..4cc6e0896fad 100644 --- a/fs/smb/client/cifs_unicode.c +++ b/fs/smb/client/cifs_unicode.c @@ -484,10 +484,21 @@ cifsConvertToUTF16(__le16 *target, const char *source, int srclen, /** * Remap spaces and periods found at the end of every * component of the path. The special cases of '.' and - * '..' do not need to be dealt with explicitly because - * they are addressed in namei.c:link_path_walk(). + * '..' are need to be handled because of symlinks. + * They are treated as non-end-of-string to avoid + * remapping and breaking symlinks pointing to . or .. **/ - if ((i == srclen - 1) || (source[i+1] == '\\')) + if ((i == 0 || source[i-1] == '\\') && + source[i] == '.' && + (i == srclen-1 || source[i+1] == '\\')) + end_of_string = false; /* "." case */ + else if (i >= 1 && + (i == 1 || source[i-2] == '\\') && + source[i-1] == '.' && + source[i] == '.' && + (i == srclen-1 || source[i+1] == '\\')) + end_of_string = false; /* ".." case */ + else if ((i == srclen - 1) || (source[i+1] == '\\')) end_of_string = true; else end_of_string = false; From patchwork Sun Sep 29 18:50:50 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Pali_Roh=C3=A1r?= X-Patchwork-Id: 13815201 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 90E0A5674D; Sun, 29 Sep 2024 18:51:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635864; cv=none; b=HYl3yKzPrKiiHyuFMO8s9mR/AtKMPC7f1FtmvY1AFQYoTFI/3s4IrfCBu5zaO9cCwF3EuMOnpu0zdBAaCtRyWvZ0aIJdJFlYZKz6SdFZpP2tZnVYKjUzkN71hHwgsaDRERtB6C4mUOv9i265N4UUqRa8fiMhN6s3+LsIEOAv0h8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635864; c=relaxed/simple; bh=g5bO75Vj6gytCHCQB/Ouq7ycs436HTpOnN8x1VmKzYw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=NTf3Ttr9i7Ahq/5lyDgkvQbA/nRILQThIIaEkfS7TnfUJkNfTCsRsqKuAXE8jPo5NWdLI413I3PLjh5ctIRUiZG08jCpRJgSxMQYbknBGCycoCtlNL2o12l8CgmIbYOBOSf+j/k5lK625S/uqntQcEDNiMu19dApUtHQg7YjCRw= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=lnymQfSg; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="lnymQfSg" Received: by smtp.kernel.org (Postfix) with ESMTPSA id DDBDFC4CECD; Sun, 29 Sep 2024 18:51:03 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1727635864; bh=g5bO75Vj6gytCHCQB/Ouq7ycs436HTpOnN8x1VmKzYw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=lnymQfSgLCMK26rUr+sM5JQbiDYjQO5kK+Au1yHkbz/wtV/THd2yIjFhfrleA5EA7 XZpzYuuOQ5IYBpu6t/YKL9/oAYP80I473Tq1bbZt0lLBElrFJ3h1+8tMJhiQ/yu85d tECY5xtxlStGv1S9xzs4oZ9zT2VkYMWwj0KB6y5mWn5Wm9UrXmKaLiH9S6lS5z58Hf DY0kt5LUMG4zdHOku8OOjstUKYGWoZz2Yck1xx6sVTmm8cDBQTssKT4Q39YLvbGGXa 64LHTybkBjz8K/CBM4K114Qih/O4j47obPTgh7T8sXAkK9hNPtuCL+2q7JwXsrJluy 9QQZNMbEGZ8Dw== Received: by pali.im (Postfix) id 8396DC46; Sun, 29 Sep 2024 20:50:57 +0200 (CEST) From: =?utf-8?q?Pali_Roh=C3=A1r?= To: Steve French , Paulo Alcantara , Ronnie Sahlberg Cc: linux-cifs@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 4/7] cifs: Fix parsing native symlinks relative to the export Date: Sun, 29 Sep 2024 20:50:50 +0200 Message-Id: <20240929185053.10554-5-pali@kernel.org> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20240929185053.10554-1-pali@kernel.org> References: <20240929185053.10554-1-pali@kernel.org> Precedence: bulk X-Mailing-List: linux-cifs@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 SMB symlink which has SYMLINK_FLAG_RELATIVE set is relative (as opposite of the absolute) and it can be relative either to the current directory (where is the symlink stored) or relative to the top level export path. To what it is relative depends on the first character of the symlink target path. If the first character is path separator then symlink is relative to the export, otherwise to the current directory. Linux (and generally POSIX systems) supports only symlink paths relative to the current directory where is symlink stored. Currently if Linux SMB client reads relative SMB symlink with first character as path separator (slash), it let as is. Which means that Linux interpret it as absolute symlink pointing from the root (/). But this location is different than the top level directory of SMB export (unless SMB export was mounted to the root) and thefore SMB symlinks relative to the export are interpreted wrongly by Linux SMB client. Fix this problem. As Linux does not have equivalent of the path relative to the top of the mount point, convert such symlink target path relative to the current directory. Do this by prepending "../" pattern N times before the SMB target path, where N is the number of path separators found in SMB symlink path. So for example, if SMB share is mounted to Linux path /mnt/share/, symlink is stored in file /mnt/share/test/folder1/symlink (so SMB symlink path is test\folder1\symlink) and SMB symlink target points to \test\folder2\file, then convert symlink target path to Linux path ../../test/folder2/file. Deduplicate code for parsing SMB symlinks in native form from functions smb2_parse_symlink_response() and parse_reparse_native_symlink() into new function smb2_parse_native_symlink() and pass into this new function a new full_path parameter from callers, which specify SMB full path where is symlink stored. This change fixes resolving of the native Windows symlinks relative to the top level directory of the SMB share. Signed-off-by: Pali Rohár --- fs/smb/client/cifsglob.h | 1 + fs/smb/client/cifsproto.h | 1 + fs/smb/client/inode.c | 1 + fs/smb/client/reparse.c | 92 ++++++++++++++++++++++++++++++++++----- fs/smb/client/reparse.h | 4 +- fs/smb/client/smb1ops.c | 3 +- fs/smb/client/smb2file.c | 21 +++++---- fs/smb/client/smb2inode.c | 6 ++- fs/smb/client/smb2proto.h | 9 +++- 9 files changed, 110 insertions(+), 28 deletions(-) diff --git a/fs/smb/client/cifsglob.h b/fs/smb/client/cifsglob.h index 119537e98f77..7c8d242b98e5 100644 --- a/fs/smb/client/cifsglob.h +++ b/fs/smb/client/cifsglob.h @@ -589,6 +589,7 @@ struct smb_version_operations { /* Check for STATUS_NETWORK_NAME_DELETED */ bool (*is_network_name_deleted)(char *buf, struct TCP_Server_Info *srv); int (*parse_reparse_point)(struct cifs_sb_info *cifs_sb, + const char *full_path, struct kvec *rsp_iov, struct cifs_open_info_data *data); int (*create_reparse_symlink)(const unsigned int xid, diff --git a/fs/smb/client/cifsproto.h b/fs/smb/client/cifsproto.h index 791bddac0396..44555a0e4df6 100644 --- a/fs/smb/client/cifsproto.h +++ b/fs/smb/client/cifsproto.h @@ -675,6 +675,7 @@ char *extract_hostname(const char *unc); char *extract_sharename(const char *unc); int parse_reparse_point(struct reparse_data_buffer *buf, u32 plen, struct cifs_sb_info *cifs_sb, + const char *full_path, bool unicode, struct cifs_open_info_data *data); int __cifs_sfu_make_node(unsigned int xid, struct inode *inode, struct dentry *dentry, struct cifs_tcon *tcon, diff --git a/fs/smb/client/inode.c b/fs/smb/client/inode.c index 0c23634438e5..0fe54b2d2561 100644 --- a/fs/smb/client/inode.c +++ b/fs/smb/client/inode.c @@ -1107,6 +1107,7 @@ static int reparse_info_to_fattr(struct cifs_open_info_data *data, rc = 0; } else if (iov && server->ops->parse_reparse_point) { rc = server->ops->parse_reparse_point(cifs_sb, + full_path, iov, data); } break; diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 9390ab801696..d8edb513556f 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -497,13 +497,81 @@ static int parse_reparse_nfs(struct reparse_nfs_data *buf, return 0; } +int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, + bool unicode, bool relative, + const char *full_path, + struct cifs_sb_info *cifs_sb) +{ + char sep = CIFS_DIR_SEP(cifs_sb); + char *linux_target = NULL; + char *smb_target = NULL; + int levels; + int rc; + int i; + + smb_target = cifs_strndup_from_utf16(buf, len, unicode, cifs_sb->local_nls); + if (!smb_target) { + rc = -ENOMEM; + goto out; + } + + if (smb_target[0] == sep && relative) { + /* + * This is a relative SMB symlink from the top of the share, + * which is the top level directory of the Linux mount point. + * Linux does not support such relative symlinks, so convert + * it to the relative symlink from the current directory. + * full_path is the SMB path to the symlink (from which is + * extracted current directory) and smb_target is the SMB path + * where symlink points, therefore full_path must always be on + * the SMB share. + */ + int smb_target_len = strlen(smb_target)+1; + levels = 0; + for (i = 1; full_path[i]; i++) { /* i=1 to skip leading sep */ + if (full_path[i] == sep) + levels++; + } + linux_target = kmalloc(levels*3 + smb_target_len, GFP_KERNEL); + if (!linux_target) { + rc = -ENOMEM; + goto out; + } + for (i = 0; i < levels; i++) { + linux_target[i*3 + 0] = '.'; + linux_target[i*3 + 1] = '.'; + linux_target[i*3 + 2] = sep; + } + memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */ + } else { + linux_target = smb_target; + smb_target = NULL; + } + + if (sep == '\\') + convert_delimiter(linux_target, '/'); + + rc = 0; + *target = linux_target; + + cifs_dbg(FYI, "%s: symlink target: %s\n", __func__, *target); + +out: + if (rc != 0) + kfree(linux_target); + kfree(smb_target); + return rc; +} + static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym, u32 plen, bool unicode, struct cifs_sb_info *cifs_sb, + const char *full_path, struct cifs_open_info_data *data) { unsigned int len; unsigned int offs; + int rc; /* We handle Symbolic Link reparse tag here. See: MS-FSCC 2.1.2.4 */ @@ -514,20 +582,19 @@ static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym, return -EIO; } - data->symlink_target = cifs_strndup_from_utf16(sym->PathBuffer + offs, - len, unicode, - cifs_sb->local_nls); - if (!data->symlink_target) - return -ENOMEM; - - convert_delimiter(data->symlink_target, '/'); - cifs_dbg(FYI, "%s: target path: %s\n", __func__, data->symlink_target); - - return 0; + rc = smb2_parse_native_symlink(&data->symlink_target, + sym->PathBuffer + offs, + len, + unicode, + le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE, + full_path, + cifs_sb); + return rc; } int parse_reparse_point(struct reparse_data_buffer *buf, u32 plen, struct cifs_sb_info *cifs_sb, + const char *full_path, bool unicode, struct cifs_open_info_data *data) { struct cifs_tcon *tcon = cifs_sb_master_tcon(cifs_sb); @@ -542,7 +609,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf, case IO_REPARSE_TAG_SYMLINK: return parse_reparse_symlink( (struct reparse_symlink_data_buffer *)buf, - plen, unicode, cifs_sb, data); + plen, unicode, cifs_sb, full_path, data); case IO_REPARSE_TAG_LX_SYMLINK: case IO_REPARSE_TAG_AF_UNIX: case IO_REPARSE_TAG_LX_FIFO: @@ -558,6 +625,7 @@ int parse_reparse_point(struct reparse_data_buffer *buf, } int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, + const char *full_path, struct kvec *rsp_iov, struct cifs_open_info_data *data) { @@ -567,7 +635,7 @@ int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, buf = (struct reparse_data_buffer *)((u8 *)io + le32_to_cpu(io->OutputOffset)); - return parse_reparse_point(buf, plen, cifs_sb, true, data); + return parse_reparse_point(buf, plen, cifs_sb, full_path, true, data); } static void wsl_to_fattr(struct cifs_open_info_data *data, diff --git a/fs/smb/client/reparse.h b/fs/smb/client/reparse.h index 2a91f64de557..0203f6d669b3 100644 --- a/fs/smb/client/reparse.h +++ b/fs/smb/client/reparse.h @@ -128,7 +128,9 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, int smb2_mknod_reparse(unsigned int xid, struct inode *inode, struct dentry *dentry, struct cifs_tcon *tcon, const char *full_path, umode_t mode, dev_t dev); -int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, struct kvec *rsp_iov, +int smb2_parse_reparse_point(struct cifs_sb_info *cifs_sb, + const char *full_path, + struct kvec *rsp_iov, struct cifs_open_info_data *data); #endif /* _CIFS_REPARSE_H */ diff --git a/fs/smb/client/smb1ops.c b/fs/smb/client/smb1ops.c index e03c91a49650..fed32db32a46 100644 --- a/fs/smb/client/smb1ops.c +++ b/fs/smb/client/smb1ops.c @@ -994,6 +994,7 @@ static int cifs_query_symlink(const unsigned int xid, } static int cifs_parse_reparse_point(struct cifs_sb_info *cifs_sb, + const char *full_path, struct kvec *rsp_iov, struct cifs_open_info_data *data) { @@ -1004,7 +1005,7 @@ static int cifs_parse_reparse_point(struct cifs_sb_info *cifs_sb, buf = (struct reparse_data_buffer *)((__u8 *)&io->hdr.Protocol + le32_to_cpu(io->DataOffset)); - return parse_reparse_point(buf, plen, cifs_sb, unicode, data); + return parse_reparse_point(buf, plen, cifs_sb, full_path, unicode, data); } static bool diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c index c23478ab1cf8..dc52995f5591 100644 --- a/fs/smb/client/smb2file.c +++ b/fs/smb/client/smb2file.c @@ -63,12 +63,12 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov) return sym; } -int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, char **path) +int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, + const char *full_path, char **path) { struct smb2_symlink_err_rsp *sym; unsigned int sub_offs, sub_len; unsigned int print_offs, print_len; - char *s; if (!cifs_sb || !iov || !iov->iov_base || !iov->iov_len || !path) return -EINVAL; @@ -86,15 +86,13 @@ int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec iov->iov_len < SMB2_SYMLINK_STRUCT_SIZE + print_offs + print_len) return -EINVAL; - s = cifs_strndup_from_utf16((char *)sym->PathBuffer + sub_offs, sub_len, true, - cifs_sb->local_nls); - if (!s) - return -ENOMEM; - convert_delimiter(s, '/'); - cifs_dbg(FYI, "%s: symlink target: %s\n", __func__, s); - - *path = s; - return 0; + return smb2_parse_native_symlink(path, + (char *)sym->PathBuffer + sub_offs, + sub_len, + true, + le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE, + full_path, + cifs_sb); } int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 *oplock, void *buf) @@ -126,6 +124,7 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 goto out; if (hdr->Status == STATUS_STOPPED_ON_SYMLINK) { rc = smb2_parse_symlink_response(oparms->cifs_sb, &err_iov, + oparms->path, &data->symlink_target); if (!rc) { memset(smb2_data, 0, sizeof(*smb2_data)); diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index fffb9df4faeb..c9cdac7d2d50 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -828,6 +828,7 @@ static int smb2_compound_op(const unsigned int xid, struct cifs_tcon *tcon, static int parse_create_response(struct cifs_open_info_data *data, struct cifs_sb_info *cifs_sb, + const char *full_path, const struct kvec *iov) { struct smb2_create_rsp *rsp = iov->iov_base; @@ -841,6 +842,7 @@ static int parse_create_response(struct cifs_open_info_data *data, break; case STATUS_STOPPED_ON_SYMLINK: rc = smb2_parse_symlink_response(cifs_sb, iov, + full_path, &data->symlink_target); if (rc) return rc; @@ -930,14 +932,14 @@ int smb2_query_path_info(const unsigned int xid, switch (rc) { case 0: - rc = parse_create_response(data, cifs_sb, &out_iov[0]); + rc = parse_create_response(data, cifs_sb, full_path, &out_iov[0]); break; case -EOPNOTSUPP: /* * BB TODO: When support for special files added to Samba * re-verify this path. */ - rc = parse_create_response(data, cifs_sb, &out_iov[0]); + rc = parse_create_response(data, cifs_sb, full_path, &out_iov[0]); if (rc || !data->reparse_point) goto out; diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index fd092f2bb8c5..11cef65fa831 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -113,7 +113,14 @@ extern int smb3_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const unsigned char *path, char *pbuf, unsigned int *pbytes_read); -int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, char **path); +int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, + bool unicode, bool relative, + const char *full_path, + struct cifs_sb_info *cifs_sb); +int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, + const struct kvec *iov, + const char *full_path, + char **path); int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 *oplock, void *buf); extern int smb2_unlock_range(struct cifsFileInfo *cfile, From patchwork Sun Sep 29 18:50:51 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Pali_Roh=C3=A1r?= X-Patchwork-Id: 13815202 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id B1BF664A8F; Sun, 29 Sep 2024 18:51:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635864; cv=none; b=u5x3zdpGqT/RrmQfGhPJRrXxdZdL3Lqtgsrylo1Wfvslry/rf7q2cCcBJMAcKNzSl/ULC4bqJXjwwzx7y9ca+zZXlqxTQLqHWPsU6IJQwmBdaJ58lx4kgUJeofxl3JY9YTL5CgHVbheWF71Kf+yOpVwfBvWzZTpqbnhhOsG0XMI= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635864; c=relaxed/simple; bh=8lNwbhZLqMkizGfxdOcD83bcxKPbRqUTAFdELuazLtw=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=crpikpo3x7DMYlu+wWRnEbQUHIk9joFWXmgu5bLvA6s1NaNj3rTgykydSa7rcX4TXIUY8Ul/pJdq90Zb2mx/pWg2XeIOoH2SwMeHubmmMD2bXwoocxQ00YBNrAiOAWfkPF1640tA62j0Rhr4TRwbN169Qy7FNCetXpxf4iYK3YU= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=uvPJjVis; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="uvPJjVis" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 49B10C4CECF; Sun, 29 Sep 2024 18:51:04 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1727635864; bh=8lNwbhZLqMkizGfxdOcD83bcxKPbRqUTAFdELuazLtw=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=uvPJjViseUcxI6RNmcu+wGfrWuisuQ1FW98/TT6uIwXnABQr+hI7Z2VNM9szrr1L3 oLVb4CCKN89vMQrtzW5MIFdUQaJBSxYX3QIuyScMWuD48ZBh65LNKoMx9BrCcu5+cH a4hqd1yk1DDXN+WfP4l+uH5kf86TQTXlnFvf9nHe1xgp7/v4OioPV0LBw+ZWDrG+lM VCRIZCezH7ixR3Eq+WaNEpCCh+lEEv3I9c7iFaB6uUtn7sY5Bg68nPWEeF8ClFmnLj xmtKWy13ILbdixCMqC5OoV3UP1Lntu5Ax0JrJ/rLc0JTlorm4/7tMIkL3vNhahRj0o pfWkgNNEeUHRQ== Received: by pali.im (Postfix) id BEA7CCB5; Sun, 29 Sep 2024 20:50:57 +0200 (CEST) From: =?utf-8?q?Pali_Roh=C3=A1r?= To: Steve French , Paulo Alcantara , Ronnie Sahlberg Cc: linux-cifs@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 5/7] cifs: Fix parsing native symlinks directory/file type Date: Sun, 29 Sep 2024 20:50:51 +0200 Message-Id: <20240929185053.10554-6-pali@kernel.org> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20240929185053.10554-1-pali@kernel.org> References: <20240929185053.10554-1-pali@kernel.org> Precedence: bulk X-Mailing-List: linux-cifs@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 As SMB protocol distinguish between symlink to directory and symlink to file, add some mechanism to disallow resolving incompatible types. When SMB symlink is of the directory type, ensure that its target path ends with slash. This forces Linux to not allow resolving such symlink to file. And when SMB symlink is of the file type and its target path ends with slash then returns an error as such symlink is unresolvable. Such symlink always points to invalid location as file cannot end with slash. This mimics Windows behavior of native SMB symlinks. Signed-off-by: Pali Rohár --- fs/smb/client/reparse.c | 4 ++++ fs/smb/client/smb2file.c | 46 +++++++++++++++++++++++++++++++++++++++ fs/smb/client/smb2inode.c | 4 ++++ fs/smb/client/smb2proto.h | 1 + 4 files changed, 55 insertions(+) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index d8edb513556f..5a738f65b190 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -589,6 +589,10 @@ static int parse_reparse_symlink(struct reparse_symlink_data_buffer *sym, le32_to_cpu(sym->Flags) & SYMLINK_FLAG_RELATIVE, full_path, cifs_sb); + if (!rc) { + bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY; + rc = smb2_fix_symlink_target_type(&data->symlink_target, directory); + } return rc; } diff --git a/fs/smb/client/smb2file.c b/fs/smb/client/smb2file.c index dc52995f5591..8a1a1b2a1c81 100644 --- a/fs/smb/client/smb2file.c +++ b/fs/smb/client/smb2file.c @@ -63,6 +63,48 @@ static struct smb2_symlink_err_rsp *symlink_data(const struct kvec *iov) return sym; } +int smb2_fix_symlink_target_type(char **target, bool directory) +{ + char *buf; + int len = strlen(*target); + + if (!len) + return -EIO; + + /* + * If this is directory symlink and it does not have trailing slash then + * append it. Trailing slash simulates Windows/SMB behavior which do not + * allow resolving directory symlink to file. + */ + if (directory && (*target)[len-1] != '/') { + buf = kzalloc(len+2, GFP_KERNEL); + if (!buf) + return -ENOMEM; + memcpy(buf, *target, len); + buf[len] = '/'; + kfree(*target); + *target = buf; + } + + /* + * If this is a symlink which points to file name with trailing slash, + * or to file named "." or file named ".." then this symlink cannot be + * resolved on Linux because Linux does not allow files with such names. + * So return an error to prevent resolving this file type symlink to + * directory, as it do not point to directory at all. + */ + if (!directory) { + const char *basename = kbasename(*target); + int basename_len = strlen(basename); + if (basename_len == 0 || /* symname ends with slash */ + (basename_len == 1 && basename[0] == '.') || /* last component is "." */ + (basename_len == 2 && basename[0] == '.' && basename[1] == '.')) /* last component is ".." */ + return -EIO; + } + + return 0; +} + int smb2_parse_symlink_response(struct cifs_sb_info *cifs_sb, const struct kvec *iov, const char *full_path, char **path) { @@ -132,6 +174,10 @@ int smb2_open_file(const unsigned int xid, struct cifs_open_parms *oparms, __u32 rc = SMB2_open(xid, oparms, smb2_path, &smb2_oplock, smb2_data, NULL, NULL, NULL); oparms->create_options &= ~OPEN_REPARSE_POINT; + if (!rc) { + bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY; + rc = smb2_fix_symlink_target_type(&data->symlink_target, directory); + } } } } diff --git a/fs/smb/client/smb2inode.c b/fs/smb/client/smb2inode.c index c9cdac7d2d50..faf0a8344faa 100644 --- a/fs/smb/client/smb2inode.c +++ b/fs/smb/client/smb2inode.c @@ -960,6 +960,10 @@ int smb2_query_path_info(const unsigned int xid, rc = smb2_compound_op(xid, tcon, cifs_sb, full_path, &oparms, in_iov, cmds, num_cmds, cfile, NULL, NULL, NULL); + if (data->reparse.tag == IO_REPARSE_TAG_SYMLINK && !rc) { + bool directory = le32_to_cpu(data->fi.Attributes) & ATTR_DIRECTORY; + rc = smb2_fix_symlink_target_type(&data->symlink_target, directory); + } break; case -EREMOTE: break; diff --git a/fs/smb/client/smb2proto.h b/fs/smb/client/smb2proto.h index 11cef65fa831..d308f3c2f8df 100644 --- a/fs/smb/client/smb2proto.h +++ b/fs/smb/client/smb2proto.h @@ -113,6 +113,7 @@ extern int smb3_query_mf_symlink(unsigned int xid, struct cifs_tcon *tcon, struct cifs_sb_info *cifs_sb, const unsigned char *path, char *pbuf, unsigned int *pbytes_read); +int smb2_fix_symlink_target_type(char **target, bool directory); int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, bool unicode, bool relative, const char *full_path, From patchwork Sun Sep 29 18:50:52 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Pali_Roh=C3=A1r?= X-Patchwork-Id: 13815200 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 8DBBD55E53; Sun, 29 Sep 2024 18:51:04 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635864; cv=none; b=CPKU4JIBzqnKm+FbVD+9ZInU5RZ2Mz2VbarcL3yK+QnC8QCxTX/ARvh7EX5Cc0AycwZXlOEbWq+Be/KsmOinqtUyeF0X6gh0KMD7nPOmTIbdRQPYfukUTKZEiximX9c3CBl4s4Lq7RLHaMofy5WX/pu1fXnXt5ccJKO8IcWj9l4= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635864; c=relaxed/simple; bh=Smyda09QytiOgtvr5vHjvB5Eoa2wj8kkQxDlMltOPVc=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=dgMzePpfK05sYtk7GaJO07CarK4YWTTGs0mTQ8XpEbZRJSwyJFJLVxhT+T9bb1a6xShnWZZjG4YHxTK7t2/uCj3jQtXWmyzf4ZfwP4jToU8EKX80bPFOPhoG/xQPuCRTOVPYRFY16fI+DXgzOJBlNH+wmKzqA8rBYJQm0tbh8ZY= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=O/cO4g/P; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="O/cO4g/P" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 505D0C4CED3; Sun, 29 Sep 2024 18:51:04 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1727635864; bh=Smyda09QytiOgtvr5vHjvB5Eoa2wj8kkQxDlMltOPVc=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=O/cO4g/Preg7vgqyLNL0va0+bR5lIBuPmwau3eVoDZE6OBjEvYRdM/3yROaG2aRfH TRWHXuWBUhIqsdB+4JUF4C/HcgAd4vE6ghFAvG0BQHggy7Eo6C8jx7v6AZ4ud0hhvW ZfePehDszOswj9dh9ZqcO4wYsdeASoC1En9mrhZo1UF6HifTDFKYRWdSdnvMO1qCLT DIaWNBffj6XzAD2t24hVuI8l6QRGAxLX2Gvd5vQSO1QXUlOQBRnOooQr0CAi2hubAJ g5reDm/G/DeEO7DUxe44B7QTYdcGq4Dff17gxst13u0PRlJpcN/wY9JtkQ6Dvd9sqq vLb/K6oQ8sxLw== Received: by pali.im (Postfix) id 07ACFD48; Sun, 29 Sep 2024 20:50:58 +0200 (CEST) From: =?utf-8?q?Pali_Roh=C3=A1r?= To: Steve French , Paulo Alcantara , Ronnie Sahlberg Cc: linux-cifs@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 6/7] cifs: Validate content of native symlink Date: Sun, 29 Sep 2024 20:50:52 +0200 Message-Id: <20240929185053.10554-7-pali@kernel.org> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20240929185053.10554-1-pali@kernel.org> References: <20240929185053.10554-1-pali@kernel.org> Precedence: bulk X-Mailing-List: linux-cifs@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 Check that buffer does not contain UTF-16 null codepoint because Linux cannot process symlink with null byte. Signed-off-by: Pali Rohár --- fs/smb/client/reparse.c | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index 5a738f65b190..ca4f96c43508 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -509,6 +509,16 @@ int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, int rc; int i; + /* + * Check that buffer does not contain UTF-16 null codepoint + * because Linux cannot process symlink with null byte. + */ + if (unicode && UniStrnlen((wchar_t *)buf, len/2) != len/2) { + cifs_dbg(VFS, "srv returned null byte in native symlink target location\n"); + rc = -EIO; + goto out; + } + smb_target = cifs_strndup_from_utf16(buf, len, unicode, cifs_sb->local_nls); if (!smb_target) { rc = -ENOMEM; From patchwork Sun Sep 29 18:50:53 2024 Content-Type: text/plain; charset="utf-8" MIME-Version: 1.0 Content-Transfer-Encoding: 8bit X-Patchwork-Submitter: =?utf-8?q?Pali_Roh=C3=A1r?= X-Patchwork-Id: 13815203 Received: from smtp.kernel.org (aws-us-west-2-korg-mail-1.web.codeaurora.org [10.30.226.201]) (using TLSv1.2 with cipher ECDHE-RSA-AES256-GCM-SHA384 (256/256 bits)) (No client certificate requested) by smtp.subspace.kernel.org (Postfix) with ESMTPS id 0307417C22B; Sun, 29 Sep 2024 18:51:06 +0000 (UTC) Authentication-Results: smtp.subspace.kernel.org; arc=none smtp.client-ip=10.30.226.201 ARC-Seal: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635867; cv=none; b=TnJnHk13GBy9gC24/N3Bx9yVls4Su8Tvrp78w9YdqY8S31lU6PhM+UcWanDJdiTaffDS0M8KscsSr7DGDvrdhB89iN904LuiWynmqmGlYqMBuW79x84thnl1K4kIKVs83RYve4QjlOPpMfCYLrnFebq5H8vF5oAUrTKSBEAjX/8= ARC-Message-Signature: i=1; a=rsa-sha256; d=subspace.kernel.org; s=arc-20240116; t=1727635867; c=relaxed/simple; bh=lAmZVsAkcynsmdPdL1C4ZHuO0aKIMeLVr+aIuAIVwWg=; h=From:To:Cc:Subject:Date:Message-Id:In-Reply-To:References: MIME-Version:Content-Type; b=UUYw4mvTGxGyOda3L/uJnUpHrjQB53jv1oS4u6MTU93bifABnbSjZU+1FypC7Ua0XGrVFQNWMTGFUX15KCaSpLcd5RjphMISFYILKIvjJEWsi++CnqJiaW7DWhNs9N5iwd4XDFIVrSSmEBYvpo344jFAS2/gv8XZ3H2hRYXbZGQ= ARC-Authentication-Results: i=1; smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b=AbwKjyBH; arc=none smtp.client-ip=10.30.226.201 Authentication-Results: smtp.subspace.kernel.org; dkim=pass (2048-bit key) header.d=kernel.org header.i=@kernel.org header.b="AbwKjyBH" Received: by smtp.kernel.org (Postfix) with ESMTPSA id 3A790C4CED1; Sun, 29 Sep 2024 18:51:06 +0000 (UTC) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/simple; d=kernel.org; s=k20201202; t=1727635866; bh=lAmZVsAkcynsmdPdL1C4ZHuO0aKIMeLVr+aIuAIVwWg=; h=From:To:Cc:Subject:Date:In-Reply-To:References:From; b=AbwKjyBH0m8oobH9RPKEExSTqRTcPryMP0OOZp6M5L5rmY5EZow8tDLiX6UbYNezj PZPGa9wIsSvodOLUta2h3czPv+q5RbB6Y+wIuI248Y+h7rOPom7gL7SyMCMDhoNdpJ yCjjroBGo9r/7iRUtN7JTkx2IOz1hJ/etuzqJEEwhTqaCBW/7l6eHv2frhDh2K7FnN SnhR8CjEn21frKVySdnf+OSnxoD3puDrw6XugxNYEShcBd+E/gLH6vxPtfoF8UmUU4 1CcT7PhFkv0FhwWqix+BYHd9l8iHK/hb72MoNVcUjKWbAweCa9KUfHORQSpOWLXccN eG/4rBzKpnPjA== Received: by pali.im (Postfix) id 4064BD8F; Sun, 29 Sep 2024 20:50:58 +0200 (CEST) From: =?utf-8?q?Pali_Roh=C3=A1r?= To: Steve French , Paulo Alcantara , Ronnie Sahlberg Cc: linux-cifs@vger.kernel.org, linux-kernel@vger.kernel.org Subject: [PATCH 7/7] cifs: Fix creating and resolving absolute NT-style symlinks Date: Sun, 29 Sep 2024 20:50:53 +0200 Message-Id: <20240929185053.10554-8-pali@kernel.org> X-Mailer: git-send-email 2.39.5 In-Reply-To: <20240929185053.10554-1-pali@kernel.org> References: <20240929185053.10554-1-pali@kernel.org> Precedence: bulk X-Mailing-List: linux-cifs@vger.kernel.org List-Id: List-Subscribe: List-Unsubscribe: MIME-Version: 1.0 If the SMB symlink is stored on NT server in absolute form then it points to the NT object hierarchy, which is different from POSIX one and needs some conversion / mapping. To make interoperability with Windows SMB server and WSL subsystem, reuse its logic of mapping between NT paths and POSIX paths into Linux SMB client. WSL subsystem on Windows uses for -t drvfs mount option -o symlinkroot= which specifies the POSIX path where are expected to be mounted lowercase Windows drive letters (without colon). Do same for Linux SMB client and add a new mount option -o symlinkroot= which mimics the drvfs mount option of the same name. It specifies where in the Linux VFS hierarchy is the root of the DOS / Windows drive letters, and translates between absolute NT-style symlinks and absolute Linux VFS symlinks. Default value of symlinkroot is "/mnt", same what is using WSL. Note that DOS / Windows drive letter symlinks are just subset of all possible NT-style symlinks. Drive letters live in NT subtree \??\ and important details about NT paths and object hierarchy are in the comments in this change. When symlink target location from non-POSIX SMB server is in absolute form (indicated by absence of SYMLINK_FLAG_RELATIVE) then it is converted to Linux absolute symlink according to symlinkroot configuration. And when creating a new symlink on non-POSIX SMB server in absolute form then Linux absolute target is converted to NT-style according to symlinkroot configuration. When SMB server is POSIX, then this change does not affect neither reading target location of symlink, nor creating a new symlink. It is expected that POSIX SMB server works with POSIX paths where the absolute root is /. This change improves interoperability of absolute SMB symlinks with Windows SMB servers. Signed-off-by: Pali Rohár --- fs/smb/client/fs_context.c | 22 +++ fs/smb/client/fs_context.h | 2 + fs/smb/client/reparse.c | 276 ++++++++++++++++++++++++++++++++++--- 3 files changed, 281 insertions(+), 19 deletions(-) diff --git a/fs/smb/client/fs_context.c b/fs/smb/client/fs_context.c index 2f0c3894b0f7..22b550860cc8 100644 --- a/fs/smb/client/fs_context.c +++ b/fs/smb/client/fs_context.c @@ -178,6 +178,7 @@ const struct fs_parameter_spec smb3_fs_parameters[] = { fsparam_string("sec", Opt_sec), fsparam_string("cache", Opt_cache), fsparam_string("reparse", Opt_reparse), + fsparam_string("symlinkroot", Opt_symlinkroot), /* Arguments that should be ignored */ fsparam_flag("guest", Opt_ignore), @@ -355,6 +356,7 @@ smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx new_ctx->source = NULL; new_ctx->iocharset = NULL; new_ctx->leaf_fullpath = NULL; + new_ctx->symlinkroot = NULL; /* * Make sure to stay in sync with smb3_cleanup_fs_context_contents() */ @@ -369,6 +371,7 @@ smb3_fs_context_dup(struct smb3_fs_context *new_ctx, struct smb3_fs_context *ctx DUP_CTX_STR(nodename); DUP_CTX_STR(iocharset); DUP_CTX_STR(leaf_fullpath); + DUP_CTX_STR(symlinkroot); return 0; } @@ -1614,9 +1617,26 @@ static int smb3_fs_context_parse_param(struct fs_context *fc, if (parse_reparse_flavor(fc, param->string, ctx)) goto cifs_parse_mount_err; break; + case Opt_symlinkroot: + if (param->string[0] != '/') { + cifs_errorf(fc, "symlinkroot mount options must be absolute path\n"); + goto cifs_parse_mount_err; + } + kfree(ctx->symlinkroot); + ctx->symlinkroot = kstrdup(param->string, GFP_KERNEL); + if (!ctx->symlinkroot) + goto cifs_parse_mount_err; + break; } /* case Opt_ignore: - is ignored as expected ... */ + /* + * By default resolve all native absolute symlinks relative to "/mnt/". + * Same default has drvfs driver running in WSL for resolving SMB shares. + */ + if (!ctx->symlinkroot) + ctx->symlinkroot = kstrdup("/mnt/", GFP_KERNEL); + return 0; cifs_parse_mount_err: @@ -1747,6 +1767,8 @@ smb3_cleanup_fs_context_contents(struct smb3_fs_context *ctx) ctx->prepath = NULL; kfree(ctx->leaf_fullpath); ctx->leaf_fullpath = NULL; + kfree(ctx->symlinkroot); + ctx->symlinkroot = NULL; } void diff --git a/fs/smb/client/fs_context.h b/fs/smb/client/fs_context.h index cf577ec0dd0a..8dd12498ffd8 100644 --- a/fs/smb/client/fs_context.h +++ b/fs/smb/client/fs_context.h @@ -157,6 +157,7 @@ enum cifs_param { Opt_sec, Opt_cache, Opt_reparse, + Opt_symlinkroot, /* Mount options to be ignored */ Opt_ignore, @@ -284,6 +285,7 @@ struct smb3_fs_context { struct cifs_ses *dfs_root_ses; bool dfs_automount:1; /* set for dfs automount only */ enum cifs_reparse_type reparse_type; + char *symlinkroot; /* top level directory for native SMB symlinks in absolute format */ }; extern const struct fs_parameter_spec smb3_fs_parameters[]; diff --git a/fs/smb/client/reparse.c b/fs/smb/client/reparse.c index ca4f96c43508..5845dfde950b 100644 --- a/fs/smb/client/reparse.c +++ b/fs/smb/client/reparse.c @@ -19,35 +19,137 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, const char *full_path, const char *symname) { struct reparse_symlink_data_buffer *buf = NULL; - struct cifs_open_info_data data; + struct cifs_open_info_data data = {}; struct cifs_sb_info *cifs_sb = CIFS_SB(inode->i_sb); struct inode *new; struct kvec iov; - __le16 *path; + char *sym = NULL; + __le16 *path = NULL; bool directory = false; - char *sym, sep = CIFS_DIR_SEP(cifs_sb); - u16 len, plen; + char *symlink_target = NULL; + char sep = CIFS_DIR_SEP(cifs_sb); + u16 len, plen, poff, slen; int rc = 0; len = strlen(symname)+1; - sym = kzalloc(len+1, GFP_KERNEL); /* +1 for possible directory slash */ - if (!sym) - return -ENOMEM; - memcpy(sym, symname, len); + symlink_target = kzalloc(len+1, GFP_KERNEL); /* +1 for possible directory slash */ + if (!symlink_target) { + rc = -ENOMEM; + goto out; + } + memcpy(symlink_target, symname, len); data = (struct cifs_open_info_data) { .reparse_point = true, .reparse = { .tag = IO_REPARSE_TAG_SYMLINK, }, - .symlink_target = sym, + .symlink_target = symlink_target, }; - convert_delimiter(sym, sep); + if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') { + /* + * This is a request to create an absolute symlink on the server + * which does not support POSIX paths, and expects symlink in + * NT-style path. So convert absolute Linux symlink target path + * to the absolute NT-style path. Root of the NT-style path for + * symlinks is specified in "symlinkroot" mount option. This will + * ensure compatibility of this symlink stored in absolute form + * on the SMB server. + */ + if (!strstarts(symname, cifs_sb->ctx->symlinkroot)) { + /* + * If the absolute Linux symlink target path is not + * inside "symlinkroot" location then there is no way + * to convert such Linux symlink to NT-style path. + */ + cifs_dbg(VFS, + "absolute symlink '%s' cannot be converted to NT format " + "because it is outside of symlinkroot='%s'\n", + symname, cifs_sb->ctx->symlinkroot); + rc = -EINVAL; + goto out; + } + len = strlen(cifs_sb->ctx->symlinkroot); + if (cifs_sb->ctx->symlinkroot[len-1] != '/') + len++; + if (symname[len] >= 'a' && symname[len] <= 'z' && + (symname[len+1] == '/' || symname[len+1] == '\0')) { + /* + * Symlink points to Linux target /symlinkroot/x/path/... + * where 'x' is the lowercase local Windows drive. + * NT-style path for 'x' has common form \??\X:\path\... + * with uppercase local Windows drive. + */ + int common_path_len = strlen(symname+len+1)+1; + sym = kzalloc(6+common_path_len, GFP_KERNEL); + if (!sym) { + rc = -ENOMEM; + goto out; + } + memcpy(sym, "\\??\\", 4); + sym[4] = symname[len] - ('a'-'A'); + sym[5] = ':'; + memcpy(sym+6, symname+len+1, common_path_len); + if (sym[6] == '\0') { + /* + * Symlink which target path is just a Windows + * drive letter is definitely directory symlink. + */ + directory = true; + } + } else { + /* Unhandled absolute symlink. Report an error. */ + cifs_dbg( + VFS, + "absolute symlink '%s' cannot be converted to NT format " + "because it points to unknown target\n", + symname); + rc = -EINVAL; + goto out; + } + } else { + /* + * This is request to either create an absolute symlink on + * server which expects POSIX paths or it is an request to + * create a relative symlink from the current directory. + * These paths have same format as relative SMB symlinks, + * so no conversion is needed. So just take symname as-is. + */ + sym = kstrdup(symname, GFP_KERNEL); + if (!sym) { + rc = -ENOMEM; + goto out; + } + } + + if (sep == '\\') + convert_delimiter(sym, sep); + + /* + * For absolute NT symlinks it is required to pass also leading + * backslash and to not mangle NT object prefix "\\??\\" and not to + * mangle colon in drive letter. But cifs_convert_path_to_utf16() + * removes leading backslash and replaces '?' and ':'. So temporary + * mask these characters in NT object prefix by '_' and then change + * them back. + */ + if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') + sym[0] = sym[1] = sym[2] = sym[5] = '_'; + path = cifs_convert_path_to_utf16(sym, cifs_sb); if (!path) { rc = -ENOMEM; goto out; } + if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') { + sym[0] = '\\'; + sym[1] = sym[2] = '?'; + sym[5] = ':'; + path[0] = '\\'; + path[1] = path[2] = '?'; + path[5] = ':'; + } + /* * SMB distinguish between symlink to directory and symlink to file. * They cannot be exchanged (symlink of file type which points to @@ -167,8 +269,18 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, data.symlink_target[len+1] = '\0'; } - plen = 2 * UniStrnlen((wchar_t *)path, PATH_MAX); - len = sizeof(*buf) + plen * 2; + slen = 2 * UniStrnlen((wchar_t *)path, PATH_MAX); + poff = 0; + plen = slen; + if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) && symname[0] == '/') { + /* + * For absolute NT symlinks skip leading "\\??\\" in PrintName as + * PrintName is user visible location in DOS/Win32 format (not in NT format). + */ + poff = 4; + plen -= 2 * poff; + } + len = sizeof(*buf) + plen + slen; buf = kzalloc(len, GFP_KERNEL); if (!buf) { rc = -ENOMEM; @@ -177,17 +289,17 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, buf->ReparseTag = cpu_to_le32(IO_REPARSE_TAG_SYMLINK); buf->ReparseDataLength = cpu_to_le16(len - sizeof(struct reparse_data_buffer)); + buf->SubstituteNameOffset = cpu_to_le16(plen); - buf->SubstituteNameLength = cpu_to_le16(plen); - memcpy(&buf->PathBuffer[plen], path, plen); + buf->SubstituteNameLength = cpu_to_le16(slen); + memcpy(&buf->PathBuffer[plen], path, slen); + buf->PrintNameOffset = 0; buf->PrintNameLength = cpu_to_le16(plen); - memcpy(buf->PathBuffer, path, plen); + memcpy(buf->PathBuffer, path+poff, plen); + buf->Flags = cpu_to_le32(*symname != '/' ? SYMLINK_FLAG_RELATIVE : 0); - if (*sym != sep) - buf->Flags = cpu_to_le32(SYMLINK_FLAG_RELATIVE); - convert_delimiter(sym, '/'); iov.iov_base = buf; iov.iov_len = len; new = smb2_create_reparse_inode(&data, inode->i_sb, xid, @@ -198,6 +310,7 @@ int smb2_create_reparse_symlink(const unsigned int xid, struct inode *inode, else rc = PTR_ERR(new); out: + kfree(sym); kfree(path); cifs_free_open_info(&data); kfree(buf); @@ -505,6 +618,9 @@ int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, char sep = CIFS_DIR_SEP(cifs_sb); char *linux_target = NULL; char *smb_target = NULL; + int symlinkroot_len; + int abs_path_len; + char *abs_path; int levels; int rc; int i; @@ -525,7 +641,123 @@ int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, goto out; } - if (smb_target[0] == sep && relative) { + if (!(cifs_sb->mnt_cifs_flags & CIFS_MOUNT_POSIX_PATHS) && !relative) { + /* + * This is an absolute symlink from the server which does not + * support POSIX paths, so the symlink is in NT-style path. + * So convert it to absolute Linux symlink target path. Root of + * the NT-style path for symlinks is specified in "symlinkroot" + * mount option. + * + * Root of the DOS and Win32 paths is at NT path \??\ + * It means that DOS/Win32 path C:\folder\file.txt is + * NT path \??\C:\folder\file.txt + * + * NT systems have some well-known object symlinks in their NT + * hierarchy, which is needed to take into account when resolving + * other symlinks. Most commonly used symlink paths are: + * \?? -> \GLOBAL?? + * \DosDevices -> \?? + * \GLOBAL??\GLOBALROOT -> \ + * \GLOBAL??\Global -> \GLOBAL?? + * \GLOBAL??\NUL -> \Device\Null + * \GLOBAL??\UNC -> \Device\Mup + * \GLOBAL??\PhysicalDrive0 -> \Device\Harddisk0\DR0 (for each harddisk) + * \GLOBAL??\A: -> \Device\Floppy0 (if A: is the first floppy) + * \GLOBAL??\C: -> \Device\HarddiskVolume1 (if C: is the first harddisk) + * \GLOBAL??\D: -> \Device\CdRom0 (if D: is first cdrom) + * \SystemRoot -> \Device\Harddisk0\Partition1\WINDOWS (or where is NT system installed) + * \Volume{...} -> \Device\HarddiskVolume1 (where ... is system generated guid) + * + * In most common cases, absolute NT symlinks points to path on + * DOS/Win32 drive letter, system-specific Volume or on UNC share. + * Here are few examples of commonly used absolute NT symlinks + * created by mklink.exe tool: + * \??\C:\folder\file.txt + * \??\\C:\folder\file.txt + * \??\UNC\server\share\file.txt + * \??\\UNC\server\share\file.txt + * \??\Volume{b75e2c83-0000-0000-0000-602f00000000}\folder\file.txt + * + * It means that the most common path prefix \??\ is also NT path + * symlink (to \GLOBAL??). It is less common that second path + * separator is double backslash, but it is valid. + * + * Volume guid is randomly generated by the target system and so + * only the target system knows the mapping between guid and the + * hardisk number. Over SMB it is not possible to resolve this + * mapping, therefore symlinks pointing to target location of + * volume guids are totally unusable over SMB. + * + * For now parse only symlink paths available for DOS and Win32. + * Those are paths with \??\ prefix or paths which points to \??\ + * via other NT symlink (\DosDevices\, \GLOBAL??\, ...). + */ + abs_path = smb_target; +globalroot: + if (strstarts(abs_path, "\\??\\")) + abs_path += sizeof("\\??\\")-1; + else if (strstarts(abs_path, "\\DosDevices\\")) + abs_path += sizeof("\\DosDevices\\")-1; + else if (strstarts(abs_path, "\\GLOBAL??\\")) + abs_path += sizeof("\\GLOBAL??\\")-1; + else { + /* Unhandled absolute symlink, points outside of DOS/Win32 */ + cifs_dbg(VFS, + "absolute symlink '%s' cannot be converted from NT format " + "because points to unknown target\n", + smb_target); + rc = -EIO; + goto out; + } + + /* Sometimes path separator after \?? is double backslash */ + if (abs_path[0] == '\\') + abs_path++; + + while (strstarts(abs_path, "Global\\")) + abs_path += sizeof("Global\\")-1; + + if (strstarts(abs_path, "GLOBALROOT\\")) { + /* Label globalroot requires path with leading '\\', so do not trim '\\' */ + abs_path += sizeof("GLOBALROOT")-1; + goto globalroot; + } + + /* For now parse only paths to drive letters */ + if (((abs_path[0] >= 'A' && abs_path[0] <= 'Z') || + (abs_path[0] >= 'a' && abs_path[0] <= 'z')) && + abs_path[1] == ':' && + (abs_path[2] == '\\' || abs_path[2] == '\0')) { + /* Convert drive letter to lowercase and drop colon */ + char drive_letter = abs_path[0]; + if (drive_letter >= 'A' && drive_letter <= 'Z') + drive_letter += 'a'-'A'; + abs_path++; + abs_path[0] = drive_letter; + } else { + /* Unhandled absolute symlink. Report an error. */ + cifs_dbg(VFS, + "absolute symlink '%s' cannot be converted from NT format " + "because points to unknown target\n", + smb_target); + rc = -EIO; + goto out; + } + + abs_path_len = strlen(abs_path)+1; + symlinkroot_len = strlen(cifs_sb->ctx->symlinkroot); + if (cifs_sb->ctx->symlinkroot[symlinkroot_len-1] == '/') + symlinkroot_len--; + linux_target = kmalloc(symlinkroot_len + 1 + abs_path_len, GFP_KERNEL); + if (!linux_target) { + rc = -ENOMEM; + goto out; + } + memcpy(linux_target, cifs_sb->ctx->symlinkroot, symlinkroot_len); + linux_target[symlinkroot_len] = '/'; + memcpy(linux_target + symlinkroot_len + 1, abs_path, abs_path_len); + } else if (smb_target[0] == sep && relative) { /* * This is a relative SMB symlink from the top of the share, * which is the top level directory of the Linux mount point. @@ -554,6 +786,12 @@ int smb2_parse_native_symlink(char **target, const char *buf, unsigned int len, } memcpy(linux_target + levels*3, smb_target+1, smb_target_len); /* +1 to skip leading sep */ } else { + /* + * This is either an absolute symlink in POSIX-style format + * or relative SMB symlink from the current directory. + * These paths have same format as Linux symlinks, so no + * conversion is needed. + */ linux_target = smb_target; smb_target = NULL; }