cloudflare / next-on-pages

CLI to build and develop Next.js apps for Cloudflare Pages
https://www.npmjs.com/package/@cloudflare/next-on-pages
MIT License
1.27k stars 121 forks source link

[🐛 Bug]: WebAssembly.instantiate(): Wasm code generation disallowed by embedder #704

Closed davidtranjs closed 7 months ago

davidtranjs commented 7 months ago

next-on-pages environment related information

System: Platform: darwin Arch: arm64 Version: Darwin Kernel Version 23.1.0: Mon Oct 9 21:27:24 PDT 2023; root:xnu-10002.41.9~6/RELEASE_ARM64_T6000 CPU: (10) arm64 Apple M1 Pro Memory: 16 GB Shell: /bin/zsh Package Manager Used: npm (9.6.7)

Relevant Packages: @cloudflare/next-on-pages: 1.9.0 vercel: N/A next: N/A

Description

Hi everyone,

Thanks for this awesome library.

Currently I have an NextJS app that deployed on Cloudflare pages, my app have an API route for generate OG Image in PNG format.

I use satori for generate SVG data and @resvg/resvg-wasm for convert SVG to PNG format.

Here is my code at pages/api/og.tsx

import OgImageTemplate from "@/components/OgImageTemplate";
import satori from "satori";
import { Resvg, initWasm } from "@resvg/resvg-wasm";

export const runtime = "edge";

export default async function handler(req) {
  const fontRegular = await fetch(
    "https://cdn.jsdelivr.net/fontsource/fonts/poppins@latest/latin-400-normal.ttf"
  ).then((res) => res.arrayBuffer());

  const { searchParams } = new URL(req.url);
  const title = searchParams.get("title");
  const sub = searchParams.get("sub");

  const ogImage = await satori(<OgImageTemplate title={title} sub={sub} />, {
    width: 1200,
    height: 630,
    fonts: [
      {
        name: "Poppins",
        data: fontRegular,
        weight: 400,
        style: "normal",
      }
    ],
  });

  const wasmResponse = await fetch("https://unpkg.com/@resvg/resvg-wasm@2.6.0/index_bg.wasm");
  const wasmArrayBuffer = await wasmResponse.arrayBuffer();

  try {
    await initWasm(wasmArrayBuffer); // => this line throw the error

    const resvg = new Resvg(ogImage, {
      font: {
        loadSystemFonts: false,
      },
    });
    const pngData = resvg.render();
    const pngBuffer = pngData.asPng();

    return new Response(pngBuffer, {
      status: 200,
      headers: {
        "Content-Type": "image/png",
      },
    });
  } catch (error) {
    console.error("Resvg wasm not initialized => ", error);
    return new Response("Resvg wasm not initialized", { status: 500 });
  }
}

In local, it work as expect, but when I deploy to Cloudflare pages, but when I access the API, function initWasm keep throw error with message

CompileError: WebAssembly.instantiate(): Wasm code generation disallowed by embedder

Is there anyone know the solution for this ?

Live URL: https://008c691d.personal-doh.pages.dev/api/og?title=xyz

Reproduction

  1. Deploy to Cloudflare pages
  2. Trigger request to API route that generate og image - https://008c691d.personal-doh.pages.dev/api/og?title=xyz
  3. Check function error log in Cloudflare pages setting

Pages Deployment Method

Pages CI (GitHub/GitLab integration)

Pages Deployment ID

https://008c691d.personal-doh.pages.dev

Additional Information

No response

Would you like to help?

JoepKockelkorn commented 7 months ago

I'm running Qwik City on Vercel Edge and I'm using the framework agnostic og-image library https://github.com/fabian-hiller/og-img but I'm running into exactly the same error:

CompileError: WebAssembly.instantiate(): Wasm code generation disallowed by embedder
    at (q-C8RaJ7ou.js:119:7794)
    at (q-C8RaJ7ou.js:119:9924)
    at (q-C8RaJ7ou.js:119:10084)
    at (q-C8RaJ7ou.js:186:29695)

I guess this is a security measure to prevent loading external wasm files?

dario-piotrowicz commented 7 months ago

Hi @JoepKockelkorn, thank you for the issue 🙂 (and for the nice words regarding the library ❤️)

Yes, wasm code generation is not supported in our runtime for security reasons, exactly like in JavaScript where eval and new Function are also not supported.

I can see that we don't document this in our docs, I've created an issue for that: https://github.com/cloudflare/cloudflare-docs/issues/13469

I don't think there can be any possible workaround for this either 😓

JoepKockelkorn commented 7 months ago

Thanks for your clarification. Security above everything!

For the nice words you'll have to thank @dungmidside, I just ran into something similar on Vercel Edge 😉. But I'm sure cloudflare pages is great!

