You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

如何正确避免StreamReader.ReadToEnd()引发的内存不足异常及DoS风险?

问题描述

我通过以下代码使用StreamReader.ReadToEnd()读取嵌入的文本文件:

private string GetCSSReset()
{
    var assembly = typeof(EmailSender).GetTypeInfo().Assembly;
    using (var stream = EmbeddedScriptsProvider.ReadInternal(assembly, GlobalVars.CSSFileName))
    {
        using (var textReader = new StreamReader(stream))
        {
            var text = textReader.ReadToEnd();
            return $"{text}";
        }
    }
}

但Fortify SCA扫描指出这段代码可能引发拒绝服务(DoS)攻击。我已经修改了EmbeddedScriptsProvider.ReadInternal()方法来检查流的长度:

internal static long MBtoContentLength(int megaByte)
{
    return megaByte * 1024 * 1024;
}
internal static Stream? ReadInternal(Assembly assembly, string resource)
{
    var stream = assembly.GetManifestResourceStream($"{assembly.GetName().Name}.{resource}");
    if(stream.Length > MBtoContentLength(GlobalVars.MAX_FILE_SIZE_MB))
    {
        throw new InvalidOperationException("File size too large.");
    }
    return stream;
}

请问这种处理方式是否正确?我见过使用.Read()并检查字符串长度的方法,该如何将其应用到ReadToEnd()场景中?


你的处理方式是否正确?

这种处理方式是有效的,但有几个细节需要完善:

  • 对于嵌入资源而言,stream.Length是可靠的——嵌入资源的大小在编译时就已固定,不会在运行时被篡改,提前检查流长度可以避免ReadToEnd()一次性加载过大内容到内存,从根源上防止内存耗尽引发的DoS。
  • 要确保GlobalVars.MAX_FILE_SIZE_MB的取值合理:既要覆盖正常业务所需的最大文件尺寸,又要限制在不会造成内存压力的范围内。
  • 需补充空值判断:如果GetManifestResourceStream返回null(比如资源不存在),直接访问stream.Length会抛出NullReferenceException,建议先判断stream是否为null,再执行长度检查。

如何将.Read()检查长度的方法应用到ReadToEnd()场景?

如果希望通过分段读取(用Read())来控制内存占用,替代一次性ReadToEnd(),可以按以下方式实现:

private string GetCSSReset()
{
    var assembly = typeof(EmailSender).GetTypeInfo().Assembly;
    using (var stream = EmbeddedScriptsProvider.ReadInternal(assembly, GlobalVars.CSSFileName))
    {
        if (stream == null)
        {
            throw new InvalidOperationException("Resource not found.");
        }
        using (var textReader = new StreamReader(stream))
        {
            // 按字符数估算上限(需根据编码调整,此处为简化处理)
            var maxChars = (int)(MBtoContentLength(GlobalVars.MAX_FILE_SIZE_MB) / sizeof(char));
            var sb = new StringBuilder();
            char[] buffer = new char[4096]; // 4KB缓冲区,可根据实际调整
            int readChars;
            
            while ((readChars = textReader.Read(buffer, 0, buffer.Length)) > 0)
            {
                if (sb.Length + readChars > maxChars)
                {
                    throw new InvalidOperationException("File size exceeds allowed limit.");
                }
                sb.Append(buffer, 0, readChars);
            }
            return sb.ToString();
        }
    }
}

这种方式的优势:

  • 不会一次性将整个文件加载到内存,而是分块读取,更适合处理超大文件(不过对于嵌入资源来说,提前检查流长度已经足够,这种方式更多用于动态加载的外部文件)。
  • 实时检查已读取的字符数,一旦超过上限就抛出异常,避免内存溢出。

需要注意:

  • 字符数和字节数的转换要考虑编码(比如UTF-8中一个字符可能占1-4字节),上面的估算为简化处理,若要精确控制,可先通过stream.Read读取字节再转换为字符串,但逻辑会更复杂。
  • 缓冲区大小可根据实际情况调整:过大的缓冲区会失去分段读取的意义,过小则会增加IO次数。

内容的提问来源于stack exchange,提问作者Lee

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.20 13:03:30