原文地址:buckleyisms.com/blog/anecdo…
原文作者:buckleyisms.com/
发布时间:2021年8月7日
像往常一样,这篇文章反映了我自己的观点,而不是其他人的。
最近,Twitter上又有一些关于macOS沙盒对一个应用程序可以同时访问的开放文件数量的限制的讨论。由于这一限制,应用程序仍然遇到问题,而在苹果公司之外,并没有很多关于这一问题的技术细节,所以我想分享我对它的理解。
macOS只允许沙盒应用程序一次访问有限数量的文件,但应用程序没有办法查询它可以打开多少文件,或者它是否接近限制。事实上,这个限制取决于电脑中安装的内存数量和其他应用程序打开的文件数量。由于这一限制只影响那些可以批量处理数千个文件的应用程序,以及想要这样做的用户,许多用户和开发者仍然没有意识到这一点,尽管它已经影响了苹果自己的一些应用程序。
我第一次意识到这个限制是在2012年初。我当时正在开发一款可以激活字体文件的应用程序,尽管大多数用户每次只使用少量的字体,但在OS X 10.7的更新中引入这一限制后,我们很快就收到了用户的支持请求。然而,在这种情况下,导致这个问题的原因并不明显。在最初的几百次激活后,字体激活开始失败,并出现一个通用错误代码。系统日志报告说,沙盒拒绝了我们的应用程序的文件读取数据,但我们的应用程序没有被沙盒化,所以这似乎是一个错误。
尽管我们试图自己解决这个问题,并在苹果开发者技术支持的帮助下,直到几个月后的WWDC 2012,我们才能够得到关于为什么会发生这种情况的答案。在WWDC上,我尽快排队进入实验室,并最终能够与一位负责字体激活的工程师交谈。在与他分享了一些展示问题的代码后,他能够准确地找到字体守护程序中发生故障的地方。当试图打开字体文件时,守护进程收到了一个通用的错误,即该文件无法打开。然而,他不确定到底该和谁谈这个问题,所以他给了他一页详细的笔记,以及其他可以交谈的工程师的建议,还有他们会在哪个实验室。
所有的工程师都很乐于助人,并希望弄清这个谜底,但他们都不得不建议与其他工程师交谈,直到最后,我被推荐给安全实验室的一名工程师。当时天色已晚,实验室即将关闭。我清楚地记得,我腋下夹着笔记本电脑,穿过房间,跑到被封锁的安全实验室区域。在那里,我遇到了一位非常有帮助的工程师,他能够解开这个谜团。
macOS使用安全范围内的书签,允许应用程序访问它们通常无法访问的文件。为了促进这一点,macOS必须保持一个映射,即哪些应用程序被允许访问哪些文件路径,而出于安全原因,这个映射必须存在于内核内存中。内核内存是有线的。内核不能访问虚拟内存。内核保留了四分之一的物理内存,在这四分之一中,内核为文件映射分配了一定比例的内存。
他从未直言不讳,但我的印象是,安全团队最近才知道这给应用程序带来了问题,而且只是因为他们得到了iPhotos团队的通知,但他们并不知道这给字体守护程序带来了问题。我们的雷达、DTS票据以及与苹果代表的电子邮件都未能将这个问题传递给安全团队。作为一个旁观者,这就是为什么WWDC实验室是如此重要和有价值。要让苹果内部的适当工程师注意到这些问题往往是不可能的。即使他们不能解决这些问题,实验室里的工程师往往可以提供信息和解决方法。
这位工程师留到很晚,帮助我为安全团队和字体团队提交关于这个问题的雷达,但他警告说,根本问题可能不会很快得到解决。这将取决于应用程序开发人员和苹果公司的其他团队,以解决该限制造成的问题。这是macOS应用沙盒设计的基础,只有当他们找到一种安全和高性能的方法在用户空间中存储这种映射时才能解决,而这是不可能的。苹果不打算扔掉沙盒或损害其安全性来消除这一限制。考虑到这个问题至今仍是一个问题,他是对的。
从苹果公司外部看来,自2011年首次亮相以来,应用沙盒几乎没有什么变化。许多例外权利仍然是 "临时 "的。当这些临时例外情况被引入时,苹果鼓励开发者提交雷达,解释他们为什么需要这些例外情况,表明苹果将在未来扩大沙盒,以涵盖需要这些例外情况的使用案例,然后删除临时权利。据我所知,尽管沙盒中的安全漏洞已经被修补过,而且还增加了新的权利,但沙盒从未被扩展到包括这些使用情况,也从未删除过任何临时例外,这使得 "临时 "在某种程度上成了一个错误的说法。
由于从未在苹果公司工作过,我将避免对这一现象的原因进行无知的猜测。考虑到这种限制仍然影响着像微软Office这样的高知名度的应用程序,我不认为苹果没有动力去解决这个问题,但考虑到这个问题已经存在了多长时间,在苹果宣布其他情况之前,我们不能假设它很快会消失。应用开发者要尽可能地解决这个问题。
有两种方法来处理这个问题。第一个是尽快放弃安全范围的书签,第二个是提示用户打开文件夹,而不是文件。
安全范围的书签可以使用NSURL的stopAccessingSecurityScopedResource方法来放弃。这个方法释放了内核中的文件条目。如果你的应用程序一次只需要访问几个文件,你可以在完成每个文件后立即调用这个方法。
在这个方法不起作用的情况下,你可以提示用户打开文件夹而不是文件。当一个文件夹在NSOpenPanel中被打开时,只有一个条目被添加到内核内存中,但你的应用程序仍然能够访问该文件夹中的文件。然而,你可能需要限制自己使用字符串路径而不是NSURLs访问这些文件的API。打开文件夹后,你的应用程序将需要列举该文件夹的内容,并提供自己的文件列表供用户选择文件。任何通过NSOpenPanel打开的文件都会消耗内核内存。
在我的案例中,由于我在WWDC 2012上得到的帮助,我能够了解到,当使用CoreText API激活字体时,字体守护程序会正确地放弃书签,而不是我们正在使用的新淘汰的ATS API。我们能够切换到CoreText,但是全部使用ATS的Carbon应用程序无法看到所有激活的字体。看起来,这些应用程序在收到字体激活的通知后,会与字体守护程序进行通信,以使用ATS功能获得字体数据,而这些功能仍然没有足够快地放弃这些书签。据我所知,这一点从未被修复,但是由于Carbon从未被完全移植到64位,Carbon应用的使用量下降,直到32位支持在macOS 10.15中被移除,使这个问题变得毫无意义。
然而,当时许多流行的应用程序都是用Carbon编写的,或者至少包含足够的Carbon代码而受到这个问题的影响,包括微软Office和Adobe Creative Suite的部分内容。当用户试图激活大量的字体时,我们不得不在我们的应用程序中添加一个警告,解释说并非所有的字体都能在这些应用程序中使用。一些用户抱怨得很厉害,但我们有一个令人信服的演示来证明这个问题不是我们应用程序的错。如果用户只是将他们所有的字体文件移到~/Library/Fonts中,他们可以确认这些字体在TextEdit中都是可用的,但在Word中却不是。