Thanks for creating the issue 👍

dario-piotrowicz commented 7 months ago

For the nice words you'll have to thank @dungmidside

Sorry, I somehow missed that the comment was from a different user 😅🤦

@dungmidside thanks! ❤️

dario-piotrowicz commented 7 months ago

@dungmidside @JoepKockelkorn since there isn't really much we can do about this here I would be inclined to close this issue, would that work for you? 🙂

JoepKockelkorn commented 7 months ago

Sure!

davidtranjs commented 7 months ago

Hi @dario-piotrowicz, thanks for your response.

I just wonder about this, if cloudflare block wasm code generation, why this worker-og library - which use the same initWasm function, work fine ?

I have another worker that use this library and it worked even it seem use the same @resvg/resvg-wasm with my code in NextJS app

import { ImageResponse } from 'workers-og';

export default {
  async fetch(req: Request, env: Env, ctx: ExecutionContext): Promise<Response> {
    const fontRegular = await fetch('https://cdn.jsdelivr.net/fontsource/fonts/poppins@latest/latin-400-normal.ttf').then((res) =>
      res.arrayBuffer()
    );

    const decodedUrl = req.url.replace(/&amp;/g, '&');
    const params = new URLSearchParams(new URL(decodedUrl).search);
    const title = params.get('title');

    const html = `<div style="">${title}</div>
    `;

    return new ImageResponse(html, {
      width: 1200,
      height: 630,
      fonts: [
        {
          name: 'Poppins',
          data: fontRegular,
          weight: 400,
          style: 'normal',
        },
      ],
    });
  },
};
dario-piotrowicz commented 7 months ago

@dungmidside the code you shared imports the wasm modules from local files: https://github.com/kvnang/workers-og/blob/52489a562245fb01f10a38a7a1a523fc03497a1c/packages/workers-og/src/og.ts#L8-L11

So those modules are fine to be initialized, the issue if that the you cannot fetch the wasm modules from some external source (like you did with fetch("https://unpkg.com/@resvg/resvg-wasm@2.6.0/index_bg.wasm")) and initialize them (since that's not trusted code).

davidtranjs commented 7 months ago

@dario-piotrowicz I used fetch because import wasmFile from './index_bg.wasm' not worked in NextJS So this could solve by adapt NextJS config to able import *.wasm file ?

dario-piotrowicz commented 7 months ago

@dungmidside yes exactly, you could update the config file to allow for wasm imports or more simply you could just import the wasm using import ... from '...?module'

Like I did here for example:

I've also just found out that there's an error page regarding this in the Next.js docs 🙂: https://nextjs.org/docs/messages/edge-dynamic-code-evaluation

davidtranjs commented 7 months ago

@dario-piotrowicz thanks for your suggestion about import with ?module But when I deploy to Cloudflare Pages, it show this error. Is there any NextJS config changes for this to work ?


Build Completed in .vercel/output [30s]
--
23:05:21.143 | ⚡️ Completed `npx vercel build`.
23:05:21.741 | ⚡️ Unexpected error: ENOENT: no such file or directory, copyfile '/opt/buildhome/repo/.vercel/output/functions/api/og.func/wasm/wasm_3481987cfb7f2b7b841f3cf7520cc11c1d5434cd.wasm' -> '/opt/buildhome/repo/.vercel/output/static/_worker.js/__next-on-pages-dist__/wasm/wasm_3481987cfb7f2b7b841f3cf7520cc11c1d5434cd.wasm'
23:05:21.776 | Finished

I copy index_bg.wasm file in node_modules of @resvg/resvg-wasm and import wasmModule from './index_bg.wasm?module';, it worked in local

import { Resvg, initWasm } from "@resvg/resvg-wasm";
import wasmModule from './index_bg.wasm?module';
// other code
await initWasm(wasmModule);
dario-piotrowicz commented 7 months ago

@dungmidside the error you shared is an error that should have already been fixed in: https://github.com/cloudflare/next-on-pages/pull/681, which has been shipped as part of the 1.10.0 release.

From the PR description it seems like you're on 1.9.0?

Could you make sure to use 1.10.0 and let me know if that helps? 🙏

davidtranjs commented 7 months ago

@dario-piotrowicz it seem work now But now my worker is exceed 1MB : (( Maybe because the wasm file of resvg is about 2.5MB already Thanks a lot for your quick response 💯

dario-piotrowicz commented 7 months ago

@dungmidside my pleasure, I'm glad we did get to the bottom of it 😄

regarding the limit yeah sorry about that, there's nothing I can really do about it 😓 but if you could try out the paid plan there the limit is 10MB 🙂 (https://developers.cloudflare.com/workers/platform/limits/#worker-size)